 
                    支付宝扫一扫付款
 
                    微信扫一扫付款
(微信为保护隐私,不显示你的昵称)
自动关联,允许你在容器中注册服务时,使用最简配置。它基于构造器的类型提示(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 创作共用授权。