Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork9.7k
[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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
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.
dunglas commentedMar 17, 2016
Tests will be green when#18206 will be merged. |
derrabus commentedMar 17, 2016
dunglas commentedMar 17, 2016
@derrabus right, sorry about that I edit the description. |
derrabus commentedMar 17, 2016
| 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, likehttp://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 andexample.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 evensymfony.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, usingexample.com for better consistency would be good.
derrabus commentedMar 17, 2016
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/aBackport 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/aBackport 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/aBackport tests of#18193 to the `abstract` Controller.Commits-------5ee9f93 [FrameworkBundle][2.8] Add tests for the Controller class
dunglas commentedMar 27, 2016
ping @symfony/deciders |
weaverryan commentedMar 29, 2016
👎 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 themost common services - like Cheers! |
dunglas commentedMar 29, 2016
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. |
weaverryan commentedMar 29, 2016
@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 aton less work) and (B) familiarity/consistency: people can use the same shortcut methods - like |
dunglas commentedMar 30, 2016
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? |
rvanlaak commentedMar 30, 2016
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 followADR). In addition, the traits are great for testing because you canchange 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? 👍 |
derrabus commentedMar 30, 2016
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 |
nicolas-grekas commentedFeb 27, 2017
Looks like you missedhttp://symfony.com/blog/new-in-symfony-3-3-getter-injection :) |
robfrawley commentedFeb 27, 2017
@nicolas-grekas I did; thanks! 👍 |
e5fbe6d to01b183aCompare01b183a todcc7e09Compare| */ | ||
| 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 thejson() method to make the Serializer mandatory:
- it's a cleaner design (the behavior is predictable)
- it allows to not
catchaTypeError - having a proxy method doing
return new JsonResponse($data, $status, $headers)is useless
fabpot commentedMar 2, 2017
@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. |
dunglas commentedMar 2, 2017
done |
fabpot commentedMar 2, 2017
👍 |
4439218 to2c69e1eCompare2c69e1e to8972503Compare
nicolas-grekas left a comment
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.
👍
fabpot commentedMar 2, 2017
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:```yamlservices: _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
fabpot commentedMar 25, 2017
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 AbstractControllerDocumentsymfony/symfony#18193.I'm not sure it should be merged right now though as it's an experimental feature.\cc@dunglasCommits-------4ac5da7 Fix88a4806 Fixcf2ae91 [FrameworkBundle] Document the AbstractController
Uh oh!
There was an error while loading.Please reload this page.
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 andDunglasActionBundle 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. Seehttps://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::newActionform thesymfony-democan now looks like:As you can see, there is no need to register the
sluggerservice inservices.ymlanymore and the dependency is explicitly injected. In fact the container is not injected in controllers anymore.Convenience methods still work if the
ControllerTraitis 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
@Routestill work too. The oldabstractcontroller 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.