[12.x] fix: container resolution order when resolving class dependencies #53522
+41
−10
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Hello!
This was deemed too risky to merge to 11.x in #53519, so trying for 12.x.
Changes
Currently, when resolving a class with with dependencies, the container only falls back to the parameter default value if the class dependency failed to resolve. However, this is not intuitive---if I specify a default value for a parameter then I would expect that default value to be used (if I have not explicitly bound that parameter class to the container). This updates the container resolution logic to use the default value for a dependency (when available) before trying to resolve a class instance. The container class dependency logic flow now looks like:
For example, consider resolving the following class from the container:
Motivation
I would consider this particular container nuance to be a bug and this has bitten us several times (usually when dealing with Model or Carbon instances) resulting in lengthy debugging efforts for seemingly obscure problems.
Most recently, this reared its head when using a scheduled, dispatched job. This particular job is designed to be executed early in the morning and it aggregates results for the previous day. We do allow a date to be passed into the constructor so the same job can be manually dispatched for a particular date if necessary---it is considered an override when anything other than
null
is passed in.However, this job fails when executed via the scheduler every morning because the Scheduler uses the container to make the job class:
framework/src/Illuminate/Console/Scheduling/Schedule.php
Lines 165 to 166 in a296e81
and the container ignores the default value and instead constructs the job with a Carbon instance = now---the logic to handle the default case is never executed. This works just fine as intended when dispatching the job with
ScheduledJob::dispatch()
(because of which, this was a tricky bug to troubleshoot). I would expect the job to have the same behaviour regardless if it was dispatched manually via::dispatch()
or automatically via the scheduler.I know I can use
to schedule the job with a class instance, however:
new
ing them all up would incur unnecessary overhead on every console invocationschedule:work
because the constructor would be invoked when the app is booted and not right before the job is dispatched to the queue---resulting in a stale$date
propertyNote
I recognize that there are many ways to work around this current behaviour, but the crux of the issue is that the resolution logic is not intuitive and ignores the developer provided default. This issue has continued to pop up in various locations in the app when using the container to resolve classes with dependencies that have default values.
Thanks!