Skip to content

Deprecate bundle:controller:action and service:method notation #26085

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

Conversation

Tobion
Copy link
Contributor

@Tobion Tobion commented Feb 8, 2018

Q A
Branch? master
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? yes
Tests pass? yes
Fixed tickets #25910
License MIT
Doc PR

The a::b notation had some awkward limitations. It supported MyControllerClass::method where MyControllerClass is either plain class or a service with the same name but the class must exists. This meant it did NOT support my_service_controller_id::method because the class_exists check would fail at the wrong point in time. But it did support services where class name == id, i.e. the new auto registration based psr naming. This made it very confusing.

I enhanced the a::b notation to be very straight forward:

  • if a exists as a service then use a as a service
  • otherwise try to use a as a class, i.e. new $a()
  • otherwise check if a::b is a static method (only relevant when the class is abstract or has private contructor). this was potentially supported when using array controller syntax. it now works the same when using the :: string syntax, like in php itself. since it only happens when nothing else works, it does not have any performance impact.

The old a:b syntax is deprecated and just forwards to a::b now internally, just as bundle:controller:action.
In general I was able to refactor the logic quite a bit because it always goes through instantiateController now.
Spotting deprecated usages is very easy as all outdated routing configs will trigger a deprecation with the DelegatingLoader and it will be normalized in the dumped routes. So you don't get a deprecation again in the ControllerResolver. But if the controller does not come from routing, e.g. twigs render controller function, then it will still be triggered there.

  • deprecate a:b:c
  • deprecate a:b
  • update existing references to a::b
  • fix tests
  • fix/add support for static controllers
  • add support for closures as controllers
  • update Symfony\Component\Routing\Loader\ObjectRouteLoader
  • deprecate \Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser but we still need to use it in several places for BC.
  • add changelog/upgrade
  • update controller.service_arguments logic for double colon controller syntax

@Tobion Tobion changed the title WIP: Deprecate old controller notations WIP: Deprecate bundle:controller:action and service:method notation Feb 8, 2018

if (1 === substr_count($controller, ':') && is_array($resolvedController)) {
$resolvedController[0] = $this->configureController($resolvedController[0]);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This did not configure the controller when it's invokable. It's fixed now since it's also going through instantiateController now like all the other cases.

$controller = parent::getController($request);

if (is_array($controller) && isset($controller[0]) && is_string($controller[0]) && $this->container->has($controller[0])) {
$controller[0] = $this->instantiateController($controller[0]);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This does not belong here. I moved it to the base controller resolver. This is much more consistent and also makes array controller work where the class needs to be instantiated with old-school new $class.

}

if (!is_callable($controller)) {
throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable. %s', $request->getPathInfo(), $this->getControllerError($controller)));
Copy link
Contributor Author

@Tobion Tobion Feb 8, 2018

Choose a reason for hiding this comment

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

this array controller case was missing validation. otherwise it fails in the kernel with a type error at

public function __construct(HttpKernelInterface $kernel, callable $controller, Request $request, ?int $requestType)

@Tobion Tobion force-pushed the deprecate-old-controller-notations branch 3 times, most recently from 4e2d6ef to f1d6484 Compare February 8, 2018 19:47
}

return $resolvedController;
return parent::createController($controller);
Copy link
Member

Choose a reason for hiding this comment

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

The whole class can be deprecated after removing the deprecated code in 5.1. Which means removing this class in 6.0. What about deprecating the class right now. Upgrading could then be done by 1/ removing all old notation usage 2/ switching to the Component class equivalent. WDYT?

Copy link

Choose a reason for hiding this comment

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

You mean 4.1 and 5.0 right :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@fabpot we still need to class for the ContainerAwareInterface logic, see configureController. So we can't deprecate it now. We might want to remove ContainerAware logic in the future as well, but that's a different issue.

Copy link
Member

Choose a reason for hiding this comment

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

Indeed, I missed that. It could be deprecated at some point, but definitely not part of this work.

Copy link
Member

Choose a reason for hiding this comment

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

Can't we move the ContainerAwareInterface logic to the ContainerControllerResolver of the component ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think it's worth it because there is also logic for the AbstractController which is part of the framework bundle. So it fits in the framework bundle.

@@ -47,23 +43,27 @@ public function getController(Request $request)
}

if (is_array($controller)) {
if (isset($controller[0]) && is_string($controller[0])) {
$controller[0] = $this->instantiateController($controller[0]);
Copy link
Member

Choose a reason for hiding this comment

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

wouldn't this break support for using static methods as controller ?

Copy link
Contributor Author

@Tobion Tobion Feb 9, 2018

Choose a reason for hiding this comment

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

Yeah I also thought about this. We don't have tests using static methods and class::method already had the problem of not supporting static methods while that would be a valid php callable.
But I already have a solution for this in my mind that will support both cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

implemented

}

return $resolvedController;
return parent::createController($controller);
Copy link
Member

Choose a reason for hiding this comment

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

Can't we move the ContainerAwareInterface logic to the ContainerControllerResolver of the component ?

@Tobion Tobion force-pushed the deprecate-old-controller-notations branch 2 times, most recently from 252cbcc to 0632c6b Compare February 11, 2018 00:21
@Tobion Tobion changed the title WIP: Deprecate bundle:controller:action and service:method notation Deprecate bundle:controller:action and service:method notation Feb 11, 2018
@Tobion
Copy link
Contributor Author

Tobion commented Feb 11, 2018

Tests should pass once merged.

@nicolas-grekas nicolas-grekas added this to the 4.1 milestone Feb 11, 2018
@Tobion Tobion force-pushed the deprecate-old-controller-notations branch 2 times, most recently from a7d6d0a to 23c999d Compare February 11, 2018 22:34
}
}

if (!is_callable($controller)) {
Copy link
Member

Choose a reason for hiding this comment

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

What about using http://php.net/manual/en/closure.fromcallable.php to ensure it's callable?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't know what you intend. In which way does Closure::fromCallable help here?

Copy link
Member

Choose a reason for hiding this comment

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

The Closure::fromCallable method calls is_callable for you.
Then, It "standardize" how we (will) manage callable

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Using a closure here provides no benefit and the error it would return for wrong callables is worse than what getControllerError does. For example it does not hint that an __invoke method might be missing. https://3v4l.org/fJJNs

Copy link
Contributor

@ro0NL ro0NL left a comment

Choose a reason for hiding this comment

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

just some random comments :) 👍 another great step forward.

@@ -41,6 +43,8 @@ public function __construct(KernelInterface $kernel)
*/
public function parse($controller)
{
@trigger_error(sprintf('The %s class is deprecated since version 4.1 and will be removed in 5.0.', __CLASS__), E_USER_DEPRECATED);
Copy link
Contributor

Choose a reason for hiding this comment

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

why not put the trigger on top of the file?

Copy link
Contributor Author

@Tobion Tobion Feb 12, 2018

Choose a reason for hiding this comment

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

because we still use the class and service internally. but we only want to trigger when it gets executed

@@ -19,6 +19,8 @@
* (Bundle\BlogBundle\Controller\PostController::indexAction).
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated since version 4.1, will be removed in 5.0.
Copy link
Contributor

Choose a reason for hiding this comment

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

, to be removed in Symfony 5.0

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I normalized all deprecation messages

@@ -86,6 +90,8 @@ public function parse($controller)
*/
public function build($controller)
{
@trigger_error(sprintf('The %s class is deprecated since version 4.1 and will be removed in 5.0.', __CLASS__), E_USER_DEPRECATED);
Copy link
Contributor

Choose a reason for hiding this comment

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

"%s" quoted + Symfony 5.0

@@ -17,8 +17,6 @@
* A ControllerResolverInterface implementation knows how to determine the
* controller to execute based on a Request object.
*
* It can also determine the arguments to pass to the Controller.
Copy link
Contributor

Choose a reason for hiding this comment

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

to be applied on 4.0?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I guess also for 3.4 but no priority for me

Copy link
Contributor

@HeahDude HeahDude left a comment

Choose a reason for hiding this comment

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

Great!

}
}

if (1 === substr_count($controller, ':')) {
Copy link
Contributor

Choose a reason for hiding this comment

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

You could use an elseif here if the condition is mutually exclusive with the one above. It would prevent evaluating this one when the first is true.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Depends if someone used an extended name parser that returned something else. I would leave it like this for simplicitly.

@@ -47,8 +47,7 @@ public function process(ContainerBuilder $container)
} else {
// any methods listed for call-at-instantiation cannot be actions
$reason = false;
$action = substr(strrchr($controller, ':'), 1);
$id = substr($controller, 0, -1 - strlen($action));
list($id, $action) = explode('::', $controller);
Copy link
Contributor

Choose a reason for hiding this comment

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

What about using [$id, $action] here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could but I don't think we use that syntax in the code base yet.

@Tobion Tobion force-pushed the deprecate-old-controller-notations branch from b3fe6f0 to 748f725 Compare February 12, 2018 20:36
@Tobion
Copy link
Contributor Author

Tobion commented Feb 20, 2018

Can we merge this? I'd prefer to not having to rebase this again.

@fabpot
Copy link
Member

fabpot commented Feb 21, 2018

Thank you @Tobion.

@fabpot fabpot closed this Feb 21, 2018
fabpot added a commit that referenced this pull request Feb 21, 2018
…notation (Tobion)

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

Discussion
----------

Deprecate bundle:controller:action and service:method notation

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | yes
| Tests pass?   | yes
| Fixed tickets | #25910
| License       | MIT
| Doc PR        |

The `a::b` notation had some awkward limitations. It supported `MyControllerClass::method` where `MyControllerClass` is either plain class or a service with the same name but the class must exists. This meant it did NOT support `my_service_controller_id::method` because the `class_exists` check would fail at the wrong point in time. But it did support services where class name == id, i.e. the new auto registration based psr naming. This made it very confusing.

I enhanced the `a::b` notation to be very straight forward:
- if `a` exists as a service then use `a` as a service
- otherwise try to use `a` as a class, i.e. `new $a()`
- otherwise check if a::b is a static method (only relevant when the class is abstract or has private contructor). this was potentially supported when using array controller syntax. it now works the same when using the `::` string syntax, like in php itself. since it only happens when nothing else works, it does not have any performance impact.

The old `a:b` syntax is deprecated and just forwards to `a::b` now internally, just as bundle:controller:action.
In general I was able to refactor the logic quite a bit because it always goes through `instantiateController` now.
Spotting deprecated usages is very easy as all outdated routing configs will trigger a deprecation with the DelegatingLoader and it will be normalized in the dumped routes. So you don't get a deprecation again in the ControllerResolver. But if the controller does not come from routing, e.g. twigs render controller function, then it will still be triggered there.

- [x] deprecate `a:b:c`
- [x] deprecate `a:b`
- [x] update existing references to `a::b`
- [x] fix tests
- [x] fix/add support for static controllers
- [x] add support for closures as controllers
- [x] update Symfony\Component\Routing\Loader\ObjectRouteLoader
- [x] deprecate \Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser but we still need to use it in several places for BC.
- [x] add changelog/upgrade
- [x] update controller.service_arguments logic for double colon controller syntax

Commits
-------

f8a609c Deprecate bundle:controller:action and service:method notation
@Tobion Tobion deleted the deprecate-old-controller-notations branch February 21, 2018 20:42
nicolas-grekas added a commit that referenced this pull request Mar 19, 2018
… docblock (hacfi)

This PR was merged into the 4.1-dev branch.

Discussion
----------

[FrameworkBundle] Use `a::b` notation in ControllerTrait docblock

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #26085
| License       | MIT
| Doc PR        | already documented

<!--
Write a short README entry for your feature/bugfix here (replace this comment block.)
This will help people understand your PR and can be used as a start of the Doc PR.
Additionally:
 - Bug fixes must be submitted against the lowest branch where they apply
   (lowest branches are regularly merged to upper ones so they get the fixes too).
 - Features and deprecations must be submitted against the master branch.
-->

Replaced the deprecated the bundle notation with the new `a::b` notation in the docblock of `ControllerTrait::forward`

Commits
-------

973c5ec [FrameworkBundle] Use `a::b` notation in ControllerTrait docblock
nicolas-grekas added a commit that referenced this pull request Apr 29, 2018
… (Tobion)

This PR was merged into the 4.1-dev branch.

Discussion
----------

[HttpKernel] fix duplicate controller resolver test case

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | no <!-- don't forget to update src/**/CHANGELOG.md files -->
| BC breaks?    | no     <!-- see https://symfony.com/bc -->
| Deprecations? | no <!-- don't forget to update UPGRADE-*.md and src/**/CHANGELOG.md files -->
| Tests pass?   | yes    <!-- please add some, will be required by reviewers -->
| Fixed tickets | #26085
| License       | MIT
| Doc PR        |

Commits
-------

fc5afea fix duplicate controller resolver test case
@fabpot fabpot mentioned this pull request May 7, 2018
robfrawley added a commit to robfrawley/liip-imagine-bundle that referenced this pull request May 7, 2018
…anged

browser kit client to not catch exceptions to fix our test suite.

References:
  - https://symfony.com/blog/new-in-symfony-4-1-deprecated-the-bundle-notation
  - symfony/symfony#26085
  - symfony/symfony#22890

Signed-off-by: Rob Frawley 2nd <rmf@src.run>
}

if (1 === substr_count($controller, ':')) {
$controller = str_replace(':', '::', $controller);
Copy link
Contributor

Choose a reason for hiding this comment

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

Unfortunately this change is a BC break and breaks custom code relying on single colon notation. (Simply commenting this out fixes the BC break.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see. Removing this is an option. Do you want to create a PR?

Copy link
Contributor

Choose a reason for hiding this comment

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

Do you want to create a PR?

Not necessarily, I don't have knowledge of Symfony test suite so I'd be happy if you do that, unless you are busy. 👍

Btw already reported in #27522.

fabpot added a commit that referenced this pull request Jun 11, 2018
… notation (dmaicher)

This PR was merged into the 4.1 branch.

Discussion
----------

[FrameworkBundle] fix for allowing single colon controller notation

| Q             | A
| ------------- | ---
| Branch?       | 4.1
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #27522
| License       | MIT
| Doc PR        | -

This fixes a BC break introduced in #26085 (review).

ping @Tobion

Commits
-------

1680674 [FrameworkBundle] fix for allowing single colon controller notation
fabpot added a commit that referenced this pull request May 30, 2019
…roller (Tobion)

This PR was merged into the 4.3 branch.

Discussion
----------

[FrameworkBundle] fix test fixture using deprecated controller

| Q             | A
| ------------- | ---
| Branch?       | 4.3
| Bug fix?      | no
| New feature?  | no <!-- please update src/**/CHANGELOG.md files -->
| BC breaks?    | no     <!-- see https://symfony.com/bc -->
| Deprecations? | no <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files -->
| Tests pass?   | yes    <!-- please add some, will be required by reviewers -->
| Fixed tickets |
| License       | MIT
| Doc PR        |

https://github.com/symfony/symfony/pull/21035/files#diff-636946f5b52668178ede98d59135a181 reintroduced some deprecated controller notations (#26085) which wasn't spotted because ResolveControllerNameSubscriber missed a deprecation trigger. This also adds this trigger.

Commits
-------

7f00a74 [FrameworkBundle] fix test fixture using deprecated controller and add missing deprecation
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.