Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[6.x] Fix Model::withoutEvents() not registering listeners inside boot() #33149

Merged

Conversation

derekmd
Copy link
Contributor

@derekmd derekmd commented Jun 8, 2020

Fixes #32935

Eloquent model boot() hooks (and booting()/booted() in 7.x+) run once per-class in each request. If an Eloquent model class is instantiated for the first time inside Model::withoutEvents(), boot() callbacks registering custom event listeners expected later will be skipped. e.g.,

class Invoice extends Model
{
    public static function boot()
    {
        parent::boot();

        static::creating(function (Invoice $invoice) {
            if (blank($invoice->number)) {
                $invoice->generateNumber();
            }
        });
    }
}
public function test_it_will_auto_generate_unique_invoice_number()
{
    $firstInvoice = User::withoutEvents(function () {
        // Wish to avoid firing listeners from User model events.
        $user = factory(User::class)->create();

        return Invoice::create([
            'user_id' => $user->id,
            'number' => 'I'.Str::random(5),
        ]);
    });

    // This doesn't fire the 'creating' listener to auto-fill database column
    // "invoices"."number". `Invoice` above had no event dispatcher so the
    // 'creating' listener was never registered.
    $secondInvoice = Invoice::create(['user_id' => $firstInvoice->user_id]);

Instead of Model::withoutEvents() removing the event dispatcher from all models, replace it with a null pattern implementation. Fired event dispatch() attempts are noop when inside withoutEvents(). However event listeners can still be registered on the concrete dispatcher for post-withoutEvents() calls.

If an Eloquent model class is instantiated for the
first time inside a withoutEvents() Closure, any
model boot() callbacks registering custom event
listeners will be skipped.

Instead of removing the dispatcher, replaced it with
a null pattern implementation. Registration method
calls still go through to the concrete dispatcher
however fired event dispatch() calls become noop.
@derekmd derekmd force-pushed the without-events-registers-listeners branch from f6ebf5f to 18b68cf Compare June 8, 2020 14:59
@derekmd
Copy link
Contributor Author

derekmd commented Jun 8, 2020

Packagist and/or GitHub hosting seem to be eating it at the moment so CI step "Install dependencies" for Composer is failing from network timeouts.

@taylorotwell taylorotwell merged commit 3e47385 into laravel:6.x Jun 8, 2020
@taylorotwell
Copy link
Member

Thanks

@derekmd derekmd deleted the without-events-registers-listeners branch June 8, 2020 15:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants