juliangut / slim-routing
Slim 框架路由
Requires
- php: ^8.0
- juliangut/mapping: ^1.4.2
- slim/slim: ^4.7
- symfony/polyfill-php81: ^1.27
Requires (Dev)
- ext-dom: *
- ext-json: *
- doctrine/annotations: ^2.0
- infection/infection: ~0.25|~0.27
- juliangut/easy-coding-standard-config: ^1.14
- juliangut/phpstan-config: ^1.1
- laminas/laminas-diactoros: ^3.2
- mikey179/vfsstream: ^1.6
- overtrue/phplint: ^9.0
- phpcompatibility/php-compatibility: ^9.3
- phpmd/phpmd: ^2.14
- phpunit/phpunit: ^9.6|^10.4
- povils/phpmnd: ^3.2
- roave/security-advisories: dev-master
- slim/twig-view: ^3.3
- spatie/array-to-xml: ^3.2
- symfony/console: ^6.0|^7.0
- symfony/yaml: ^6.0|^7.0
Suggests
- slim/twig-view: In order to return Twig rendered responses (>=3.3)
- spatie/array-to-xml: In order to return XML responses (>=3.2)
- symfony/console: In order to use console commands (>=6.0)
- symfony/yaml: In order to load routing from YAML files (>=6.0)
README
slim-routing
是 Slim 路由器的替代品,它增加了基于属性和配置的路由,并通过处理不同的响应类型来扩展路由回调的可能性
有了这个库,您不必逐个手动配置路由并将它们包含到 Slim 的路由中,而可以创建映射文件来定义和结构化您的路由,并让它们自动包含
此外,如果您熟悉 Symfony 通过属性定义路由的方式,您会发现在 slim-routing 中也会感到宾至如归,因为路由映射可以以相同的方式进行定义
- 在类属性(即控制器类)上
- 路由定义文件,目前支持 PHP、JSON、XML 和 YAML
根据定义的类/文件和路由数量,路由收集和编译可能是一个相当繁重的过程,尤其是在属性的情况下。因此,强烈建议在生产应用程序中始终使用路由收集器的缓存和 Slim 的路由表达式缓存,并在部署时失效缓存
多亏了 slim-routing,路由回调现在可以返回 \Jgut\Slim\Routing\Response\ResponseType
对象,这些对象最终将被转换为强制性的 Psr\Message\ResponseInterface
,这样可以完全解耦视图和路由
安装
Composer
composer require juliangut/slim-routing
symfony/yaml 用于解析 yaml 路由文件
composer require symfony/yaml
spatie/array-to-xml 用于返回 XML 响应
composer require spatie/array-to-xml
slim/twig-view 用于返回 Twig 渲染的响应
composer require slim/twig-view
用法
要求 composer 自动加载文件
require './vendor/autoload.php';
use Jgut\Slim\Routing\AppFactory; use Jgut\Slim\Routing\Configuration; use Jgut\Slim\Routing\Response\PayloadResponse; use Jgut\Slim\Routing\Response\RedirectResponse; use Jgut\Slim\Routing\Response\ResponseType; use Jgut\Slim\Routing\Response\Handler\JsonResponseHandler; use Jgut\Slim\Routing\Response\Handler\RedirectResponseHandler; use Jgut\Slim\Routing\Strategy\RequestHandler; use Psr\Http\Message\ServerRequestInterface; $configuration = new Configuration([ 'sources' => ['/path/to/routing/files'], ]); AppFactory::setRouteCollectorConfiguration($configuration); // Instantiate the app $app = AppFactory::create(); $routeCollector = $app->getRouteCollector(); $responseFactory = $app->getResponseFactory(); // Register custom invocation strategy to handle ResponseType objects $invocationStrategy = new RequestHandler( [ RedirectResponse::class => new RedirectResponseHandler($responseFactory, $routeCollector), // Handlers can be pulled from the container PayloadResponse::class => JsonResponseHandler::class, ], $responseFactory, $app->getContainer() ); $routeCollector->setDefaultInvocationStrategy($invocationStrategy); $cache = new PSR16Cache(); $routeCollector->setCache($cache); // Recommended if you want to add more routes manually $routeCollector->registerRoutes(); // Additional routes if needed $app->get('/', function(ServerRequestInterface $request): ResponseType { return new PayloadResponse(['param' => 'value'], $request); }); $app->run();
配置
sources
必须是一个包含创建映射驱动对象配置数组的数组type
是 \Jgut\Slim\Routing\Mapping\Driver\DriverFactory 常量之一:DRIVER_ATTRIBUTE
、DRIVER_PHP
、DRIVER_JSON
、DRIVER_XML
、DRIVER_YAML
或DRIVER_ANNOTATION
(如果没有驱动程序,则默认为 DRIVER_ATTRIBUTE)path
是字符串路径或包含映射文件位置(文件或目录)的路径数组(如果没有驱动程序,则为必需)driver
是已创建的 \Jgut\Slim\Routing\Mapping\Driver\DriverInterface 对象(如果没有类型和路径,则为必需)
trailingSlash
布尔值,表示是否在路由模式上追加尾部斜杠(true)或完全删除它(false),默认为 falseplaceholderAliases
额外占位符别名的数组。已提供一些默认别名- numeric =>
\d+
- alpha =>
[a-zA-Z]+
- alnum =>
[a-zA-Z0-9]+
- slug ->
[a-zA-Z0-9-]+
- uuid ->
[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}
- mongoid ->
[0-9a-f]{24}
- any =>
[^}]+
- numeric =>
namingStrategy
,\Jgut\Slim\Routing\Naming\Strategy 的实例(默认为 \Jgut\Slim\Routing\Naming\SnakeCase)
在属性映射的情况下,将遍历源路径中的所有类以查找路由定义
响应处理
您是否曾想过为什么您应该在每个单独的路由中编码输出或调用模板渲染器?或者甚至为什么最终要以 ResponseInterface 对象的形式进行响应?
$app->get('/hello/{name}', function (ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface { return $this->view->render( $response, 'greet.html', [ 'name' => $args['name'] ] ); })->setName('greet');
路由回调通常响应一个Psr\Message\ResponseInterface
对象,但得益于slim-routing,它们现在可以响应一个字符串、null,甚至更好的是更具有表达意图的ResponseType对象,之后将对其进行处理。
$app->get( '/hello/{name}', fn (array $args): string => 'Hello ' . $args['name'], )->setName('greet');
当然,路由回调的正常ResponseInterface响应将按常规处理。
ResponseType感知调用策略
为了让新的响应处理工作,您需要注册由该库提供的新调用策略,提供了四个策略,直接模仿Slim提供的策略。
Jgut\Slim\Routing\Strategy\RequestHandler
Jgut\Slim\Routing\Strategy\RequestResponse
Jgut\Slim\Routing\Strategy\RequestResponseArgs
Jgut\Slim\Routing\Strategy\RequestResponseNamedArgs
响应类型
响应类型是包含所需数据的值对象,用于稍后生成ResponseInterface对象。这使路由的展示逻辑分离出来,从而允许更干净的路线和易于重用的展示逻辑。
use Jgut\Slim\Routing\Response\ResponseType; use Jgut\Slim\Routing\Response\ViewResponse; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; $app->get( '/hello/{name}', fn (ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseType => new ViewResponse('greet.html', ['name' => $args['name']], $request, $response), )->setName('greet');
如果路由返回\Jgut\Slim\Routing\Response\ResponseType
的实例,它将被传递给相应的处理器,具体取决于配置。
已经提供了三种响应类型。
RedirectResponse
Slim的具有路由感知的重定向,可以重定向到Slim路由或外部位置。PayloadResponse
存储简单的有效负载数据,稍后可以将其转换为JSON或XML等。ViewResponse
保持无知的模板参数,因此它们可以在处理器中渲染。
您可以轻松创建自己的。
响应类型处理器
映射在调用策略上,响应处理器将负责从接收到的\Jgut\Slim\Routing\Response\ResponseType
返回Psr\Message\ResponseInterface
。
通常,它们将聚集展示逻辑:如何表示响应类型中包含的数据,例如将其转换为JSON、XML等,或使用模板引擎(如Twig)进行渲染。
在创建调用策略时注册响应类型处理器,或
use Jgut\Slim\Routing\Response\PayloadResponse; use Jgut\Slim\Routing\Response\Handler\JsonResponseHandler; $invocationStrategy->setResponseHandler(PayloadResponse::class, JsonResponseHandler::class);
提供的响应类型处理器
RedirectResponseHandler
接收一个RedirectResponse类型,并返回相应的PSR7重定向响应。JsonResponseHandler
接收一个PayloadResponse类型,并返回一个PSR7 JSON响应。XmlResponseHandler
接收一个PayloadResponse类型,并返回一个PSR7 XML响应(需要spatie/array-to-xml)。TwigViewResponseHandler
接收一个通用的ViewResponse类型,并返回一个由slim/twig-view渲染的模板响应。
您可以创建自己的响应类型处理器来组合特定格式的响应(JSON:API等),或使用其他模板引擎(Plates,Blade等),或制作任何其他响应。
参数转换
路由参数在到达路由调用之前可以进行转换。此功能的常见用途是将ID转换为调用中使用的实际对象/实体。
为此,您需要提供一个\Jgut\Slim\Routing\Transformer\ParameterTransformer
实例。
例如,您可能希望将参数转换为Doctrine实体。
use Jgut\Slim\Routing\Transformer\ParameterTransformer; use Slim\Exception\HttpNotFoundException; final class UserEntityTransformer implements ParameterTransformer { public function __construct( private EntityManager $entityManager, ) {} protected function supports(string $parameter, string $type) : bool { return $parameter === 'user' && $type === UserEntity::class; } protected function transform(string $parameter, string $type, mixed $value): mixed { $user = $this->entityManager->getRepository($type)->find($value); if ($user === null) { throw new HttpNotFoundException('User not found'); } return $user; } }
请注意,单个转换器可以转换一个或多个参数。
控制台命令
use Symfony\Component\Console\Application; use Jgut\Slim\Routing\Console\ListCommand; use Jgut\Slim\Routing\Console\MatchCommand; /** @var \Slim\App $app */ $routeResolver = $app->getRouteResolver(); $cli = new Application('Slim CLI'); $cli->add(new ListCommand($routeResolver)); $cli->add(new MatchCommand($routeResolver)); $app->run();
列出路由
列出支持搜索和排序的已定义路由
php -f cli.php slim:routing:list --help
匹配路由
使用Slim的路由解析器匹配路由,因此结果将与通过HTTP请求完全相同
php -f cli.php slim:routing:match --help
路由映射
路由可以通过两种基本方式定义:通过在定义文件的多种格式中编写它们,或直接在带有属性的类中编写。
属性
组(类级别)
定义了一个路由可能驻留的组。这不是必需的,但非常有用,如果您想进行路由分组或将中间件应用于多个路由,则很有用。
use Jgut\Slim\Routing\Mapping\Attribute\Group; #[Group( prefix: 'routePrefix', parent: Area::class, pattern: 'section/{section}', placeholders: ['section': 'alpha'], arguments: ['scope' => 'public'], )] class Section {}
prefix
,可选,要附加到路由名称的前缀parent
,可选,引用父组名称pattern
,可选,路径模式,预置于路由模式之前。模式中不要使用占位符placeholders
,可选,正则表达式/别名的数组,用于模式占位符arguments
,可选,要附加到最终路由的参数数组
路由(方法级别)
定义添加到Slim中的最终路由
use Jgut\Slim\Routing\Mapping\Attribute\Route; class Section { #[Route( name: 'routeName', methods: ['GET', 'POST'], pattern: 'user/{user}', placeholders: ['user': 'alnum'], arguments: ['scope': 'admin.read'] xmlHttpRequest: true, priority: -10, )] public function userAction() {} }
name
,可选,路由名称,以便在Slim中引用methods
,可选,一个或多个接受的HTTP路由方法。特殊方法“ANY”转换成[GET, POST, PUT, PATCH, DELETE]
,如果使用ANY,则列表中不允许其他方法(默认为GET)pattern
,可选,路径模式(默认为'/')。模式中不要使用占位符placeholders
,可选,正则表达式/别名的数组,用于模式占位符arguments
,可选,要附加到路由的参数数组xmlHttpRequest
,请求应为AJAX,默认为falsepriority
,可选,整数用于排序路由注册。顺序在整个加载的路由中是全局的。负路由先加载(默认为0)
中间件(类和方法级别)
定义要应用的中间件。可以添加多个属性
use Jgut\Slim\Routing\Mapping\Attribute\Group; use Jgut\Slim\Routing\Mapping\Attribute\Middleware; use Jgut\Slim\Routing\Mapping\Attribute\Route; use Psr\Http\Message\ResponseInterface; #[Group()] #[Middleware(GroupMiddleware::class)] #[Middleware(AdditionalMiddleware::class)] class Section { #[Route(methods: ['GET'], pattern: 'user')] #[Middleware(RouteMiddleware::class)] public function userAction(): ResponseInterface {} }
middleware
,从容器中提取的MiddlewareInterface类
转换器(类和方法级别)
定义路由转换器。可以添加多个属性
use Jgut\Slim\Routing\Mapping\Attribute\Group; use Jgut\Slim\Routing\Mapping\Attribute\Route; use Jgut\Slim\Routing\Mapping\Attribute\Transformer; use Psr\Http\Message\ResponseInterface; #[Group()] #[Transformer(transformer: SectionEntityTransfomer::class)] class Section { #[Route(methods: ['GET'], pattern: 'user/{user}')] #[Transformer( transformer: UserEntityTransfomer::class), parameters: ['user': User::class], )] public function userAction($request, $response, $user): ResponseInterface {} }
transformer
,从容器中提取的ParameterTransformer类parameters
,可选,参数定义数组
定义文件
PHP
return [ [ // Group 'prefix' => 'prefix', 'pattern' => 'group-pattern', 'placeholders' => [ 'group-placeholder' => 'type', ], 'arguments' => [ 'group-argument' => 'value', ], 'parameters' => [ 'group-parameters' => 'type', ], 'transformers' => ['group-transformer'], 'middlewares' => ['group-middleware'], 'routes' => [ [ // Route 'name' => 'routeName', 'xmlHttpRequest' => true, 'methods' => ['GET', 'POST'], 'priority' => 0, 'pattern' => 'route-pattern', 'placeholders' => [ 'route-placeholder' => 'type', ], 'parameters' => [ 'route-parameters' => 'type', ], 'transformers' => ['route-transformer'], 'arguments' => [ 'route-argument' => 'value', ], 'middlewares' => ['route-middleware'], 'invokable' => 'callable', ], [ // Subgroup 'pattern' => 'subgroup-pattern', 'placeholders' => [ 'subgroup-placeholder' => 'type', ], 'arguments' => [ 'subgroup-argument' => 'value', ], 'middlewares' => ['subgroup-middleware'], 'routes' => [ // Routes/groups ... ], ], // Routes/groups ... ], ], // Routes/groups ... ];
XML
<?xml version="1.0" encoding="utf-8"?> <root> <group1 prefix="prefix" pattern="group-pattern"> <placeholders> <group-placeholder1>type</group-placeholder1> </placeholders> <arguments> <group-argument1>value</group-argument1> </arguments> <parameters> <group-parameter1>type</group-parameter1> </parameters> <transformers> <transformer1>group-transformer</transformer1> </transformers> <middlewares> <middleware1>group-middleware</middleware1> </middlewares> <routes> <route1 name="routeName" priority="0" pattern="route-pattern"> <xmlHttpRequest>true</xmlHttpRequest> <methods> <method1>GET</method1> <method2>POST</method2> </methods> <placeholders> <route-placeholder1>type</route-placeholder1> </placeholders> <parameters> <route-parameter1>type</route-parameter1> </parameters> <transformers> <transformer1>route-ransformer</transformer1> </transformers> <arguments> <route-argument1>value</route-argument1> </arguments> <middlewares> <middleware1>route-middleware</middleware1> </middlewares> <invokable>callable</invokable> </route1> <subgroup1 prefix="prefix" pattern="subgroup-pattern"> <placeholders> <subgroup-placeholder1>type</subgroup-placeholder1> </placeholders> <argument> <subgroup-argument1>value</subgroup-argument1> </argument> <middlewares> <middleware1>subgroup-middleware</middleware1> </middlewares> <routes> <!-- Routes/groups... --> </routes> </subgroup1> <!-- Routes/groups... --> </routes> </group1> <!-- Routes/groups... --> </root>
JSON
思维注释不是有效的标准JSON
[ { // Group "prefix": "prefix", "pattern": "group-pattern", "placeholders": [{ "group-placeholder": "type" }], "arguments": [{ "group-argument": "value" }], "parameters": [{ "group-parameter": "type" }], "transformers": ["group-transformer"], "middlewares": ["group-middleware"], "routes": [ { // Route "name": "routeName", "xmlHttpRequest": true, "methods": ["GET", "POST"], "priority": 0, "pattern": "route-pattern", "placeholders": [{ "route-placeholder": "type" }], "parameters": [{ "route-parameter": "type" }], "transformers": ["route-transformer"], "arguments": [{ "route-argument": "value" }], "middlewares": ["route-middleware"], "invokable": "callable" }, { // Subgroup "pattern": "subgroup-pattern", "placeholders": [{ "subgroup-placeholder": "type" }], "arguments": [{ "subgroup-argument": "value" }], "middlewares": ["subgroup-middleware"], "routes": [ // Routes/groups ... ] } // Routes/groups ... ] } // Routes/groups ... ]
YAML
# Group - prefix: prefix pattern: group-pattern placeholders: - group-placeholder: type arguments: - group-argument: value parameters: - group-parameter: type transformers: [group-ransformer] middlewares: [group-middleware] routes: # Route - name: routeName xmlHttpRequest: true methods: [GET, POST] priority: 0 pattern: route-pattern placeholders: - route-placeholder: type parameters: - route-parameter: type transformers: [route-ransformer] arguments: - route-argument: value middlewares: [route-middleware] invokable: callable # Subgroup - pattern: subgroup-pattern placeholders: - subgroup-placeholder: type arguments: - subgroup-argument: value middlewares: [subgroup-middleware] routes: # Routes/groups ... # Routes/groups ... # Routes/groups ...
组
定义一个路由可能存在的组
routes
,路由和/或子组的数组(此键标识一个组)prefix
,可选,要附加到路由名称的前缀pattern
,可选,路径模式,预置于路由模式之前。模式中不要使用占位符placeholders
,可选,正则表达式/别名的数组,用于模式占位符parameters
,可选,参数定义数组,用于转换器transformers
,可选,从容器中提取的ParameterTransformer类数组arguments
,可选,要附加到最终路由的参数数组middlewares
,可选,要添加到所有组路由的MiddlewareInterface类数组
路由
定义添加到Slim中的路由
invokable
,在路由匹配时调用的可调用对象。可以是容器条目、类名或[类,方法]数组name
,可选,路由名称,以便在Slim中引用pattern
,可选,路径模式(默认为'/')。模式中不要使用占位符placeholders
,可选,正则表达式/别名的数组,用于模式占位符xmlHttpRequest
,请求应为AJAX,默认为falsemethods
,可选,一个或多个接受的HTTP路由方法。特殊方法“ANY”转换成[GET, POST, PUT, PATCH, DELETE]
,如果使用ANY,则列表中不允许其他方法(默认为GET)parameters
,可选,参数定义数组,用于转换器transformers
,可选,从容器中提取的ParameterTransformer类数组arguments
,可选,要附加到路由的参数数组middlewares
,可选,要添加到路由的MiddlewareInterface类数组priority
,可选,整数用于排序路由注册。顺序在整个加载的路由中是全局的。负路由先加载(默认为0)
注解
注解已弃用,最终将删除。尽可能使用属性映射.
您需要要求Doctrine的注解包
composer require doctrine/annotations
组(类级别)
定义了一个路由可能驻留的组。这不是必需的,但非常有用,如果您想进行路由分组或将中间件应用于多个路由,则很有用。
use Jgut\Slim\Routing\Mapping\Annotation as JSR; /** * @JSR\Group( * prefix="routePrefix", * parent=Area::class, * pattern="section/{section}", * placeholders={"section": "[a-z]+"}, * arguments={"scope": "public"} * parameters={"section": "\Namespace\To\Section"}, * transformers={"\Namespace\To\GroupTransformer"} * middleware={"\Namespace\To\GroupMiddleware"} * ) */ class Section {}
prefix
,可选,要附加到路由名称的前缀parent
,可选,引用父组名称pattern
,可选,路径模式,预置于路由模式之前placeholders
,可选,正则表达式/别名的数组,用于模式占位符parameters
,可选,参数定义数组,用于路由转换器transformers
,可选,从容器中提取的ParameterTransformer类数组arguments
,可选,要附加到最终路由的参数数组middlewares
,可选,要添加到所有组路由的MiddlewareInterface类数组
路由(方法级别)
定义添加到Slim中的最终路由
use Jgut\Slim\Routing\Mapping\Annotation as JSR; class Section { /** * @JSR\Route( * name="routeName", * xmlHttpRequest=true, * methods={"GET", "POST"}, * pattern="user/{user}", * placeholders={"user": "[a-z0-9]+"}, * arguments={"scope": "admin.read"} * parameters={"user": "\Namespace\To\User"}, * transformers={"\Namespace\To\RouteTransformer"}, * middleware={"\Namespace\To\RouteMiddleware"}, * priority=-10 * ) */ public function userAction() {} }
name
,可选,路由名称,以便在Slim中引用pattern
,可选,路径模式(默认为'/')placeholders
,可选,正则表达式/别名的数组,用于模式占位符xmlHttpRequest
,请求应为AJAX,默认为falsemethods
,可选,接受的HTTP路由方法列表。特殊方法“ANY”转换成[GET, POST, PUT, PATCH, DELETE]
,如果使用ANY,则列表中不允许其他方法(默认为GET)parameters
,可选,参数定义数组,用于转换器transformers
,可选,从容器中提取的ParameterTransformer类数组arguments
,可选,要附加到路由的参数数组middlewares
,可选,要添加到路由的MiddlewareInterface类数组priority
,可选,整数用于排序路由注册。顺序在整个加载的路由中是全局的。负路由先加载(默认为0)
路由组合
使用juliangut/slim-routing的分组与默认Slim的路由器工作方式略有不同
组实际上不会被添加到路由器中(在您可以使用$app->group(...)
在Slim中添加它们的意义上),相反,路由是定义的组合,最终形成路由
名称
最终路由名称由组前缀的连接和根据配置的路由命名策略的路由名称组成
模式
结果路由模式由组模式(如果有)的连接和最终路由模式组成
占位符
生成的占位符列表由所有分组占位符(如有)和路由占位符组成
注意不要在生成的模式中重复占位符名称是非常重要的,因为FastRoute无法处理这种情况。检查分组树模式中的占位符名称
参数
生成的路由参数由所有分组参数(如有)和路由参数组成
中间件
添加到路由中的生成中间件将是组合分组中间件和路由中间件的结果,并按照以下顺序应用于路由,以确保最终中间件执行顺序与任何Slim应用程序中的预期相同
- 首先,将路由中间件设置为路由中定义的顺序 (强调整体顺序)
- 然后,将分组中间件(如有)设置为路由中定义的顺序 (强调整体顺序)
- 如果分组有父级,则父级的中间件将按定义的顺序设置,并且这会一直持续到没有父级分组为止
转换器和参数
生成的转换器和参数列表是所有分组转换器和参数(如有)以及路由转换器和参数的组合
与占位符类似,注意不要重复参数名称是非常重要的,因为子分组和路由将替换之前的参数
从2.x迁移
- 最低PHP版本现在是8.0
- 最低Slim版本现在是4.7
- 为路由映射引入了PHP8属性
- ParameterTransformer方法及其签名已更改
- 已删除AbstractTransformer,只需实现ParameterTransformer即可
- 一些内部方法已更改其签名或变为final
注解
注释已被弃用,并强烈建议使用属性来代替其使用
- @Router 注释是可选的,并触发E_USER_DEPRECATED
- @Group 和 @Route 注释的 "middleware" 参数已更改为 "middlewares",并接受一个列表或单个中间件
- @Route 注释现在接受一个列表或单个转换器
- @Group 注释现在也支持转换器
贡献
发现了一个错误或有一个功能请求? 请打开一个新问题。在提交之前先查看现有问题。
查看文件 CONTRIBUTING.md
许可证
有关许可证条款的副本,请参阅源代码中包含的文件 LICENSE。