8000 [DX] Improve exception message when AbstractController::getParameter fails by curry684 · Pull Request #27443 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[DX] Improve exception message when AbstractController::getParameter fails #27443

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

Closed
wants to merge 6 commits into from

Conversation

curry684
Copy link
Contributor
Q A
Branch? 4.1
Bug fix? no (DX)
New feature? no
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets #27436
License MIT
Doc PR symfony/symfony-docs#...

Improve exception message for situations where the parameter_bag is not present in AbstractController. Also fixed the exception to the correct type.

@curry684 curry684 changed the title Improve exception message when AbstractController::getParameter fails [DX] Improve exception message when AbstractController::getParameter fails May 31, 2018
@nicolas-grekas nicolas-grekas added this to the 4.1 milestone May 31, 2018
@@ -64,7 +65,9 @@ public function setContainer(ContainerInterface $container)
protected function getParameter(string $name)
{
if (!$this->container->has('parameter_bag')) {
throw new \LogicException('The "parameter_bag" service is not available. Try running "composer require dependency-injection:^4.1"');
throw new ServiceNotFoundException('parameter_bag', null, null, array(),
'The "parameter_bag" service could not be located. Ensure that symfony/dependency-injection 4.1.0 or higher '.
Copy link
Member
@nicolas-grekas nicolas-grekas May 31, 2018

Choose a reason for hiding this comment

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

it would be great to decide ourselves instead of this "and" (line below) in the message (a class_exists check and we can know for sure which alternative is the correct one)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Any ideas on a class to reliably test that was introduced in 4.1 and will not disappear until "infinity", so not internal?

Copy link
Member

Choose a reason for hiding this comment

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

ContainerBagInterface :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

throw new \LogicException('The "parameter_bag" service is not available. Try running "composer require dependency-injection:^4.1"');
$message = 'The "parameter_bag" service could not be located. ';
if (!interface_exists(ContainerBagInterface::class)) {
$message .= 'Upgrade symfony/dependency-injection to at least 4.1.0 to have it injected automatically.';
Copy link
Member

Choose a reason for hiding this comment

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

suggesting to install/upgrade symfony/dependency-injection does not seem relevant as framework-bundle requires it ^4.1

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Lol, hadn't bothered to doublecheck that because it was the original message and I assumed it to have a reason.

throw new \LogicException('The "parameter_bag" service is not available. Try running "composer require dependency-injection:^4.1"');
throw new ServiceNotFoundException('parameter_bag', null, null, array(),
'The "parameter_bag" service could not be located. Ensure that the controller is either set '.
'to be autoconfigured or wired manually to inject it.');
Copy link
Member

Choose a reason for hiding this comment

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

[...] to be autoconfigured or has the "container.service_subscriber" tag.?

should be on one single long line

Copy link
Member
@nicolas-grekas nicolas-grekas Jun 1, 2018

Choose a reason for hiding this comment

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

sprintf('The "%s::getParameter()" method is missing a parameter bag to work properly. Did you forget to register your controller as a service subscriber? This can be fixed e.g. by using autoconfiguration or by manually wiring a "parameter_bag" in the service locator passed to the controller.', get_class($this))

(the name of the parameter is of no importance IMHO, but the controller class IS.)

@javiereguiluz
Copy link
Member
javiereguiluz commented May 31, 2018

Would you agree making this error message more verbose to give more context?

Original:

The "parameter_bag" service could not be located. Ensure that the controller is
either set to be autoconfigured or wired manually to inject it.

Proposal:

sprintf('The "getParameter()" method of the controller can\'t get the value of
the "%s" parameter because the "parameter_bag" service could not be located.
Either enable autowiring for the controller or inject the "parameter_bag"
service manually in the controller.', $name);

My concern with the current message is that the user wrote $this->getParameter('foo') and the error message is about something strange called parameter_bag that the user didn't use anywhere in their code.

@weaverryan
Copy link
Member

You’re right - I missed that. I like your message more :)

@curry684
Copy link
Contributor Author
curry684 commented May 31, 2018

@javiereguiluz that suggestion is wrong on 2 counts: first off autowiring won't fix it, it needs autoconfigure. Secondly injecting parameter_bag into the controller manually won't work as it has to be in whatever is in $this->container, effectively meaning you need to have a ServiceLocator or make the parameter_bag service public, both of which are unfeasible for end-user developers not at home in DI innards.

In practice autoconfigure is the only viable solution to the problem. Realizing that makes you second guess whether this generic feature was a good idea to begin with even though autoconfigure+autowire is now recommended config - it's still not required.

edit: out of the box thinking - shouldn't we just rock hard fail somewhere at compile-time if an AbstractController is used but doesn't get its ServiceLocator injected in any way? I suspect the missing parameter_bag is just the first and smallest of issues one would run into (most subscribed services in its list are private).

@nicolas-grekas
Copy link
Member

autoconfigure+autowire is now recommended config - it's still not required.

that's still the case, autoconfiguration+autowiring is not the only way to define a service locator for a service subscriber. Using the container.service_subscriber tag + its attributes are the really low level way to handle this configuration.

Of course in practice, esp. for AbstractController, it's still the most common way to configure them, so that the message is OK mentioning these imho.

shouldn't we just rock hard fail somewhere at compile-time if an AbstractController is used but doesn't get its ServiceLocator injected in any way

what would be the criteria? from the inside of the class, all service locators look the same :)
and at compile time, it's just a service subscriber. So yes, we could remove a few "?" in front of types returned by getSubscribedServices, but that's not possible for optional deps, and all are here. For AbstractControllers, because of the special way they are handled, I'm not sure we can do anything, do we?

@curry684
Copy link
Contributor Author

the really low level way to handle this configuration

Which makes it "unfeasible for end-user developers" like I said and only realistic for those that do not really require better DX in error messages like that in the first place.

For AbstractControllers, because of the special way they are handled, I'm not sure we can do anything, do we?

There's multiple things we could do actually. Reviewing the default subscriptions quickly we have 5 that could (should?) lose the optional as they're direct dependencies of FrameworkBundle:

yes 'router' => '?'.RouterInterface::class,
yes 'request_stack' => '?'.RequestStack::class,
yes 'http_kernel' => '?'.HttpKernelInterface::class,
no  'serializer' => '?'.SerializerInterface::class,
yes 'session' => '?'.SessionInterface::class,
no  'security.authorization_checker' => '?'.AuthorizationCheckerInterface::class,
no  'templating' => '?'.EngineInterface::class,
no  'twig' => '?'.Environment::class,
no  'doctrine' => '?'.ManagerRegistry::class,
no  'form.factory' => '?'.FormFactoryInterface::class,
no  'security.token_storage' => '?'.TokenStorageInterface::class,
no  'security.csrf.token_manager' => '?'.CsrfTokenManagerInterface::class,
yes 'parameter_bag' => '?'.ContainerBagInterface::class,
no  'message_bus' => '?'.MessageBusInterface::class,

This automatically makes them candidates for a late-phase compiler pass to check integrity - any service that derives from AbstractController can have its getSubscribedServices queried, and we can then fail hard at compile-time if any mandatory ones (including user added ones) cannot resolve.

It's the difference between getting "You've screwed up on how to use AbstractController for <10 reasons>" during compiletime for all of them at once, or 10 separate vague errors about missing parameter bags, request stacks and HTTP kernels at runtime.

(actually the compiler pass could generically check all ServiceSubscriberInterface implementers for this as at that point we know whether mandatory references can ever be resolved on this container, and tell the user to add the ? in front if it's intentional that we can't find it... but that could potentially be considered a BC break)

@curry684
Copy link
Contributor Author

Meanwhile, how about this for the issue at hand, mainly adapting Javier's suggestion to implementation:

sprintf('The "getParameter()" method of the controller can\'t get the value of
the "%s" parameter because the "parameter_bag" service could not be located.
Either enable autoconfigure for the controller service or inject a ServiceLocator
referencing a "parameter_bag" service manually in the controller.', $name);

@nicolas-grekas
Copy link
Member

5 that could (should?) lose the optional

Could, but what would we gain doing so? Nothing related to the issue at glance. To which one then? This means "shouldn't" to me :)

@curry684
Copy link
Contributor Author
curry684 commented May 31, 2018

It's related to the second question: should having a ServiceLocator with a mandatory but undefined reference crash unexpectedly during development or right away at compiletime right after wrongly defining it?

I think for DX it'd be better to fail early, and it'd fix the entire issue at hand ages before we ever got to the getParameter function. TS would've been happy to get a "Controllers deriving from AbstractController must contain a ServiceLocator with valid references to 'router', 'request_stack', 'http_kernel', 'session' and 'parameter_bag' services. Enable autoconfigure for the controller service to handle this automatically" 30 seconds after upgrading to Symfony 4.1.

It'd even microscopically improve performance as it would mean getParameter can just be reduced to $this->container->get('parameter_bag')->get($parameter) without needing the runtime check for existence on every call first.

@nicolas-grekas
Copy link
Member

But this wouldn't fail at compile time. Or based on what criteria would it fail? What would be the trigger to throw this when the issue is precisely that this controller was not registered for the compiler pass that looks for building locators for subscribers?

@curry684
Copy link
Contributor Author

If we can autoconfigure services that implement ServiceSubscriberInterface we can also enumerate them and check that we can resolve what they subscribe to? AbstractController implements it so it would automatically be subject to that check.

@nicolas-grekas
Copy link
Member

Here is a proposal to fix the DX issue: #27462

@curry684
Copy link
Contributor Author
curry684 commented Jun 1, 2018

Good idea, but let's also fix the error message now as it's currently just wrong in 4.1. Just pushed my last proposal merging Javier's suggestion with mine.

@@ -64,7 +65,8 @@ public function setContainer(ContainerInterface $container)
protected function getParameter(string $name)
{
if (!$this->container->has('parameter_bag')) {
throw new \LogicException('The "parameter_bag" service is not available. Try running "composer require dependency-injection:^4.1"');
throw new ServiceNotFoundException('parameter_bag', null, null, array(),
sprintf('The "getParameter()" method of the controller can\'t get the value of the "%s" parameter because the "parameter_bag" service could not be located. Either enable autoconfigure for the controller service or inject a ServiceLocator referencing a "parameter_bag" service manually in the controller.', $name));
Copy link
Member

Choose a reason for hiding this comment

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

You missed my last comment here it is :) Also this should really be on one line, not two.

sprintf('The "%s::getParameter()" method is missing a parameter bag to work properly. Did you forget to register your controller as a service subscriber? This can be fixed e.g. by using autoconfiguration or by manually wiring a "parameter_bag" in the service locator passed to the controller.', get_class($this))

(the name of the parameter is of no importance IMHO, but the controller class IS.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thought you meant only the string with the previous one-line remark. Is the Symfony code style on layout stuff like that documented somewhere? It's confusing that most projects (including our internal ones) explicitly prohibit lines beyond common-screen limits (ie. ~120 characters) and Symfony 'prefers oneliners'.

Copy link
Member
@xabbuh xabbuh left a comment

Choose a reason for hiding this comment

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

with Nicolas' comment applied

Copy link
Member
@nicolas-grekas nicolas-grekas left a comment

Choose a reason for hiding this comment

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

Long line for user message allow using grep to find where that exception/deprecation comes from. That's and important DX feature for ppl that use this workflow when debugging.

@curry684
Copy link
Contributor Author
curry684 commented Jun 3, 2018

Ah ok, understandable from that perspective.

@chalasr
Copy link
Member
chalasr commented Jun 4, 2018

Thank you @curry684.

chalasr pushed a commit that referenced this pull request Jun 4, 2018
…getParameter fails (curry684)

This PR was squashed before being merged into the 4.1 branch (closes #27443).

Discussion
----------

[DX] Improve exception message when AbstractController::getParameter fails

| Q             | A
| ------------- | ---
| Branch?       | 4.1
| Bug fix?      | no (DX)
| New feature?  | no
| BC breaks?    | no     <!-- see https://symfony.com/bc -->
| Deprecations? | no
| Tests pass?   | yes    <!-- please add some, will be required by reviewers -->
| Fixed tickets | #27436
| License       | MIT
| Doc PR        | symfony/symfony-docs#... <!-- required for new features -->

Improve exception message for situations where the `parameter_bag` is not present in `AbstractController`. Also fixed the exception to the correct type.

Commits
-------

a8f4128 [DX] Improve exception message when AbstractController::getParameter fails
@chalasr chalasr closed this Jun 4, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants
0