-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[HttpKernel] Add a better error messages when passing a private or non-tagged controller #25201
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
[HttpKernel] Add a better error messages when passing a private or non-tagged controller #25201
Conversation
return parent::instantiateController($class); | ||
} catch (FatalThrowableError $e) { | ||
if ($this->container instanceof ContainerBuilder) { | ||
var_dump($this->container->getRemovedIds()); |
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.
Meh
oops i pushed it to fast since I was in the train will remove it
Le mer. 29 nov. 2017 à 08:31, Samuel ROZE <notifications@github.com> a
écrit :
… ***@***.**** commented on this pull request.
------------------------------
In
src/Symfony/Component/HttpKernel/Controller/ContainerControllerResolver.php
<#25201 (comment)>:
> @@ -82,10 +85,19 @@ protected function createController($controller)
*/
protected function instantiateController($class)
{
- if ($this->container->has($class)) {
- return $this->container->get($class);
+ try {
+ if ($this->container->has($class)) {
+ return $this->container->get($class);
+ }
+
+ return parent::instantiateController($class);
+ } catch (FatalThrowableError $e) {
+ if ($this->container instanceof ContainerBuilder) {
+ var_dump($this->container->getRemovedIds());
Meh
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#25201 (review)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ADSq8r-Ka3NRfSQN47vNDH_zF_4JpHFrks5s7QhtgaJpZM4Quh-N>
.
|
} | ||
|
||
return parent::instantiateController($class); | ||
} catch (FatalThrowableError $e) { |
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.
You need to be more specific than that. First of all: isn't it on the $this->container->get(...)
that you want to catch the exception? Also, can you use the exception specific to this error, not a Debug
exception?
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.
Actually, I'm not sure: this is Debug turning an E_RECOVERABLE_ERROR
into an exception.
There is no way to catch this error without the debug component.
And any other exception might be unrelated in fact.
So this looks specific to me: improves DX by throwing to the user when applicable.
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 problem I encountered into issue #25192 is that the Controller is not registered into the container, and the parent::instantiateController()
method is called, which instantiate the controller without any argument.
So the try catch should only be for the return parent::instantiateController($class);
.
Can we not use introspection in this method (instantiateController
) to check that the constructor does not require arguments, and throw an exception here if it does?
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.
So it is maybe something to change on the ControllerResolver
directly?
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 think I would go for that and throw an exception telling something similar to
"Your controller constructor requires argument but is neither declared as a public service nor tagged with controller.service_arguments
"
But having instrospection is heavy work so I don't know if it worth it...
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 parent doens't know anything about the container, so here is the correct place, isn't it?
catching only the parent call looks sensible to me also
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.
@nicolas-grekas I think the point here is that ControllerResolver::instantiateController()
cannot instantiate controllers requiring parameters in the constructor. So this method should check that the controller's constructor does not require any arguments and throw an exception if it does. This is not related to container.
If a controller is correctly configured as a public service or tagged with controller.service_arguments
, then the controller will not be instantiate, and pulled from the container so the ControllerResolver::instantiateController()
method will never be called, so no problem here...
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.
So this method should check that the controller's constructor does not require any arguments and throw an exception if it does.
This supposes that the check is free, which is not the case. Here, we're in a performance critical code path. There is no better way to do this check than the current one: try, and catch any error.
Status: Needs Work |
@@ -13,6 +13,9 @@ | |||
|
|||
use Psr\Container\ContainerInterface; | |||
use Psr\Log\LoggerInterface; | |||
use Symfony\Component\Debug\Exception\FatalThrowableError; | |||
use Symfony\Component\DependencyInjection\ContainerBuilder; |
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.
Why ContainerBuilder
, and not 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.
ill use Container
@@ -175,7 +196,7 @@ protected function createControllerResolver(LoggerInterface $logger = null, Cont | |||
|
|||
protected function createMockContainer() | |||
{ | |||
return $this->getMockBuilder(ContainerInterface::class)->getMock(); | |||
return $this->getMockBuilder(ContainerBuilder::class)->getMock(); |
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.
?
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.
Ill revert that.
a0c7d55
to
193a461
Compare
to be rebased on 3.4 btw |
193a461
to
1f14f4d
Compare
7ce94f3
to
35ab6dd
Compare
@@ -82,10 +84,25 @@ protected function createController($controller) | |||
*/ | |||
protected function instantiateController($class) | |||
{ | |||
if ($this->container->has($class)) { | |||
return $this->container->get($class); | |||
try { |
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.
Should be moved below, see comments
9ddc5c9
to
828d8c4
Compare
} catch (\ArgumentCountError $e) { | ||
} | ||
|
||
if (null !== $e) { |
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.
$e should be initialized, is it? This "if" should be merged with the next one also.
|
||
if (null !== $e) { | ||
if ($this->container instanceof Container) { | ||
if (in_array($class, $this->container->getRemovedIds())) { |
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.
A bit unrelated but maybe an opportunity to catch character case mismatches also here? Supposes that we can prevent the parent from failing hard in that case.
7cb768b
to
6345f3b
Compare
@@ -86,6 +94,18 @@ protected function instantiateController($class) | |||
return $this->container->get($class); | |||
} | |||
|
|||
return parent::instantiateController($class); | |||
$e = 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.
Could be removed and use isset($e)
in if below ?
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.
isn't better for performance to do this instead of isset ?
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.
Hmm, no idea. Actually I thought we already used isset($e)
for such cases in many places. But it seems I was wrong. So fine 👍
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.
Setting to null is the CS we use in the code base.
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.
oh, in fact, this can be removed, the variable is not needed: if we reach L99, $e will always be non null
which means the null !== $e check can be removed from the below if also
} catch (\ArgumentCountError $e) { | ||
} | ||
|
||
if (null !== $e && $this->container instanceof Container && in_array($class, $this->container->getRemovedIds())) { |
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.
Shouldn't this be in the Fwb ControllerResolver instead?
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.
@nicolas-grekas WDYT?
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.
this would limit the behavior to ppl using the fwb, I don't see any reason to do so
6345f3b
to
210016f
Compare
->will($this->returnValue(array(ImpossibleConstructController::class))) | ||
; | ||
|
||
if (method_exists($this, 'expectException')) { |
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.
Could we use an annotation instead?
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.
Ill try but in other test we use this instead of annot, it's maybe because of lower version of php using lower version of phpunit.
} | ||
|
||
if (null !== $e && $this->container instanceof Container && in_array($class, $this->container->getRemovedIds())) { | ||
throw new \InvalidArgumentException(sprintf('The controller %s seems to be removed from the container, which means that he maybe private, add the tag \'controller.service_arguments\' or made it public.', $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.
RuntimeException? The argument is valid, the config is not.
Also pass $e as previous exception to the constructor?
b0d2aba
to
1a872d1
Compare
} | ||
|
||
if (null !== $e && $this->container instanceof Container && in_array($class, $this->container->getRemovedIds())) { | ||
throw new \RuntimeException(sprintf('The controller %s seems to be removed from the container, which means that he maybe private, add the tag \'controller.service_arguments\' or made it public.', $class), 0, $e); |
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 propose this:
new \LogicException(sprintf('Controller "%s" cannot be fetched from the container because it is private. Did you forget to tag the service with "controller.service_arguments"?', $class), 0, $e);
} catch (\ArgumentCountError $e) { | ||
} | ||
|
||
if (null !== $e && $this->container instanceof Container && in_array($class, $this->container->getRemovedIds())) { |
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.
null !== $e &&
to be removed, see comment above
|
||
if (null !== $e && $this->container instanceof Container && in_array($class, $this->container->getRemovedIds())) { | ||
throw new \RuntimeException(sprintf('The controller %s seems to be removed from the container, which means that he maybe private, add the tag \'controller.service_arguments\' or made it public.', $class), 0, $e); | ||
} |
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.
else throw $e
2d29a78
to
83c0784
Compare
} | ||
|
||
if ($this->container instanceof Container && in_array($class, $this->container->getRemovedIds())) { | ||
throw new \LogicException(sprintf('Controller "%s" cannot be fetched from the container because it is private. Did you forget to tag the service with "controller.service_arguments"?', $class), 0, $e); |
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.
It looks to weird from a DX POV to recommend to add this tag instead of just recommending to make the service public.
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 crafted the message so that, from DX pov, the best experience is suggested by default (allowing services as arguments, ie the tag)
and from a more exprienced DX pov, the word "private" hints the advanced dev about "public" being just missing.
If one doesn't know the difference, then we should definitely suggest the tag. Hence the current message.
a784e86
to
92e67e6
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.
👍
f5709c5
to
5824d18
Compare
…n-tagged controller
5824d18
to
b1173f3
Compare
Thank you @Simperfit. |
…ivate or non-tagged controller (Simperfit) This PR was merged into the 3.4 branch. Discussion ---------- [HttpKernel] Add a better error messages when passing a private or non-tagged controller | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | no | New feature? | no <!-- don't forget to update src/**/CHANGELOG.md files --> | BC breaks? | no | Deprecations? | no <!-- don't forget to update UPGRADE-*.md files --> | Tests pass? | yes | Fixed tickets | #25192 <!-- #-prefixed issue number(s), if any --> | License | MIT | Doc PR | none - [x] Add more tests Commits ------- b1173f3 [HttpKernel] Add a better error messages when passing a private or non-tagged controller
…ler has been removed (sroze) This PR was merged into the 3.4 branch. Discussion ---------- [DX][HttpKernel] Throw a sensible exception when controller has been removed | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #25335 | License | MIT | Doc PR | ø Following on #25201, we need to throw the same kind of sensible exception when the controller service is not found. Commits ------- 458d63f Throw a sensible exception when controller has been removed
Uh oh!
There was an error while loading. Please reload this page.