I am migrating legacy project routing (Yii1) to Symfony 5
Right now my config/routing.yaml
looks something like this:
- {path: '/login', methods: ['GET'], controller: 'App\Controller\RestController::actionLogin'}
- {path: '/logout', methods: ['GET'], controller: 'App\Controller\RestController::actionLogout'}
# [...]
- {path: '/readme', methods: ['GET'], controller: 'App\Controller\RestController::actionReadme'}
As you can see there is plenty of repetitive url
to action
conversion.
Is it possible to dynamically resolve controller method depending on some parameter. E.g.
- {path: '/{action<login|logout|...|readme>}', methods: ['GET'], controller: 'App\Controller\RestController::action<action>'}
One option would be to write annotations, but that somehow does not work for me and throws Route.php not found
The controller is determined by a RequestListener
, specifically the router RouterListener
. This in turn uses UrlMatcher
to check the uri against the RouteCollection
. You could implement a Matcher
that resolves the controller based on the route. All you have to do is return an array with a _controller
key.
Take note that this solution won't allow you to generate a url from a route name, since that's a different Interface
, but you could wire it together.
// src/Routing/NaiveRequestMatcher
namespace App\Routing;
use App\Controller\RestController;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
use Symfony\Component\Routing\RequestContext;
class NaiveRequestMatcher implements UrlMatcherInterface
{
private $matcher;
/**
* @param $matcher The original 'router' service (implements UrlMatcher)
*/
public function __construct($matcher)
{
$this->matcher = $matcher;
}
public function setContext(RequestContext $context)
{
return $this->matcher->setContext($context);
}
public function getContext()
{
return $this->matcher->getContext();
}
public function match(string $pathinfo)
{
try {
// Check if the route is already defined
return $this->matcher->match($pathinfo);
} catch (ResourceNotFoundException $resourceNotFoundException) {
// Allow only GET requests
if ('GET' != $this->getContext()->getMethod()) {
throw $resourceNotFoundException;
}
// Get the first component of the uri
$routeName = current(explode('/', ltrim($pathinfo, '/')));
// Check that the method is available...
$baseControllerClass = RestController::class;
$controller = $baseControllerClass.'::action'.ucfirst($routeName);
if (is_callable($controller)) {
return [
'_controller' => $controller,
];
}
// Or bail
throw $resourceNotFoundException;
}
}
}
Now you need to override the Listener
configuration:
// config/services.yaml
Symfony\Component\HttpKernel\EventListener\RouterListener:
arguments:
- '@App\Routing\NaiveRequestMatcher'
App\Routing\NaiveRequestMatcher:
arguments:
- '@router.default'
Not sure if it's the best approach, but seems the simpler one. The other option that comes to mind is to hook into the RouteCompiler
itself.