-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[DI] Add context to service-not-found exceptions thrown by service locators #25381
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
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Brilliant!
3afa88b
to
4c056d9
Compare
@@ -31,6 +36,15 @@ public function __construct(array $factories) | |||
$this->factories = $factories; | |||
} | |||
|
|||
public function withContext($externalId, Container $container) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@internal
?
@@ -94,6 +97,15 @@ public static function register(ContainerBuilder $container, array $refMap) | |||
$container->setDefinition($id, $locator); | |||
} | |||
|
|||
if (null !== $callerId = 2 < func_num_args() ? func_get_arg(2) : null) { | |||
$locatorId = $id; | |||
$container->register($id .= '.'.$callerId, ServiceLocator::class) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know you hate it when I suggest this, but a comment above this explaining what's going on would help. I can't locate where and why registering this service is significant.
{ | ||
if ($this->loading) { | ||
$msg = sprintf('The service "%s" has a dependency on a non-existent service "%s".', end($this->loading), $id); | ||
$msg .= sprintf(' This locator only knows about "%s"', implode('", "', array_keys($this->factories))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does this situation happen? Once I use $this->container->get('foo')
, if foo
depends on a non-existent bar
service, wouldn't the container have already exploded at build time?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see new test case :)
} else { | ||
$class = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 3); | ||
$class = isset($class['object'][2]) ? get_class($class['object'][2]) : null; | ||
$externalId = $this->externalId ?: $class; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When would $externalId
be set versus not set? It obviously makes a difference in the error message below, so just curious.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Eg the locator is used from a function.
But in practice, should not happen to users.
$msg .= 'even though it exists in the app\'s container, '; | ||
} | ||
if ($externalId) { | ||
$msg .= sprintf('"%s" only knows about pre-listed services "%s".', $externalId, implode('", "', array_keys($this->factories))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe:
The container inside "%s" is a smaller service locator that only contains the following services: "%s".
} | ||
|
||
if ($class && is_subclass_of($class, ServiceSubscriberInterface::class)) { | ||
$msg .= sprintf(' Did you forget to declare the depencency using "%s::getSubscribedServices()"?', preg_replace('/([^\\\\]++\\\\)++/', '', $class)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not to go too crazy, but if the actual class that implements ServiceSubscriberInterface
is a vendor, then this error message doesn't make much sense. The screenshot of the error in the PR is a good example of this - the correct solution for a controller is probably not to override getSubscribedServices()
.
For the controller situations specifically (which probably would work for any case where getSubscribedServices()
is actually implemented by a vendor class), the better message might be:
Instead of using the container, try using dependency injection to get the service.
We could go even more bonkers and do something (for AbstractController
) about type-hinting something on the controller... but probably that's overboard. We just need to say "Hey! Stop using the container! Do what you see recommended everywhere in the docs instead".
*/ | ||
public static function register(ContainerBuilder $container, array $refMap) | ||
public static function register(ContainerBuilder $container, array $refMap/*, string $callerId = null*/) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the class is final (not even @final
but actually final), so you can add the new argument without this magic
} | ||
|
||
if ($class && is_subclass_of($class, ServiceSubscriberInterface::class)) { | ||
$msg .= sprintf(' Did you forget to declare the depencency using "%s::getSubscribedServices()"?', preg_replace('/([^\\\\]++\\\\)++/', '', $class)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo here depencency
vs dependency
@@ -150,7 +150,7 @@ private function handleRaw(Request $request, $type = self::MASTER_REQUEST) | |||
$arguments = $event->getArguments(); | |||
|
|||
// call controller | |||
$response = call_user_func_array($controller, $arguments); | |||
$response = \call_user_func_array($controller, $arguments); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest submitting this to 2.7 instead. It is unrelated to this PR.
@stof @weaverryan thanks for the review, comments addressed. |
6d2f355
to
d34e760
Compare
PR green and ready. Status: needs review |
28e1964
to
60cd0f6
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
next level error messages :) i like it.
return sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $this->formatAlternatives()); | ||
} | ||
|
||
$class = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 3); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
with DEBUG_BACKTRACE_IGNORE_ARGS
or is it negligible?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good idea, added
60cd0f6
to
27d8d69
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice!
if ($this->loading) { | ||
return sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $this->formatAlternatives()); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extra spaces
27d8d69
to
9512f26
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 for the messaging as shown on the test cases. The technical details are a bit complex, but this only touches the messaging and is well-covered with test cases.
Thank you @nicolas-grekas. |
… service locators (nicolas-grekas) This PR was merged into the 3.4 branch. Discussion ---------- [DI] Add context to service-not-found exceptions thrown by service locators | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | yes (DX) | New feature? | yes (...) | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #25342, #25196 | License | MIT | Doc PR | - Here hopefully is the fully-context aware message you're looking for @weaverryan @curry684.  Commits ------- 9512f26 [DI] Add context to service-not-found exceptions thrown by service locators
Here hopefully is the fully-context aware message you're looking for @weaverryan @curry684.