支付宝扫一扫付款
微信扫一扫付款
(微信为保护隐私,不显示你的昵称)
自动关联,允许你在容器中注册服务时,使用最简配置。它基于构造器的类型提示(type hint),自动解析服务的依赖。类型提示在 Rapid Application Development 领域是非常重要的,特别是在大型项目的早期原型开发阶段。它可以令服务注册和重构变得容易。
假设,你要构建一个API,用来发布Twttier订阅的状态,使用 ROT13 来加密(凯撒加密法的一种特例)。
由创建一个ROT13 transformer类开始:
1 2 3 4 5 6 7 8 9 | namespace Acme;
class Rot13Transformer
{
public function transform($value)
{
return str_rot13($value);
}
} |
现在,一个Twitter客户端要使用这个transformer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | namespace Acme;
class TwitterClient
{
private $transformer;
public function __construct(Rot13Transformer $transformer)
{
$this->transformer = $transformer;
}
public function tweet($user, $key, $status)
{
$transformedStatus = $this->transformer->transform($status);
// ... connect to Twitter and send the encoded status
}
} |
现在,当 twitter_client
服务被标记为autowired之后,DependencyInjection组件能自动注册 TwitterClient
类的依赖:
1 2 3 4 | services:
twitter_client:
class: Acme\TwitterClient
autowire: true |
1 2 3 4 5 6 7 8 9 | <?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="twitter_client" class="Acme\TwitterClient" autowire="true" />
</services>
</container> |
1 2 3 4 5 6 7 | use Symfony\Component\DependencyInjection\Definition;
// ...
$definition = new Definition('Acme\TwitterClient');
$definition->setAutowired(true);
$container->setDefinition('twitter_client', $definition); |
自动关联子系统,将通过解析 TwitterClient
类的构造器探测到其依赖。例如,它将找到这里有一个 Rot13Transformer
实例可以作为依赖。如果一个既有的服务定义(只有一个——见下文)正是所需要的类型,那么这个服务将被注入。但如果没有找到(如同本例代码),子系统将足够智能地自动帮 Rot13Transformer
类注册一个私有服务,然后把它设为 twitter_client
服务的第一个参数(注入)。再一次,子系统只工作在“有一个给定类型的类存在” 条件下。如果相同的类型有好几个类存在,你必须使用一个显式的服务定义(来完成DI)或者注册一个默认的实现(译注:$container->register('acme.your_service', default_class_FQCN))。
你已经看到,自动关联功能戏剧化地减少了定义一个服务时所需的配置信息。没有了参数选项!同时也令改变 TwitterClient
类的依赖变得容易:只需添加或删除构造器中的类型提示参数(typehinted arguments),然后一切搞定。没有更多的诸如查找和编辑相关服务定义的步骤。
下面是一个典型的使用了 twitter_client
服务的控制器:
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 | namespace Acme\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
class DefaultController extends Controller
{
/**
* @Route("/tweet")
* @Method("POST")
*/
public function tweetAction(Request $request)
{
$user = $request->request->get('user');
$key = $request->request->get('key');
$status = $request->request->get('status');
if (!$user || !$key || !$status) {
throw new BadRequestHttpException();
}
$this->get('twitter_client')->tweet($user, $key, $status);
return new Response('OK');
}
} |
你可以通过curl
来给出API:
1 | $ curl -d "user=kevin&key=ABCD&status=Hello" http://localhost:8000/tweet |
它将返回OK
。
你可能已经发现,你正在使用抽象类来取代实现接口(特别是在近年来的程序中),因为它允许你轻松替换一些依赖,而毋须修改依赖它们的类。
为了遵循最佳实践,构造参数必须有接口级别的类型提示,而不使用具体化的类。这能令当前的implementation“在需要的时候被轻松替换”。同时还能使用其他的transformers。
现在我们引入一个 TransformerInterface
:
1 2 3 4 5 6 | namespace Acme;
interface TransformerInterface
{
public function transform($value);
} |
然后编辑 Rot13Transformer
,让它实现上面的新接口:
1 2 3 4 5 | // ...
class Rot13Transformer implements TransformerInterface
// ... |
再更新 TwitterClient
类,使其依赖这个新接口:
1 2 3 4 5 6 7 8 9 10 11 | class TwitterClient
{
// ...
public function __construct(TransformerInterface $transformer)
{
// ...
}
// ...
} |
最后把服务定义更新,因为很明显,自动关联子系统不能够找到它要自动注册的“实现了新接口的类”:
1 2 3 4 5 6 7 | services:
rot13_transformer:
class: Acme\Rot13Transformer
twitter_client:
class: Acme\TwitterClient
autowire: true |
1 2 3 4 5 6 7 8 9 10 | <?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="rot13_transformer" class="Acme\Rot13Transformer" />
<service id="twitter_client" class="Acme\TwitterClient" autowire="true" />
</services>
</container> |
1 2 3 4 5 6 7 8 9 | use Symfony\Component\DependencyInjection\Definition;
// ...
$definition1 = new Definition('Acme\Rot13Transformer');
$container->setDefinition('rot13_transformer', $definition1);
$definition2 = new Definition('Acme\TwitterClient');
$definition2->setAutowired(true);
$container->setDefinition('twitter_client', $definition2); |
现在,自动关联子系统已能够侦测到 rot13_transformer
服务实现了 TransformerInterface
接口,进而自动地注入它(到 twitter_client
服务)。甚至在使用接口时(你本当如此)、构造服务图表时、重构项目时,都比使用标准服务定义要容易。
最后,同样重要的是,自动关联功能允许指定“给定类型”的默认实现。我们引入TransformerInterface
接口的另外一个新实现,返回大写的ROT13 transformation结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | namespace Acme;
class UppercaseTransformer implements TransformerInterface
{
private $transformer;
public function __construct(TransformerInterface $transformer)
{
$this->transformer = $transformer;
}
public function transform($value)
{
return strtoupper($this->transformer->transform($value));
}
} |
这个类可以对任何一个transformer进行“装修”,返回大写的内容。
现在我们重构控制器,添加另外一个action以满足这个新的transformer:
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 37 38 39 40 41 42 43 44 | namespace Acme\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
class DefaultController extends Controller
{
/**
* @Route("/tweet")
* @Method("POST")
*/
public function tweetAction(Request $request)
{
return $this->tweet($request, 'twitter_client');
}
/**
* @Route("/tweet-uppercase")
* @Method("POST")
*/
public function tweetUppercaseAction(Request $request)
{
return $this->tweet($request, 'uppercase_twitter_client');
}
private function tweet(Request $request, $service)
{
$user = $request->request->get('user');
$key = $request->request->get('key');
$status = $request->request->get('status');
if (!$user || !$key || !$status) {
throw new BadRequestHttpException();
}
$this->get($service)->tweet($user, $key, $status);
return new Response('OK');
}
} |
最后一步就是更新服务定义了,注册这个新的实现,以便Twitter客户端能够使用它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | services:
rot13_transformer:
class: Acme\Rot13Transformer
autowiring_types: Acme\TransformerInterface
twitter_client:
class: Acme\TwitterClient
autowire: true
uppercase_rot13_transformer:
class: Acme\UppercaseRot13Transformer
autowire: true
uppercase_twitter_client:
class: Acme\TwitterClient
arguments: ['@uppercase_rot13_transformer'] |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <?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="rot13_transformer" class="Acme\Rot13Transformer">
<autowiring-type>Acme\TransformerInterface</autowiring-type>
</service>
<service id="twitter_client" class="Acme\TwitterClient" autowire="true" />
<service id="uppercase_rot13_transformer" class="Acme\UppercaseRot13Transformer" autowire="true" />
<service id="uppercase_twitter_client" class="Acme\TwitterClient">
<argument type="service" id="uppercase_rot13_transformer" />
</service>
</services>
</container> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Definition;
// ...
$definition1 = new Definition('Acme\Rot13Transformer');
$definition1->setAutowiringTypes(array('Acme\TransformerInterface'));
$container->setDefinition('rot13_transformer', $definition1);
$definition2 = new Definition('Acme\TwitterClient');
$definition2->setAutowired(true);
$container->setDefinition('twitter_client', $definition2);
$definition3 = new Definition('Acme\UppercaseRot13Transformer');
$definition3->setAutowired(true);
$container->setDefinition('uppercase_rot13_transformer', $definition3);
$definition4 = new Definition('Acme\TwitterClient');
$definition4->addArgument(new Reference('uppercase_rot13_transformer'));
$container->setDefinition('uppercase_twitter_client', $definition4); |
这里要做一些解释。现在你有两个服务,实现的都是TransformerInterface
。自动关联子系统无法猜中要把哪个做为依赖,并将导致下面这种错误:
1 2 | [Symfony\Component\DependencyInjection\Exception\RuntimeException]
Unable to autowire argument of type "Acme\TransformerInterface" for the service "twitter_client". |
幸运的是,autowiring_types
键专门用于指定默认条件下,使用哪个implementation。这个键还可以在必要时,接受一个“类型列表”。
多亏了这个选项,rot13_transformer
服务能够被自动注入到 uppercase_rot13_transformer
和 twitter_client
服务中了。对于 uppercase_twitter_client
,我们使用了一个标准的服务定义,来注入特定的 uppercase_rot13_transformer
服务。
至于其他的RAD功能,比如FrameworkBundle的控制器和annotations,记得不要在public bundles中使用自动关联功能,也不要在大规模、维护复杂的项目中使用。
本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。