在Expressive中,我们已经标准化了一个名为config/routes.php
的文件来包含您所有的路由注册。一个典型的文件可能看起来像这样:
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'); }
等等。
这些文件可能真的很长,组织它们变得势在必行。
使用委托工厂
我们推荐的一种使这些文件更简单的方法是使用在Zend\Expressive\Application
类中注册的委托工厂来添加路由。看起来像这样:
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; } }
然后您可以在您的配置中的某处将其注册为委托工厂:
use App\Blog\RoutesDelegator; use Zend\Expressive\Application; return [ 'dependencies' => [ 'delegators' => [ Application::class => [ RoutesDelegator::class, ], ], ], ];
委托工厂在服务首次创建之后运行,但在容器返回之前运行。它们允许您在返回之前与服务进行交互;你可以进一步配置它,添加监听器,用它来配置其他服务,甚至用它们来替换实例。在此示例中,我们选择通过向其注册路由来进一步配置Application
类。
我们甚至在我们的文档中写下了这种方法。
到目前为止,还不错。但这意味着发现路由注册位置变得更加困难。您现在必须查看以下各项:
config/routes.php
config/autoload/
中的每个文件:- 寻找附加的委托人到
Application
类, - 然后检查它们是否注册了路由。
- 寻找附加的委托人到
- 在
config中/config.php
以识别ConfigProvider
类,然后:- 寻找附加到
Application
类的委托, - 然后检查它们是否注册了路由。
- 寻找附加到
您的应用程序越大,工作就越多。您的config/routes.php
变得更具可读性,但找到您的所有路线变得更加困难。
一次性函数
在本周第11次检查这个问题时,我终于偶然发现了一个最初我可以接受的解决方案。
我所做的如下:
- 我在我的
ConfigProvider
中创建了一个函数,它接受Application
实例和我想传递给它的任何其他参数,并且它向实例。 - 我在我的
config/routes.php
中调用该函数。
基于上面的示例,App\Blog
模块的ConfigProvider
现在具有以下方法:
namespace App\Blog; use Zend\Expressive\Application; use Zend\Expressive\Csrf\CsrfMiddleware; use Zend\Expressive\Session\SessionMiddleware; class ConfigProvider { public function __invoke() : array { /* ... */ } public function registerRoutes( Application $app, string $basePath = '/blog' ) : void { $app->get( $basePath . '[/]', App\Blog\Handler\LandingPageHandler::class, 'blog' ); $app->get($basePath . '/{id:[^/]+\.html', [ SessionMiddleware::class, CsrfMiddleware::class, Handler\BlogPostHandler::class, ], 'blog.post'); $app->post($basePath . '/comment/{id:[^/]+\.html', [ SessionMiddleware::class, CsrfMiddleware::class, Handler\ProcessBlogCommentHandler::class, ], 'blog.comment'); } }
在我的config/routes.php
中,我可以创建一个临时实例并调用方法:
declare(strict_types=1); return function ( \Zend\Expressive\Application $app, \Zend\Expressive\MiddlewareFactory $factory, \Psr\Container\ContainerInterface $container ) : void { (new \App\Blog\ConfigProvider())->registerRoutes($app); }
这种方法消除了使用委托工厂的问题:
- 有一个明确的指示表明给定的类方法注册了路由。
- 然后我可以直接查看该方法以确定它们是什么。
我喜欢这种方法的一件事是它允许我将路由保持在处理它们的代码附近(即,在每个模块内),同时仍然让我在应用程序级别控制它们的注册。
您尝试过哪些策略?