Skip to content

[13.x] Resolve scheduled event callback parameter by type rather than name - #60197

Merged
taylorotwell merged 2 commits into
laravel:13.xfrom
kayw-geek:fix/scheduled-event-callback-type-based-injection
May 20, 2026
Merged

taylorotwell merged 2 commits into
laravel:13.xfrom
kayw-geek:fix/scheduled-event-callback-type-based-injection

Conversation

@kayw-geek

Copy link
Copy Markdown
Contributor

Fixes a name-based parameter resolution issue introduced in #60144.

Background

#60144 made it possible to pass the scheduled Event into lifecycle callbacks, and the PR description promises that you can "typehint a callback with Event $event as a parameter". However, the implementation only injects the event when the closure parameter is literally named $event. The is_a($this, $eventParameterType) check in the same method shows the intent was type-based, but the lookup is name-based — these contradict each other.

All tests added by #60144 happen to use the parameter name $event, so the working contract (typehint-based) was never exercised.

Reproduction

Schedule::command('inspire')
    ->before(function (Event $scheduledEvent) {
        Log::info("Starting {$scheduledEvent->command}");
    });

This throws BindingResolutionException: the parameter lookup misses on the name 'event', no value is injected, and the container falls back to autowiring a new Event — which fails because EventMutex isn't instantiable.

Fix

Iterate the closure parameters and inject $this into the first parameter whose type is a class that $this is an instance of. This matches how Laravel container DI works elsewhere — by type, not by name.

After

Schedule::command('inspire')
    ->before(function (Event $scheduledEvent) {  // works
        Log::info("Starting {$scheduledEvent->command}");
    });

Schedule::command('inspire')
    ->before(function (Event $event) {  // still works
        Log::info("Starting {$event->command}");
    });

Comment on lines +794 to +800
foreach ($parameters as $name => $type) {
if ($type !== null && is_a($this, $type)) {
return [$name => $this];
}
}

return ['event' => $this];
return [];

This comment was marked as low quality.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

array_any returns bool though, so $event would just be true/false — $name from the closure isn't in scope either. Did you mean array_find_key?

Either way I'd lean toward keeping the foreach — the other 3 spots using closureParameterTypes() in this file all do the same $parameters = ...; foreach thing.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, sorry–my bad 🤦🏻 😅

@taylorotwell
taylorotwell merged commit c16fe0c into laravel:13.x May 20, 2026
52 checks passed
binaryfire added a commit to hypervel/components-backup that referenced this pull request Oct 1, 2026
When a group seeds pending event attributes, apply that pending copy once and
return. Applying the group again duplicated lifecycle callbacks and macros.
Use the current Laravel merge order, remove unreachable dependency guards,
and retain Hypervel's coroutine execution and event observation boundaries.

Restore the complete applicable grouping, callback, quarterly-frequency and
run-command tests. Use a shared stateless event mutex fixture and join the
task coroutine owned by each background-command test before asserting its
outcome. Preserve mutable dates in the repeat-start test so it protects the
copy guard. Correct the reversed elapsed-time comparison that kept the
sub-minute maintenance test from ever entering maintenance mode.

Remove the ineffective scheduled-task user API and its pending attributes,
generated facade annotation and Telescope metadata/UI. Tasks share the
scheduler's OS user; document running the scheduler under the required user
or using an explicit system command. Keep the long-running schedule:run
replacement for schedule:work and document the --once cron adaptation.
Complete the linked maintenance-view deployment guidance alongside these
porting-guide changes.

Upstream scheduler history:
laravel/framework#58926
laravel/framework#60133
laravel/framework#60144
laravel/framework#60148
laravel/framework#60190
laravel/framework#60197
laravel/framework#60255
laravel/framework#60712
laravel/framework#60311
laravel/framework#60469
laravel/framework#55624
laravel/framework#57621
laravel/framework#59331

Related complete test and documentation reconciliation:
laravel/framework#60761
laravel/framework#60793
laravel/framework#61199
laravel/framework#60595
laravel/framework#61117
Porting source: 01d008c9b5f32cb7c5e50a9a22273113d810b2a2.

Validation: full composer fix, both complete scheduler suites after review
corrections, immediate test-file checks, facade regeneration and Telescope
frontend build. The repeat-start assertion fails when copy() is removed;
the maintenance-state assertion fails before correcting the elapsed-time
comparison. Formatting and diff checks pass.
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.

3 participants