solventt / slim-route-strategy
为Slim微框架的路由调用策略
Requires
- php: ^8.0
- psr/container: ^1.0
- slim/slim: ^3.0 || ^4.0
Requires (Dev)
- ext-json: *
- php-di/php-di: ^6.3
- phpunit/phpunit: ^9.5
- slim/psr7: ^1.0
- squizlabs/php_codesniffer: ^3.6
- vimeo/psalm: ^4.10
- 8.0.x-dev
- 7.4.x-dev
- 1.0.0
- 0.1.0
- dev-dependabot/composer/vimeo/psalm-tw-5.24
- dev-dependabot/composer/slim/slim-tw-4.13.0
- dev-dependabot/composer/7.4/vimeo/psalm-tw-5.23
- dev-dependabot/composer/7.4/slim/slim-tw-4.13.0
- dev-dependabot/composer/squizlabs/php_codesniffer-tw-3.9
- dev-dependabot/composer/7.4/squizlabs/php_codesniffer-tw-3.9
This package is auto-updated.
Last update: 2024-08-31 00:33:24 UTC
README
目录
该软件包是Slim微框架的路由调用策略的实现。它允许您灵活地设置控制器参数的解析。关于调用策略,您可以在Slim文档中阅读。
需求
- PHP 7.4+ 或 8.0+
- Slim微框架版本 3+ 或 4+
- 任何DI容器。但如果它没有自动装配,则
TypeHintContainerRule
和MakeDtoRule
将不会影响控制器参数的解析。
安装
// php 7.4+
composer require solventt/slim-route-strategy ^0.1
// php 8.0+
composer require solventt/slim-route-strategy ^1.0
灵活控制器签名
默认情况下,Slim控制器有一个严格的签名:$request
、$response
、$args
因此,即使其中一个参数不需要,也不能省略任何一个。这被称为RequestResponse
策略。
但使用此软件包
- 您可以指定您需要的任何参数,甚至可以指定一个空的控制器签名
- 参数的顺序无关紧要
- 服务将通过类型提示进行注入
- 除了路由占位符之外,您还可以在控制器参数中接收请求属性和数据传输对象(而不是POST/PUT/PATCH数组)
- 传入的
$id
参数将是整数类型而不是默认的字符串类型(可选) - 您可以添加自己的参数解析功能,例如,您可以通过接收一些实体(用户)而不是
$id
参数
特性
自定义规则聚合器路由策略由以下规则组成
1) IdIntegerTypeRule(可选)- 如果存在,将路由参数“id”的字符串类型转换为整数类型。在声明strict_types=1时特别方便。
$app->get('/profile/{id:\d+}', [ProfileController::class, 'show']); ... public function show(int $id): Response { // incoming $id has integer type instead of default's string }
注意:控制器参数名称和路由占位符的名称必须是id
。
2) FlexibleSignatureRule - 尝试将路由参数的关联数组映射到控制器参数名称。
假设有一个控制器方法
public function show($request, $response, $id) {}
并且有路由参数
[ 'request' => 'value_1', 'response' => 'value_2', 'id' = '1' ]
那么控制器方法将接收到以下参数值
public function show($request, $response, $id) { echo $request; // 'value_1' echo $response; // 'value_2' echo $id; // '1' - string, because the IdIntegerTypeRule is off }
注意:控制器请求/响应参数的名称必须是request
和response
。
3) TypeHintContainerRule - 使用DI容器注入类型提示的控制器参数。但联合类型将被忽略。
public function show(Twig $twig, self $surrentClass) { // The Twig and declaring class instances will be automatically resolved }
4) NullTypeRule - 如果控制器参数没有默认值,则检查“null”参数类型是否存在,并在成功的情况下将其用于解析。
public function show(?string $name, ?int $count = 5) { var_dump($name); // null echo $count; // 5 }
5) MakeDtoRule - 请参阅下一节。
默认情况下,只有FlexibleSignatureRule
、TypeHintContainerRule
和NullTypeRule
是启用的。
您还可以添加自己的规则。
解析DTO
MakeDtoRule
将POST|PUT|PATCH请求的数据数组转换为数据传输对象(DTO)
public function update(Dto $dto, int $id) { // do something with $dto }
默认情况下,将创建一个内置的Dto类,并用请求数据填充。但您可以使用工厂定义自己的DTO类和自己的数据处理逻辑,以填充对象。
示例
DI容器的定义
return [ 'dtoFactories' => [ // key - is a parameter name of a controller method // value - a corresponding DTO factory class 'dto' => UserUpdateDtoFactory::class ] ];
工厂逻辑
class UserUpdateDtoFactory { public function __invoke(array $requestData): UserUpdateDto { $dto = new UserUpdateDto(); foreach ($requestData as $field => $value) { $value = match ($field) { 'phoneType' => (int) $value, 'date' => new \DateTime($value), 'isActive' => (bool) $value, default => $value }; $dto->$field = $value; } return $dto; } }
以及控制器方法
public function update(UserUpdateDto $dto) { // do something with $dto }
记住
- 参数名称必须包含“dto”子字符串。例如:'$userUpdateDto'、'$dto'、'myDto'、'loginDto'等。
- 您需要在DI容器定义中指定参数名称作为数组键。数组的值是相应的DTO工厂类。
- DI 容器定义必须命名为 'dtoFactories'(见上方示例)。
用例
对于 Slim 版本 ^4.0,index.php
<?php use DI\Container; use Slim\Factory\AppFactory; use SlimRouteStrategy\CustomRulesAggregator; require __DIR__ . '/vendor/autoload.php'; $container = new Container(); $app = AppFactory::createFromContainer($container); $strategy = new CustomRulesAggregator($container); $app->getRouteCollector()->setDefaultInvocationStrategy($strategy); $app->get('/hello/{name}', function ($response, $name) { $response->getBody()->write($name); return $response; }); $app->run();
对于 Slim 版本 ^3.0,index.php
<?php use Slim\App; use Slim\Container; use SlimRouteStrategy\CustomRulesAggregator; require __DIR__ . '/vendor/autoload.php'; $container = new Container(); $container['foundHandler'] = fn () => new CustomRulesAggregator($container); $app = new App($container); $app->get('/hello/{name}', function ($response, $name) { $response->getBody()->write($name); return $response; }); $app->run();
关于策略规则
如果您没有向路由策略构造函数提供任何规则,则默认情况下将启用 FlexibleSignatureRule
、TypeHintContainerRule
和 NullTypeRule
。
例如,您想添加 IdIntegerTypeRule
和 MakeDtoRule
,那么您应该明确定义所有必要的规则
... $strategyRules = [ IdIntegerTypeRule::class, FlexibleSignatureRule::class, MakeDtoRule::class, TypeHintContainerRule::class, NullTypeRule::class ]; $strategy = new CustomRulesAggregator($container, $strategyRules); ...
或者如果您只想添加一个规则
... $strategy = new CustomRulesAggregator($container, [FlexibleSignatureRule::class]); ...
记住
- 规则必须指定为存在的类字符串
- 规则顺序很重要。例如,如果您在
FlexibleSignatureRule
之后定义IdIntegerTypeRule
。那么IdIntegerTypeRule
将没有效果 -id
的类型将是字符串而不是整数。
上述示例显示了规则的正确顺序.
编写自定义规则
您的自定义规则必须实现 AggregatorRuleInterface
。
让我们看一个简单的例子。假设您希望控制器方法接收用户实体作为参数。因此路由是
$app->get('/profile/{user:\d+}', [ProfileController::class, 'show']);
控制器方法是
public function show(User $user){}
并且您编写了您的自定义 FindUserEntityRule
class FindUserEntityRule implements AggregatorRuleInterface { public function __construct(private UserRepository $users){} /** * @param ReflectionParameter[] $unresolvedParams parameters that have not yet been resolved * @param array $routeParams request/response objects, route placeholders values, request attributes * @param array $resolvedParams parameters resolved by previous rule (indexed by parameter position) * @return array parameters resolved by this + by previous rule */ public function resolveParameters(array $unresolvedParams, array $routeParams, array $resolvedParams): array { foreach ($unresolvedParams as $position => $parameter) { if ($parameter->name === 'user' && $this->hasAppropriateType($parameter)) { if (array_key_exists($parameter->name, $routeParams)) { $userId = (int) $routeParams[$parameter->name]; $user = $this->users->findOne($userId); $resolvedParams[$position] = $user; } } } return $resolvedParams; } private function hasAppropriateType(ReflectionParameter $parameter): bool { $type = $parameter->getType(); return !$type instanceof ReflectionUnionType && $type->getName() === User::class; } }