Skip to content

Commit 7672871

Browse files
[13.x] Do not allow new model instances to be created during boot (#55685)
* Add failing test * Only mark a model class as booted after the boot process has been completed * Mark a model class as booting as soon as the boot process starts - A model class is removed from the `booting` array as soon as the boot process has completed. * Throw an exception if `bootIfNotBooted` is called while booting - This infers that something in the boot process expected the boot to already be finished. - If we just ignore the call then the model class would be in an inconsistent state for whichever code called it. * formatting * formatting --------- Co-authored-by: Taylor Otwell <[email protected]>
1 parent 5900088 commit 7672871

File tree

2 files changed

+32
-1
lines changed

2 files changed

+32
-1
lines changed

src/Illuminate/Database/Eloquent/Model.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use ArrayAccess;
66
use Closure;
7+
use Exception;
78
use Illuminate\Contracts\Broadcasting\HasBroadcastChannel;
89
use Illuminate\Contracts\Queue\QueueableCollection;
910
use Illuminate\Contracts\Queue\QueueableEntity;
@@ -143,6 +144,13 @@ abstract class Model implements Arrayable, ArrayAccess, CanBeEscapedWhenCastToSt
143144
*/
144145
protected static $dispatcher;
145146

147+
/**
148+
* The models that are currently being booted.
149+
*
150+
* @var array
151+
*/
152+
protected static $booting = [];
153+
146154
/**
147155
* The array of booted models.
148156
*
@@ -286,12 +294,20 @@ public function __construct(array $attributes = [])
286294
protected function bootIfNotBooted()
287295
{
288296
if (! isset(static::$booted[static::class])) {
289-
static::$booted[static::class] = true;
297+
if (isset(static::$booting[static::class])) {
298+
throw new LogicException('The ['.__METHOD__.'] method may not be called on model ['.static::class.'] while it is being booted.');
299+
}
300+
301+
static::$booting[static::class] = true;
290302

291303
$this->fireModelEvent('booting', false);
292304

293305
static::booting();
294306
static::boot();
307+
308+
static::$booted[static::class] = true;
309+
unset(static::$booting[static::class]);
310+
295311
static::booted();
296312

297313
static::$bootedCallbacks[static::class] ??= [];

tests/Database/DatabaseEloquentModelTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3330,6 +3330,21 @@ public function testUseFactoryAttribute()
33303330
$this->assertEquals(EloquentModelWithUseFactoryAttribute::class, $factory->modelName());
33313331
$this->assertEquals('test name', $instance->name); // Small smoke test to ensure the factory is working
33323332
}
3333+
3334+
public function testNestedModelBootingIsDisallowed()
3335+
{
3336+
$this->expectExceptionMessageMatches('/The \[(.+)] method may not be called on model \[(.+)\] while it is being booted\./');
3337+
3338+
$model = new class extends Model
3339+
{
3340+
protected static function boot()
3341+
{
3342+
parent::boot();
3343+
3344+
$tableName = (new static())->getTable();
3345+
}
3346+
};
3347+
}
33333348
}
33343349

33353350
class EloquentTestObserverStub

0 commit comments

Comments
 (0)