映射 HTTP 请求¶
该库提供了一种将 HTTP 请求映射到控制器操作参数或对象属性的方法。参数可以从路由、查询和正文值映射。
三个特性可用于将参数显式绑定到单个源,确保永远不会从错误的源解析该值:
#[FromRoute]— 用于路由器从 URL 路径中提取的参数#[FromQuery]— 用于查询字符串参数#[FromBody]— 用于请求正文值
如果参数未绑定到特定源,则可以完全省略这些特性,在这种情况下,如果在多个源中找到相同的键,则会引发冲突错误。
这为控制器提供了干净、类型安全的签名,无需耦合到框架的请求对象,同时受益于库的验证和错误处理。
正常映射规则适用于此处:参数是必需的,除非它们具有默认值。
来自 HTTP 请求的路由和查询参数值通常是字符串。映射器自动处理这些参数的标量值转换:字符串"42"将正确映射到int参数。
提示
Valinor Symfony Bundle提供与 Symfony 的 HTTP Foundation 组件的本机集成。
Mezzio 集成包会自动配置映射器,从而允许 PSR-15 请求处理程序轻松地将 PSR-7 请求映射到域对象中。
使用特性映射请求¶
考虑一个列出给定作者文章的 API。作者标识符来自 URL 路径,而过滤和分页来自查询字符串。
use CuyZ\Valinor\Mapper\Http\FromQuery;
use CuyZ\Valinor\Mapper\Http\FromRoute;
use CuyZ\Valinor\Mapper\Http\HttpRequest;
use CuyZ\Valinor\MapperBuilder;
final class ListArticles
{
/**
* GET /api/authors/{authorId}/articles?status=X&page=X&limit=X
*
* @param non-empty-string $status
* @param positive-int $page
* @param int<10, 100> $limit
*/
public function __invoke(
// Comes from the route
#[FromRoute] string $authorId,
// All come from query parameters
#[FromQuery] string $status,
#[FromQuery] int $page = 1,
#[FromQuery] int $limit = 10,
): ResponseInterface { … }
}
// GET /api/authors/42/articles?status=published&page=2
$request = new HttpRequest(
routeParameters: ['authorId' => 42],
queryParameters: [
'status' => 'published',
'page' => 2,
],
);
$controller = new ListArticles();
$arguments = (new MapperBuilder())
->argumentsMapper()
->mapArguments($controller, $request);
$response = $controller(...$arguments);
不使用特性映射请求¶
当不需要区分参数来自哪个源时,可以完全省略该特性 - 映射器将从包含匹配键的源解析每个参数。
use CuyZ\Valinor\Mapper\Http\HttpRequest;
use CuyZ\Valinor\MapperBuilder;
final class PostComment
{
/**
* POST /api/posts/{postId}/comments
*
* @param positive-int $postId
* @param non-empty-string $author
* @param non-empty-string $content
*/
public function __invoke(
int $postId,
string $author,
string $content,
): ResponseInterface { … }
}
// POST /api/posts/1337/comments
$request = new HttpRequest(
routeParameters: ['postId' => 1337],
bodyValues: [
'author' => 'jane.doe@example.com',
'content' => 'Great article, thanks for sharing!',
],
);
$controller = new PostComment();
$arguments = (new MapperBuilder())
->argumentsMapper()
->mapArguments($controller, $request);
$response = $controller(...$arguments);
注意
如果在多个源中找到没有特性的参数的相同键,则会引发冲突错误。
一次映射所有参数¶
可以使用asRoot选项将所有查询参数或正文值一次映射到单个参数,而不是将各个查询参数或正文值映射到单独的参数。当处理复杂的数据结构或参数数量很大时,这非常有用。
use CuyZ\Valinor\Mapper\Http\FromQuery;
use CuyZ\Valinor\Mapper\Http\FromRoute;
final readonly class ArticleFilters
{
public function __construct(
/** @var non-empty-string */
public string $status,
/** @var positive-int */
public int $page = 1,
/** @var int<10, 100> */
public int $limit = 10,
) {}
}
final class ListArticles
{
/**
* GET /api/authors/{authorId}/articles?status=X&&page=X&limit=X
*/
public function __invoke(
#[FromRoute] string $authorId,
#[FromQuery(asRoot: true)] ArticleFilters $filters,
): ResponseInterface { … }
}
对于正文值,同样的方法适用于#[FromBody(asRoot: true)] 。
提示
成形数组可以与asRoot一起使用,将所有值映射到单个参数:
use CuyZ\Valinor\Mapper\Http\FromQuery;
use CuyZ\Valinor\Mapper\Http\FromRoute;
final class ListArticles
{
/**
* GET /api/authors/{authorId}/articles?status=X&&page=X&limit=X
*
* @param array{
* status: non-empty-string,
* page?: positive-int,
* limit?: int<10, 100>,
* } $filters
*/
public function __invoke(
#[FromRoute] string $authorId,
#[FromQuery(asRoot: true)] array $filters,
): ResponseInterface { … }
}
映射到对象¶
HttpRequest可以直接映射到对象,而不是映射到可调用的参数。这些属性在构造函数参数或提升属性上的工作方式相同。
use CuyZ\Valinor\Mapper\Http\FromBody;
use CuyZ\Valinor\Mapper\Http\FromRoute;
use CuyZ\Valinor\Mapper\Http\HttpRequest;
use CuyZ\Valinor\MapperBuilder;
final readonly class PostComment
{
public function __construct(
/** @var positive-int */
#[FromRoute] public int $postId,
/** @var non-empty-string */
#[FromBody] public string $author,
/** @var non-empty-string */
#[FromBody] public string $content,
) {}
}
$request = new HttpRequest(
routeParameters: ['postId' => 1337],
bodyValues: [
'author' => 'jane.doe@example.com',
'content' => 'Great article, thanks for sharing!',
],
);
$comment = (new MapperBuilder())
->mapper()
->map(PostComment::class, $request);
// $comment->postId === 1337
// $comment->author === 'jane.doe@example.com'
// $comment->content === 'Great article, thanks for sharing!'
使用 PSR-7 请求¶
HttpRequest实例可以直接从PSR-7 ServerRequestInterface构建。这是与使用 PSR-7 的框架集成时推荐的方法。
use CuyZ\Valinor\Mapper\Http\HttpRequest;
use CuyZ\Valinor\MapperBuilder;
// `$psrRequest` is a PSR-7 `ServerRequestInterface` instance
// `$routeParameters` are the parameters extracted by the router
$request = HttpRequest::fromPsr($psrRequest, $routeParameters);
$arguments = (new MapperBuilder())
->argumentsMapper()
->mapArguments($controller, $request);
工厂方法从getQueryParams()中提取查询参数,从getParsedBody()中提取正文值。它还传递原始 PSR-7 请求对象,因此可以根据需要将其注入控制器参数中(见下文)。
访问原始请求对象¶
构建HttpRequest时,可以提供原始请求对象。如果控制器参数的类型与该对象匹配,则会自动注入;不需要任何特性。
use CuyZ\Valinor\Mapper\Http\FromRoute;
use CuyZ\Valinor\Mapper\Http\HttpRequest;
use CuyZ\Valinor\MapperBuilder;
use Psr\Http\Message\ServerRequestInterface;
final class ListArticles
{
/**
* GET /api/authors/{authorId}/articles
*/
public function __invoke(
// Request object injected automatically
ServerRequestInterface $request,
#[FromRoute] string $authorId,
): ResponseInterface {
$acceptHeader = $request->getHeaderLine('Accept');
// …
}
}
$request = HttpRequest::fromPsr($psrRequest, $routeParameters);
$arguments = (new MapperBuilder())
->argumentsMapper()
->mapArguments(new ListArticles(), $request);
// $arguments['request'] is the original PSR-7 request instance
约束与转换键名的大小写格式¶
API 通常使用与 PHP 代码库不同的命名约定 - 例如,带有映射到camelCase法属性的snake_case键的 JSON 负载。两个系列的配置器可以帮助解决这个问题:
- RestrictKeysTo*Case配置器拒绝与预期格式不匹配的键(例如
snake_case、camelCase),从而引发映射错误。 - MapKeysTo*Case配置器在映射之前将输入键转换为目标格式(例如
first_name→firstName)。
可以将它们组合起来,以便限制验证原始键并转换重新映射它们。两者都适用于 HTTP 请求映射。
use CuyZ\Valinor\Mapper\Configurator\MapKeysToCamelCase;
use CuyZ\Valinor\Mapper\Configurator\RestrictKeysToSnakeCase;
use CuyZ\Valinor\MapperBuilder;
$controllerArguments = (new MapperBuilder())
->configureWith(
new RestrictKeysToSnakeCase(),
new MapKeysToCamelCase(),
)
->argumentsMapper()
->mapArguments($controller, $httpRequest);
请阅读通用映射器配置器章节,了解可用配置器的完整列表和详细用法。
提示
可以使用#[MapFromKey]特性结合#[FromRoute] 、 #[FromQuery]或#[FromBody]从特定键读取单个参数:
use CuyZ\Valinor\Mapper\Configurator\MapFromKey;
use CuyZ\Valinor\Mapper\Http\FromQuery;
$controller = function (
#[FromQuery] #[MapFromKey('zipCode')]
string $postalCode,
): ResponseInterface { … };
键按原样读取,不受上述键名的大小写格式配置器的影响。有关完整行为,请参阅转换源键。
错误处理¶
当映射失败时(例如,由于缺少必需的查询参数或主体值的类型错误),会抛出MappingError ,就像常规映射一样。
请阅读验证和错误处理章节以获取更多信息。