扩展对Action参数的解析
3.1
ArgumentResolver
和value resolvers在3.1中被引入。
在《起步》的控制器一文中,你了解到可以通过控制器参数来获取Request
对象。这个参数必须是Request
类的类型提示才能被识别。而具体落实则是通过ArgumentResolver
参数解析器完成。通过创建和注册自定义的参数值解析器(augument value resolvers),你可以扩展此功能。
功能性来自HttpKernel ¶
在HttpKernel组件中Symfony推出了四个value resolver(值解析器):
RequestAttributeValueResolver
- 尝试找到一个匹配到参数名(argument name)的request属性。
RequestValueResolver
- 注入当前的
Request
对象——如果类型提示是Request
,或者是一个扩展了Request
的类的话。 DefaultValueResolver
- 若参数是可选的并且出现的话,将会被设置一个默认的参数值。
VariadicValueResolver
- 验证request数据是否是数组,是的话就把全部数据添加至参数列表中。当action被调用时,最后一个(variadic)参数,将包含这个数组中的全部值。
在Symfony 3.1之前,这部分逻辑是在ControllerResolver
中被解析的。旧有功能被重写为前述的value resovlers(值解析器)。
添加一个自定义的Value Resolver ¶
要添加一个新的值解析器,需要创建一个类,以及它的服务定义。这可以通过实现ArgumentValueResolverInterface
接口来完成。这个接口要求你实现两个方法:
supports()
这个方法用于检查value resolver是否支持给定的参数。resolve()
仅在本方法返回true
的时候被执行。
resolve()
用于解析(action)参数所对应的确切的值。一旦该值被解析,你必须yealdArgumentResolver
。
两个方法都得到Request
对象,就是当前的请求,以及一个ArgumentMetadata
实例。这个对象,包含了从“当前参数的方法签名”中所取出的全部信息(译注:签名即type hint)。
现在你知道了要做什么,你可以实现这个接口。要得到User
对象,你需要当前的security token。这个token可以从token storage(服务)中取出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
// src/AppBundle/ArgumentResolver/UserValueResolver.PHP
namespace AppBundle\ArgumentResolver;
use AppBundle\Entity\User;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class UserValueResolver implements ArgumentValueResolverInterface
{
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function supports(Request $request, ArgumentMetadata $argument)
{
if (User::class !== $argument->getType()) {
return false;
}
$token = $this->tokenStorage->getToken();
if (!$token instanceof TokenInterface) {
return false;
}
return $token->getUser() instanceof User;
}
public function resolve(Request $request, ArgumentMetadata $argument)
{
yield $this->tokenStorage->getToken()->getUser();
}
} |
为了在参数中得到真正的User
,给定的值必须满足以下条件:
在你的方法签名中,参数必须经过
User
类型提示;必须有一个security token;
给定的值必须是
User
的实例。
当所有这些条件被满足而且返回了true
时,因为先调用了supports()
方法,AugumentResolver
再以相同的值来调用resolve()
方法。
就是这样!现在你要把配置信息添加到服务容器。只要对服务打上controller.argument_resolver
再添加一个优先级即可:
1 2 3 4 5 6 7 8 |
# app/config/services.yml
services:
app.value_resolver.user:
class: AppBundle\ArgumentResolver\UserValueResolver
arguments:
- '@security.token_storage'
tags:
- { name: controller.argument_value_resolver, priority: 50 } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<!-- app/config/services.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="'http://www.w3.org/2001/XMLSchema-Instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="app.value_resolver.user"
class="AppBundle\ArgumentResolver\UserValueResolver"
>
<argument type="service" id="security.token_storage">
<tag name="controller.argument_value_resolver" priority="50" />
</service>
</services>
</container> |
1 2 3 4 5 6 7 8 9 |
// app/config/services.php
use Symfony\Component\DependencyInjection\Definition;
$defintion = new Definition(
'AppBundle\ArgumentResolver\UserValueResolver',
array(new Reference('security.token_storage'))
);
$definition->addTag('controller.argument_value_resolver', array('priority' => 50));
$container->setDefinition('app.value_resolver.user', $definition); |
虽然优先级是可选的,但还是推荐设置一个,以确保预期的value被注入。RequestAttributeValueResolver
有一个100的优先级。因为它负责的是从Request
中取出属性,建议把你的自定义value resolver设为一个更低的优先级。这可以确保参数解析器在属性现前时“不被激发”。例如,当一个用户伴随着子请求被传入时。
当你查看UserValueResolver::Supports()
的代码时,user可能并不能用(比如,控制器并没有处在防火墙下时)。这时,解析器将不被执行。如果没有参数值被解析,会抛出一个异常。
为防止这种情况出现,你应该添在控制器中添加一个默认值(比如User $user = null
)。DefaultValueResolver
作为最后一个解析器被执行,如果没有值被解析,它会使用默认值。