Registering Module-Specific Routes in Expressive
In Expressive, we have standardized on a file named
config/routes.php
to contain all your route registrations. A typical file
might look something like this:
declare(strict_types=1);
use Zend\Expressive\Csrf\CsrfMiddleware;
use Zend\Expressive\Session\SessionMiddleware;
return function (
\Zend\Expressive\Application $app,
\Zend\Expressive\MiddlewareFactory $factory,
\Psr\Container\ContainerInterface $container
) : void {
$app->get('/', App\HomePageHandler::class, 'home');
$app->get('/contact', [
SessionMiddleware::class,
CsrfMiddleware::class,
App\Contact\ContactPageHandler::class
], 'contact');
$app->post('/contact', [
SessionMiddleware::class,
CsrfMiddleware::class,
App\Contact\ProcessContactRequestHandler::class
]);
$app->get(
'/contact/thank-you',
App\Contact\ThankYouHandler::class,
'contact.done'
);
$app->get(
'/blog[/]',
App\Blog\Handler\LandingPageHandler::class,
'blog'
);
$app->get('/blog/{id:[^/]+\.html', [
SessionMiddleware::class,
CsrfMiddleware::class,
App\Blog\Handler\BlogPostHandler::class,
], 'blog.post');
$app->post('/blog/comment/{id:[^/]+\.html', [
SessionMiddleware::class,
CsrfMiddleware::class,
App\Blog\Handler\ProcessBlogCommentHandler::class,
], 'blog.comment');
}
and so on.
These files can get really long, and organizing them becomes imperative.
Using Delegator Factories
One way we have recommended to make these files simpler is to use delegator
factories
registered with the Zend\Expressive\Application
class to add routes. That
looks something like this:
namespace App\Blog;
use Psr\Container\ContainerInterface;
use Zend\Expressive\Application;
use Zend\Expressive\Csrf\CsrfMiddleware;
use Zend\Expressive\Session\SessionMiddleware;
class RoutesDelegator
{
public function __invoke(
ContainerInterface $container,
string $serviceName,
callable $callback
) : Application {
/** @var Application $app */
$app = $callback();
$app->get(
'/blog[/]',
App\Blog\Handler\LandingPageHandler::class,
'blog'
);
$app->get('/blog/{id:[^/]+\.html', [
SessionMiddleware::class,
CsrfMiddleware::class,
Handler\BlogPostHandler::class,
], 'blog.post');
$app->post('/blog/comment/{id:[^/]+\.html', [
SessionMiddleware::class,
CsrfMiddleware::class,
Handler\ProcessBlogCommentHandler::class,
], 'blog.comment');
return $app;
}
}
You would then register this as a delegator factory somewhere in your configuration:
use App\Blog\RoutesDelegator;
use Zend\Expressive\Application;
return [
'dependencies' => [
'delegators' => [
Application::class => [
RoutesDelegator::class,
],
],
],
];
Delegator factories run after the service has been created for the first time,
but before it has been returned by the container. They allow you to interact
with the service before it's returned; you can configure it futher, add
listeners, use it to configure other services, or even use them to replace the
instance with an alternative. In this example, we're opting to configure the
Application
class further by registering routes with it.
We've even written this approach up in our documentation.
So far, so good. But it means discovering where routes are registered becomes more difficult. You now have to look in each of:
-
config/routes.php
- Each file in
config/autoload/
:- looking for delegators attached to the
Application
class, - and then checking those to see if they register routes.
- looking for delegators attached to the
- In
config/config.php
to identifyConfigProvider
classes, and then:- looking for delegators attached to the
Application
class, - and then checking those to see if they register routes.
- looking for delegators attached to the
The larger your application gets, the more work this becomes. Your
config/routes.php
becomes way more readable, but it becomes far harder to find
all your routes.
One-off Functions
In examining this problem for the upteenth time this week, I stumbled upon a solut
Truncated by Planet PHP, read more at the original (another 2999 bytes)