-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[FrameworkBundle] Introduce autowirable ControllerTrait #18193
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
createForm as public; | ||
createFormBuilder as public; | ||
getDoctrine as public; | ||
isCsrfTokenValid as 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.
Would it be an idea (while at it), to split them?
- ControllerSessionTrait
- ControllerSecurityTrait
- ControllerResponseTrait
- ControllerFormTrait
- ControllerRouterTrait
- ControllerDoctrineTrait
- Missed one?
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.
As explained in the description, I think this is useless. They are proxy methods to the service itself. If you don't want the convenience of this "all-in-one" trait, just inject the service and use it.
* | ||
* @return Response A Response instance | ||
*/ | ||
protected function forward($controller, array $path = array(), array $query = array()) |
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 I'll need to rebase #17589, when this one gets merged.
Tests will be green when #18206 will be merged. |
@derrabus right, sorry about that I edit the description. |
public function testRedirect() | ||
{ | ||
$controller = new TestTrait(); | ||
$response = $controller->redirect('http://dunglas.fr', 301); |
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'd prefer neutral URLs in test cases, like http://example.com
for instance.
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 agree. We should make this a requirement. And we could normalize person names too. Although they are tests and not "real code", it would be great to be consistent.
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? It's just tests (never displayed to the end user), they are already a lot of things like that in the code base and it's fun to see some "Bernhard" or "Fabien" things in test. It "humanizes" the project, I prefer that to all those "Acme Inc" and "example.com" BS.
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.
https://twitter.com/search?q=vairelles%20symfony This what I mean by "humanize".
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.
example.com
and example.org
are reserved for documentation and examples: http://www.iana.org/domains/reserved They are a global standard. They are guaranteed to work forever and it will always be safe to use them. No other domain (not even symfony.com
itself) can guarantee that.
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 doesn't matter here. The URL is never accessed. Even if the domain doesn't exist the test will still be green.
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.
Even if it does not matter, using example.com
for better consistency would be good.
The trait introduces some logic to fetch the depedency from the container if it hasn't been set via a setter. I don't see this logic being covered by the tests. |
…s (dunglas) This PR was merged into the 2.3 branch. Discussion ---------- [FrameworkBundle][2.3] Add tests for the Controller class | Q | A | ------------- | --- | Branch? | 2.3 | Bug fix? | no | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | n/a | License | MIT | Doc PR | n/a Backport tests of #18193 to the `abstract` Controller. Commits ------- ca56be1 [FrameworkBundle] Add tests for the Controller class
…s (dunglas) This PR was merged into the 2.7 branch. Discussion ---------- [FrameworkBundle][2.7] Add tests for the Controller class | Q | A | ------------- | --- | Branch? | 2.7 | Bug fix? | no | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | n/a | License | MIT | Doc PR | n/a Backport tests of #18193 to the `abstract` Controller. Commits ------- 514a060 [FrameworkBundle] Add tests for the Controller class
…s (dunglas) This PR was squashed before being merged into the 2.8 branch (closes #18206). Discussion ---------- [FrameworkBundle][2.8] Add tests for the Controller class | Q | A | ------------- | --- | Branch? | 2.8 | Bug fix? | no | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | n/a | License | MIT | Doc PR | n/a Backport tests of #18193 to the `abstract` Controller. Commits ------- 5ee9f93 [FrameworkBundle][2.8] Add tests for the Controller class
ping @symfony/deciders |
👎 Unless I'm missing something, this would require (if you're autowiring your controller) all of these services to be eagerly instantiated in order to render your controller. Most are common services, but not all of them (form factory, serializer, etc). I understand why some people might find this useful. I would rather see an approach that: A) Completely leaves the B) Introduce smaller traits that have some of the shortcuts of the controller. And sure, you could wrap those up into one "big" trait for convenience. But even that, I would only include the most common services - like Cheers! |
The check for the container in A is basically for BC and ease the maintenance (it allows to use the trait in the abstract controller class). B is useless. This trait is just a set of proxy method. If you want only some dependencies, just inject services you use in the constructor. Closing for now. |
@dunglas Yea, I know that (A) was for BC - it makes sense, but it's just a little magic, with the trait looking for a property that's named About (B), it doesn't look useless to me. It brings 2 advantages: (A) RAD (using a trait is even less work than adding a constructor arg, though not a ton less work) and (B) familiarity/consistency: people can use the same shortcut methods - like |
For A, it allows us to maintain only one trait, and it works without autowiring with any class having a For B, IMO there are 2 kind of users:
For instance, take a look at the It does almost nothing except choosing between Twig and the old templating system. I don't get the point of importing this trait instead of injecting directly an instance of However I do get the interest of using this To summarize, with this all-in-one trait I need to be aware of its existence and I can create easily controllers with all common dependencies autowired. With separated traits, I must be aware of all the existing traits and this not different than being aware of all dependencies directly (that can be autowired too). But - I maybe missing something and I would have this merged before the feature freeze. @derrabus and you find a benefit to separated traits. What can we do to make you reconsider your 👎? Do you want I that I split this all-in-one trait in separate traits like in #16863? |
From a developer perspective I'm not really sure whether having multiple traits as in #16863 or only having one trait is preferable. Having multiple traits looks preferable to me if your controller/service/action class would only need one trait (for instance to follow ADR). In addition, the traits are great for testing because you can change method visibility. If developers start working with Symfony, the abstract controller probably is one of the first things they will see while navigating through the source code. So in my opinion it should reflect the best practice, is there a specific one related to using traits? 👍 |
The Using the
I still think that autowiring the By eagerly injecting all possible dependencies, you will inevitably inject services that won't be used by the controller.
Until now, performance was not an issue with the So now this PR does an interesting trick. The trait is theoretically autowirable, but this autowiring is not actually done yet because the Now talking about splitting the trait by topic/dependency. Yes, you're right, those traits do almost nothing. And this is exactly the point. Traits should imho only implement glue code that I would otherwise copy&paste. As soon as it contains real logic, class composition should be preferred over a trait. So what's the benefit of such a small trait then? First of all, reusability for other classes than controllers:
Getting back to controllers, setter autowiring would again start make sense to me on the small traits. I would only |
Looks like you missed http://symfony.com/blog/new-in-symfony-3-3-getter-injection :) |
@nicolas-grekas I did; thanks! 👍 |
e5fbe6d
to
01b183a
Compare
01b183a
to
dcc7e09
Compare
*/ | ||
protected function getRouter(): RouterInterface | ||
{ | ||
throw new \LogicException(sprintf('An instance of "%s" must be provided.', RouterInterface::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 would remove the exceptions here as PHP would throw an error anyway thanks to the type hint.
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.
Done.
I've also removed the conditional behavior of the json()
method to make the Serializer mandatory:
- it's a cleaner design (the behavior is predictable)
- it allows to not
catch
aTypeError
- having a proxy method doing
return new JsonResponse($data, $status, $headers)
is useless
@dunglas Can you also add a note in the CHANGELOG (specifying that this new feature requires PHP 7, and perhaps mentioning the few differences with the Controller base class)? Thanks. |
done |
👍 |
4439218
to
2c69e1e
Compare
2c69e1e
to
8972503
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.
👍
Thank you @dunglas. |
…ing ControllerTrait (nicolas-grekas) This PR was merged into the 3.3-dev branch. Discussion ---------- [FrameworkBundle] Introduce AbstractController, replacing ControllerTrait | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no (master only) | Deprecations? | yes | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - Basically reverts and replaces #18193. Instead of using getter injection to provide our controller helpers, let's leverage the new `ServiceSubscriberInterface` (see #21708). This is what the proposed `AbstractController` class provides. So, instead of extending `Controller`, this would encourage extending `AbstractController`. This provides almost the same experience, but makes the container private, thus not usable by userland (this safeguard was already provided by `ControllerTrait`). I did not deprecate `Controller`, but I think we should. Now that we also have "controller.service_arguments" (see #21771), we have everything in place to encourage *not* using the container in controllers directly anymore. My target in doing so is removing getter injection, which won't have any use case in core anymore. The wiring for this could be: ```yaml services: _instanceof: Symfony\Bundle\FrameworkBundle\Controller\AbstractController: calls: [ [ setContainer, [ '@container' ] ] ] tags: [ container.service_subscriber ] ```` But this is optional, because we don't really need to inject a scoped service locator: injecting the real container is fine also, since everything is private. And this is done automatically on ControllerResolver. Commits ------- a93f059 [FrameworkBundle] Introduce AbstractController, replacing ControllerTrait
see #22157 as this was more or less reverted by that PR. |
…hemN) This PR was squashed before being merged into the master branch (closes #7657). Discussion ---------- [FrameworkBundle] Document the AbstractController Document symfony/symfony#18193. I'm not sure it should be merged right now though as it's an experimental feature. \cc @dunglas Commits ------- 4ac5da7 Fix 88a4806 Fix cf2ae91 [FrameworkBundle] Document the AbstractController
This is the missing part of the new controller system and it's fully BC with the old one.
Used together with the existing autowiring system, #17608 and DunglasActionBundle it permits to inject explicit dependencies in controllers with 0 line of config. It's a great DX improvement for Symfony.
It also has a lot of another advantages including enabling to reuse controller accros frameworks and make them easier to test. See https://dunglas.fr/2016/01/dunglasactionbundle-symfony-controllers-redesigned/ for all arguments.
Magic methods of old controllers are still available if you use this new trait in actions.
For instance, the
BlogController::newAction
form thesymfony-demo
can now looks like:As you can see, there is no need to register the
slugger
service inservices.yml
anymore and the dependency is explicitly injected. In fact the container is not injected in controllers anymore.Convenience methods still work if the
ControllerTrait
is used (of course it's not mandatory). Here I've made the choice to use an invokable class but this is 100% optional, a class can still contain several actions if wanted.Annotations like
@Route
still work too. The oldabstract
controller isn't deprecated. There is no valid reason to deprecate it IMO. People liking using the "old" way still can.Unless in #16863, there is only one trait. This trait/class is basically a bunch of proxy methods to help newcomers. If you want to use only some methods, or want explicit dependencies (better), just inject the service you need in the constructor and don't use the trait.
I'll create open a PR on the standard edition soon to include ActionBundle and provide a dev version of the standard edition to be able to play with this new system.
I'll also backport tests added to the old controller test in 2.3+.
Edit: It now uses getter injection to benefit from lazy service loading by default.