symfony配置文件之二:services和服务装配

目录

        0、所有更改都是可选的

        1、新的缺省配置文件services.yaml

1) 服务自动加载

2) 缺省自动装配: 使用类型提示,而不是服务ID

3) 控制器被注册为服务

4) 使用自动标记,完成自动配置

5)使用_instanceof,进行自动配置

6)自动装配的性能怎么样

2、如何升级到Symfony 3.3配置

1): 添加 _defaults

2) 使用服务的 id's

3) Make the Services Private 使服务私有

4) Auto-registering Services 自动注册服务

5) Cleanup!  清理


        如果你看一下services.yaml在新的Symfony 3.3或更高版本的项目文件,你会发现一些大的变化:_defaultsautowiringautoconfigure等等。这些功能旨在自动化配置并使开发更快,而不会牺牲可预测性,这非常重要!另一个目标是使控制器和服务表现得更加一致。在Symfony 3.3中,控制器 默认服务。

0、所有更改都是可选的

        最重要的是,您可以立即升级到Symfony 3.3,而无需对您的应用进行任何更改。Symfony具有严格的向后兼容性承诺,这意味着在次要版本之间升级始终是安全的。

所有新功能都是可选的:默认情况下不启用它们,因此您需要实际更改配置文件以使用它们

1、新的缺省配置文件services.yaml

        要了解这些更改,请查看新的默认services.yaml文件(这是Symfony 4中的文件):

YAML文件

# config/services.yaml
services:
    # default configuration for services in *this* file
    _defaults:
        autowire: true      # Automatically injects dependencies in your services.
        autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
        public: false       # Allows optimizing the container by removing unused services; this also means
                            # fetching services directly from the container via $container->get() won't work.
                            # The best practice is to be explicit about your dependencies anyway.

    # makes classes in src/ available to be used as services
    # this creates a service per class whose id is the fully-qualified class name
    App\:
        resource: '../src/*'
        exclude: '../src/{Entity,Migrations,Tests}'

    # controllers are imported separately to make sure services can be injected
    # as action arguments even if you don't extend any base controller class
    App\Controller\:
        resource: '../src/Controller'
        tags: ['controller.service_arguments']

    # add more service definitions when explicit configuration is needed
    # please note that last definitions always *replace* previous ones

XML文件

<!-- 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>
        <defaults autowire="true" autoconfigure="true" public="false" />

        <prototype namespace="App\" resource="../src/*" exclude="../src/{Entity,Migrations,Tests}" />

        <prototype namespace="App\Controller\" resource="../src/Controller">
            <tag name="controller.service_arguments" />
        </prototype>

        <!-- add more services, or override services that need manual wiring -->
    </services>
</container>

仅仅这些代码,展示了symfony容器配置上的样式变化。

1) 服务自动加载

第一个重大变化是服务不再需要逐个定义,这要归功于以下配置:

YAML文件

# config/services.yaml
services:
    # ...

    # makes classes in src/ available to be used as services
    # this creates a service per class whose id is the fully-qualified class name
    App\:
        resource: '../src/*'
        exclude: '../src/{Entity,Migrations,Tests}'

XML文件

<!-- 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>
        <!-- ... -->

        <prototype namespace="App\" resource="../src/*" exclude="../src/{Entity,Migrations,Tests}" />
    </services>
</container>

        这意味着在src/文件夹下的每一个类都可以用于服务,借助于_defaults设置,所有的这些服务都将被自动装配,并且设置为private。服务ID被默认设置为类名(例如 App\Service\InvoiceGenerator),这是Symfony 3.3中你会注意到的另一个变化:我们建议您使用类名作为服务ID,除非您为同一个类提供多个服务(见第2)缺省自动装配)。

   针对src/目录下的一些模型(非服务)类,是不是就意味着它们也将被注册为服务而引发异常呢?实际上,这都不是问题。由于所有新服务都是私有的 (感谢_defaults),如果您的代码中没有使用任何服务,它们将自动从编译容器中删除。这意味着无论是显式配置每个服务,还是使用此方法一次性加载所有服务,容器中的服务数应该相同。当然,我们也可以使用exclude关键字来排除一些目录和文件。但是,由于未使用的服务会自动从容器中删除,因此exclude并不重要。它最大的好处是:容器不会跟踪这些路径,从而导致容器在dev环境中更少地被重建。

2) 缺省自动装配: 使用类型提示,而不是服务ID

        第二个重大变化是,_defaults为注册的所有服务启用自动装配。这就意味着相比服务IDS,服务的“类型”(即类或接口名称)变得更重要了。

        例如,在Symfony3.3之前,你需要使用以下配置,将一个服务作为参数传递给另一个服务:

YAML文件

# config/services.yaml
services:
    app.invoice_generator:
        class: App\Service\InvoiceGenerator

    app.invoice_mailer:
        class: App\Service\InvoiceMailer
        arguments:
            - '@app.invoice_generator'

XML文件

<!-- 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.invoice_generator"
            class="App\Service\InvoiceGenerator" />

        <service id="app.invoice_mailer"
            class="App\Service\InvoiceMailer">

            <argument type="service" id="app.invoice_generator" />
        </service>
    </services>
</container>

PHP文件

// config/services.php
use App\Service\InvoiceGenerator;
use App\Service\InvoiceMailer;
use Symfony\Component\DependencyInjection\Reference;

$container->register('app.invoice_generator', InvoiceGenerator::class);
$container->register('app.invoice_mailer', InvoiceMailer::class)
    ->setArguments([new Reference('app.invoice_generator')]);

        为了传递参数InvoiceGenerator作为InvoiceMailer, 你需要指明app.invoice_mailer服务的依赖服务InvoiceGenerator的服务ID即app.invoice_generator为参数,这是配置服务的主要方式。.

        但是在Symfony 3.3版本下, 借助自动装配, 你只需使用InvoiceGenerator作为类型提示的参数即可。

// src/Service/InvoiceMailer.php
// ...

class InvoiceMailer
{
    private $generator;

    public function __construct(InvoiceGenerator $generator)
    {
        $this->generator = $generator
    }

    // ...
}

        就这样,两个服务都被自动注册, 没有任何服务配置,服务容器就会知道传递自动注册的 App\Service\InvoiceGenerator 作为第一个参数。正如你看到的,相比服务ID,类型 (如App\Service\InvoiceGenerator) 是多么重要啊。你只要请求特定类型的一个实例,然后容器就会自动传递正确的服务。

为同一个类提供多个服务如何明确依赖该类的服务?

        如果使用同一个类配置了多个服务的话,可以如symfony3.3之前那样显式配置服务和参数如下:

YAML文件

# config/services.yaml
services:
    # ...

    # this is the service's id
    site_update_manager.superadmin:
        class: App\Updates\SiteUpdateManager
        # you CAN still use autowiring: we just want to show what it looks like without
        autowire: false # 个别非自动装配的服务
        # manually wire all arguments
        arguments:
            - '@App\Service\MessageGenerator'
            - '@mailer'
            - 'superadmin@example.com'

    site_update_manager.normal_users:
        class: App\Updates\SiteUpdateManager
        autowire: false # 个别非自动装配的服务
        arguments:
            - '@App\Service\MessageGenerator'
            - '@mailer'
            - 'contact@example.com'

    # Create an alias, so that - by default - if you type-hint SiteUpdateManager,
    # the site_update_manager.superadmin will be used
    App\Updates\SiteUpdateManager: '@site_update_manager.superadmin' # 指明服务别名

XML文件

<!-- 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="site_update_manager.superadmin" class="App\Updates\SiteUpdateManager" autowire="false">
            <argument type="service" id="App\Service\MessageGenerator" />
            <argument type="service" id="mailer" />
            <argument>superadmin@example.com</argument>
        </service>

        <service id="site_update_manager.normal_users" class="App\Updates\SiteUpdateManager" autowire="false">
            <argument type="service" id="App\Service\MessageGenerator" />
            <argument type="service" id="mailer" />
            <argument>contact@example.com</argument>
        </service>

        <service id="App\Updates\SiteUpdateManager" alias="site_update_manager.superadmin" />
    </services>
</container>

PHP文件

// config/services.php
use App\Updates\SiteUpdateManager;
use App\Service\MessageGenerator;
use Symfony\Component\DependencyInjection\Reference;

$container->register('site_update_manager.superadmin', SiteUpdateManager::class)
    ->setAutowired(false)
    ->setArguments([
        new Reference(MessageGenerator::class),
        new Reference('mailer'),
        'superadmin@example.com'
    ]);

$container->register('site_update_manager.normal_users', SiteUpdateManager::class)
    ->setAutowired(false)
    ->setArguments([
        new Reference(MessageGenerator::class),
        new Reference('mailer'),
        'contact@example.com'
    ]);

$container->setAlias(SiteUpdateManager::class, 'site_update_manager.superadmin')

        如果依赖的类如App\Updates\SiteUpdateManager有多个服务,在不指明具体服务的情况下(即自动装配的情况),系统将引用自动装配的服务(即以完整类名为ID),如果仅有手动装配的服务时,我们需要手动地为以完整类名为别名的服务关联另一个别名,如yaml文件中的代码 App\Updates\SiteUpdateManager: '@site_update_manager.superadmin'。

        此外,我们也可以明确指定依赖类的服务ID,选择具体的服务。如下所示:

The MessageGenerator service created earlier requires a LoggerInterface argument:.

// src/Service/MessageGenerator.php
// ...

use Psr\Log\LoggerInterface;

class MessageGenerator
{
    private $logger;

    public function __construct(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }
    // ...
}

        容器则怎么知道应该引用哪个服务呢?在这种情况下,容器通常被配置为自动选择其中的一个logger服务 (read more about why in Using Aliases to Enable Autowiring)。但是,你也可以进行控制,选择不同的logger服务。如下所示:

YAML文件

# config/services.yaml
services:
    # ... same code as before

    # explicitly configure the service
    App\Service\MessageGenerator: #完整类名作为别名的服务,使用自动装配方式
        arguments:
            # the '@' symbol is important: that's what tells the container
            # you want to pass the *service* whose id is 'monolog.logger.request',
            # and not just the *string* 'monolog.logger.request'
            $logger: '@monolog.logger.request'

XML文件

<!-- 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>
        <!-- ... same code as before -->

        <!-- Explicitly configure the service -->
        <service id="App\Service\MessageGenerator">
            <argument key="$logger" type="service" id="monolog.logger.request" />
        </service>
    </services>
</container>

PHP文件

// config/services.php
use App\Service\MessageGenerator;
use Symfony\Component\DependencyInjection\Reference;

$container->autowire(MessageGenerator::class)
    ->setAutoconfigured(true)
    ->setPublic(false)
    ->setArgument('$logger', new Reference('monolog.logger.request'));

         这样做将告诉容器,构造函数__construct的$logger参数将引用ID名称为 monolog.logger.request的服务。

        服务容器自动装配的简单逻辑:

        自动装配系统被设计为超级智能的,它通过查找服务id 与类型提示type-hint 完全匹配的服务来进行装配。这意味着您可以完全控制由哪种类型提示映射到哪种服务。您甚至可以使用服务别名来获得更多控制权。如果你有一个特定类型的多个服务,那么就可以通过服务别名来选择哪一个应该被用于自动装配。

手动装配标量参数,解决标量参数无法自动装配的问题

        如果类构造函数的参数不是对象,而是一个标量如字符串,则无法自动装配。如果遇到标量参数自动装配的情况,系统将为您提供一个明确的异常(在下一次刷新任何页面时),告诉您哪个服务无法自动装配。要修复它,就需要手动装配标量参数。例如,假设您想要实现一个管理员邮件配置 admin email configurable,代码如下所示:

// src/Updates/SiteUpdateManager.php
// ...

class SiteUpdateManager
{
    // ...
     private $adminEmail;
     public function __construct(MessageGenerator $messageGenerator, \Swift_Mailer $mailer, $adminEmail)
    {
        // ...
         $this->adminEmail = $adminEmail;
    }

    public function notifyOfSiteUpdate()
    {
        // ...

        $message = \Swift_Message::newInstance()
            // ...
             ->setTo($this->adminEmail)
            // ...
        ;
        // ...
    }
}

        当字符串标量参数$adminEmail自动装配时,就会抛出异常:Cannot autowire service "AppUpdatesSiteUpdateManager": argument "$adminEmail" of method "__construct()" must have a type-hint or be given a value explicitly. 想要修复这个异常,可以在配置文件中手动显式地为该服务设置参数,如下所示:

YAML文件

# config/services.yaml
services:
    # ...

    # same as before
    App\:
        resource: '../src/*'
        exclude: '../src/{Entity,Migrations,Tests}'

    # explicitly configure the service
    App\Updates\SiteUpdateManager:
        arguments:
            $adminEmail: 'manager@example.com'

XML文件

<!-- 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>
        <!-- ... -->

        <!-- Same as before -->
        <prototype namespace="App\" resource="../src/*" exclude="../src/{Entity,Migrations,Tests}" />

        <!-- Explicitly configure the service -->
        <service id="App\Updates\SiteUpdateManager">
            <argument key="$adminEmail">manager@example.com</argument>
        </service>
    </services>
</container>

PHP文件

// config/services.php
use App\Updates\SiteUpdateManager;
use Symfony\Component\DependencyInjection\Definition;

// Same as before
$definition = new Definition();

$definition
    ->setAutowired(true)
    ->setAutoconfigured(true)
    ->setPublic(false)
;

$this->registerClasses($definition, 'App\\', '../src/*', '../src/{Entity,Migrations,Tests}');

// Explicitly configure the service
$container->getDefinition(SiteUpdateManager::class)
    ->setArgument('$adminEmail', 'manager@example.com');

        手动为服务配置标量参数,当创建服务SiteUpdateManager时,服务容器将把 manager@example.com 传递给构造器的 $adminEmail 参数 。构造器的其它参数,将被自动装配。

        还有一种手动配置标量参数的方法,那就是使用%parameter_name%引用parameters参数。如下所示:

YAML文件

# config/services.yaml
parameters:
    admin_email: manager@example.com

services:
    # ...

    App\Updates\SiteUpdateManager:
        arguments:
            $adminEmail: '%admin_email%'

XML文件

<!-- 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">

    <parameters>
        <parameter key="admin_email">manager@example.com</parameter>
    </parameters>

    <services>
        <!-- ... -->

        <service id="App\Updates\SiteUpdateManager">
            <argument key="$adminEmail">%admin_email%</argument>
        </service>
    </services>
</container>

PHP文件

// config/services.php
use App\Updates\SiteUpdateManager;
$container->setParameter('admin_email', 'manager@example.com');

$container->autowire(SiteUpdateManager::class)
    // ...
    ->setArgument('$adminEmail', '%admin_email%');

        实际上,一旦定义了参数,就可以在任何其他配置文件中通过%parameter_name%语法来引用它 。这些配置参数主要用于在服务中进行引用,所有应该在文件config/services.yaml中进行定义。

        然后,您可以获取服务中的参数:

class SiteUpdateManager
{
    // ...

    private $adminEmail;

    public function __construct($adminEmail) // 自动获取服务中的参数
    {
        $this->adminEmail = $adminEmail;
    }
}

        您还可以直接从容器中获取参数:

class XXXController extends AbstractController 
{
    public function new() //控制器中的动作
    {
        // ...

        // this shortcut ONLY works if you extend the base AbstractController
        $adminEmail = $this->getParameter('admin_email'); // 获取配置参数

        // this is the equivalent code of the previous shortcut:
        // $adminEmail = $this->container->get('parameter_bag')->get('admin_email');
    }
    ...
}

 

自动装配可能会使系统不稳定。如果你改变了一件事或犯了错误,可能会发生意想不到的事情。

Symfony始终首先重视稳定性,安全性和可预测性。自动装配的设计考虑到了这一点。特别是:

  • 如果将任何参数连接到任何服务时出现问题,则即使您未在该页面上使用该服务,也会在下次刷新任何页面时抛出明确的异常。这是强大的:只要出现自动装配错误,就一定能发现它,实时抛出自动装配异常。
  • 容器的自动装配逻辑,明确地决定了一种传递服务的方式:它查找服务ID和类型完全匹配的服务,而没有扫描所有服务去寻找一个实现了指定类/接口的对象。

3) 控制器被注册为服务

        第三个重大变化是,在一个新的Symfony 3.3项目中,您的控制器是服务

YAML文件

# config/services.yaml
services:
    # ...

    # controllers are imported separately to make sure they're public
    # and have a tag that allows actions to type-hint services
    App\Controller\:
        resource: '../src/Controller'
        tags: ['controller.service_arguments']

XML文件

<!-- 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>
        <!-- ... -->

        <prototype namespace="App\Controller\" resource="../src/Controller">
            <tag name="controller.service_arguments" />
        </prototype>
    </services>
</container>

PHP文件

// config/services.php

// ...

$definition->addTag('controller.service_arguments');
$this->registerClasses($definition, 'App\\Controller\\', '../src/Controller/*');

        但是,你甚至可能都没有注意到这一点。首先,您的控制器仍然可以扩展相同的基本控制器类(AbstractController)。这意味着您可以访问与以前相同的所有快捷方式。此外,在路由过程中,@Route注释和_controller语法(如App:Default:homepage)会自动使用你的控制器作为服务。(只要它的服务ID匹配它的完整类名)。有关 更多详细信息,请参见如何将控制器定义为服务。您甚至可以创建可调用的控制器

        换句话说,一切都是一样的。您甚至可以将上述配置添加到现有项目中而不会出现任何问题:控制器的行为与以前相同。但是,现在您的控制器是服务,您可以像任何其他服务一样使用依赖注入和自动装配。

        为了让生活更轻松,现在可以将控制器操作方法的参数自动装配,就像使用服务的构造函数一样。例如:

use Psr\Log\LoggerInterface;

class InvoiceController extends AbstractController
{
    public function listInvoices(LoggerInterface $logger)
    {
        $logger->info('A new way to access services!');
    }
}

        这只能在控制器中实现,并且必须标记您的控制器服务为controller.service_arguments才能实现。整个文档中都使用了这一新功能。

        通常,新的最佳实践是使用普通的构造函数依赖注入(或控制器中的“动作”注入)而不是通过获取公共服务$this->get()(尽管这仍然有效)。

4) 使用自动标记,完成自动配置

        第四个重大变化是autoconfigure键,它位于_defaults键下被设置为true。由于这个原因,容器将自动标记在此文件中注册的服务。例如,假设您要创建事件订阅者。首先,您创建类:

// src/EventSubscriber/SetHeaderSusbcriber.php
// ...

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;

class SetHeaderSusbcriber implements EventSubscriberInterface
{
    public function onKernelResponse(FilterResponseEvent $event)
    {
        $event->getResponse()->headers->set('X-SYMFONY-3.3', 'Less config');
    }

    public static function getSubscribedEvents()
    {
        return [
            KernelEvents::RESPONSE => 'onKernelResponse'
        ];
    }
}

        太好了!在Symfony 3.2或更低版本中,您现在需要在services.yaml文件中将其注册为服务,并将其标记为kernel.event_subscriber。在Symfony 3.3中,你已经完成了该服务自动注册。由于配置了autoconfigure键,Symfony会自动标记服务,因为它实现了EventSubscriberInterface

这听起来像魔术 - 它会自动标记我的服务?

        在这种情况下,您已经创建了一个实现了EventSubscriberInterface接口的类,并且将该类注册为服务。这足以让容器知道您希望将其用作事件订阅者:不需要更多配置。然而,标签系统是Symfony自己特定的机制。在services.yaml文件中,你也可以设置autoconfigurefalse,或者为了特定服务而禁用它。

这是不是意味着标签已经死了?这适用于所有标签吗?

        这种自动配置并不是对所有标签都有效。许多标记都具有必需的属性,例如事件 侦听器,它还需要您在标记中指定事件名称和方法。自动配置仅适用于没有任何必需标记属性的标记,当您查阅该特性的文档时,它会告诉您是否需要标记。您还可以查看扩展类(例如FrameworkExtension for 3.3.0)以查看它自动配置的内容。

如果我需要为我的标签添加优先权怎么办?

        许多自动配置的标签具有可选的优先级。如果需要指定优先级(或任何其他可选标记属性),没问题!手动配置服务 并添加标记。您的标记将优先于自动配置添加的标记。

5)使用_instanceof,进行自动配置

        而最后的重大变化是_instanceof。它充当默认定义模板(请参阅service-33-default_definition),但仅适用于其类与已定义的类匹配的服务,即用完整类名定义的服务。

        当许多服务共享一些无法从抽象定义继承的标记时(??????),这非常有用:

YAML文件

# config/services.yaml
services:
    # ...

    _instanceof:
        App\Domain\LoaderInterface:
            public: true
            tags: ['app.domain_loader']

XML文件

<!-- 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>
        <!-- ... -->

        <instanceof id="App\Domain\LoaderInterface" public="true">
            <tag name="app.domain_loader" />
        </instanceof>
    </services>
</container>

6)自动装配的性能怎么样

        Symfony是独一无二的,因为它有一个编译容器。这意味着使用任何这些功能都 不会影响运行时性能。这也是自动装配系统可以为您提供如此明显错误的原因。

        但是,在dev环境中会有一些性能影响。最重要的是,当您修改服务类时,您的容器可能会更频繁地重建。这是因为无论何时向服务添加新参数,或者向应该自动配置的类添加接口,都需要重建。

        在非常大的项目中,这可能是一个问题。如果是,您可以选择 使用自动装配。如果您认为缓存重建系统在某些情况下可能更智能,请打开一个问题!

2、如何升级到Symfony 3.3配置

        准备升级现有项目了吗?大!假设您具有以下配置:

# config/services.yaml
services:
    app.github_notifier:
        class: App\Service\GitHubNotifier
        arguments:
            - '@app.api_client_github'

    markdown_transformer:
        class: App\Service\MarkdownTransformer

    app.api_client_github:
        class: App\Service\ApiClient
        arguments:
            - 'https://api.github.com'

    app.api_client_sl_connect:
        class: App\Service\ApiClient
        arguments:
            - 'https://connect.symfony.com/api'

        它是可选的,但让我们一步一步地将它升级到新的Symfony 3.3配置, 而不会破坏我们的应用程序。

1): 添加 _defaults

        首先添加一个_defaultsautowire和的部分autoconfigure

# config/services.yaml
services:
+     _defaults:
+         autowire: true
+         autoconfigure: true

    # ...

        您已经明确配置了所有服务。所以,autowire 什么都不做。您还在标记您的服务,因此autoconfigure 也不会更改任何现有服务。你还没有添加public: false。那将是一分钟。

2) 使用服务的 id's

        现在,服务ID是机器名称 - 例如app.github_notifier。要使用新配置系统,您的服务ID应该是类名,除非您有多个相同服务的实例。首先将服务ID更新为类名:

# config/services.yaml
services:
    # ...

-     app.github_notifier:
-         class: App\Service\GitHubNotifier
+     App\Service\GitHubNotifier:
        arguments:
            - '@app.api_client_github'

-     markdown_transformer:
-         class: App\Service\MarkdownTransformer
+     App\Service\MarkdownTransformer: ~

    # keep these ids because there are multiple instances per class
    app.api_client_github:
        # ...
    app.api_client_sl_connect:
        # ...
警告 与全局PHP类关联的服务(即不使用PHP命名)

警告

        与全局PHP类关联的服务(即不使用PHP命名空间)必须维护该class参数。例如,当使用旧的Twig类(例如,Twig_Extensions_Extension_Intl而不是Twig\Extensions\IntlExtension)时,您无法重新定义服务,Twig_Extensions_Extension_Intl: ~并且必须保留原始class参数。

警告

         如果服务由Compiler pass处理,您可能会遇到“您已请求不存在的服务”错误。要摆脱这种情况,请确保使用Compiler Pass使用findDefinition() 代替getDefinition(),后者在查找服务时不会考虑别名。此外,始终建议使用has()函数检查定义是否存在。

注意

        如果你不反对,使用控制器扩展自 AbstractController而不是Controller,你可以跳过这一步的其余部分,因为AbstractController它没有提供一个可以从中获取服务的容器。所有服务都需要按照本文第5步中的说明进行注入。

        但是,这一变化将破坏我们的应用程序!旧服务ID(例如app.github_notifier)将不再存在。解决此问题的最简单方法是,找到所有旧服务ID并将其更新为新的类ID:app.github_notifierto App\Service\GitHubNotifier

        在大型项目中,有一种更好的方法:创建将旧id映射到新id的旧别名。创建一个新legacy_aliases.yaml文件,如下:

# config/legacy_aliases.yaml
services:
    _defaults:
        public: true
    # aliases so that the old service ids can still be accessed
    # remove these if/when you are not fetching these directly
    # from the container via $container->get()
    app.github_notifier: '@App\Service\GitHubNotifier'
    markdown_transformer: '@App\Service\MarkdownTransformer'

        然后在services.yaml文件的顶部导入:

# config/services.yaml
+ imports:
+     - { resource: legacy_aliases.yaml }

# ...

        就这样,旧的服务ID仍然有效。稍后,(请参阅下面的清理步骤),您可以从您的应用中删除这些。

3) Make the Services Private 使服务私有

        现在您已准备好将所有服务默认为私有:

# config/services.yaml
# ...

services:
     _defaults:
         autowire: true
         autoconfigure: true
+          public: false

        由于这个原因,无法直接从容器中获取在此文件中创建的任何服务。但是,由于旧服务ID是单独文件(legacy_aliases.yaml)中的别名,因此它们仍然是公共的。这可以确保应用程序继续工作。

        如果您不改变一些服务的ID(因为有相同类的多个实例),你可能需要将这些公开:

# config/services.yaml
# ...

services:
    # ...

    app.api_client_github:
        # ...

+         # remove this if/when you are not fetching this
+         # directly from the container via $container->get()
+         public: true

    app.api_client_sl_connect:
        # ...
+         public: true

        如果您没有直接从容器中获取这些服务,则不需要这样做。

4) Auto-registering Services 自动注册服务

        现在,您已准备好自动注册src/ (和/ 或其它目录/bundle)中的所有服务:

# config/services.yaml

services:
    _defaults:
        # ...

+     App\:
+         resource: '../src/*'
+         exclude: '../src/{Entity,Migrations,Tests}'
+
+     App\Controller\:
+         resource: '../src/Controller'
+         tags: ['controller.service_arguments']

    # ...

        就这样!实际上,您已经覆盖并重新配置了您正在使用的所有服务(App\Service\GitHubNotifierApp\Service\MarkdownTransformer)。但现在,您无需手动注册未来的服务。

        如果您拥有同一类的多个服务,则会再次出现一个额外的复杂情况:

# config/services.yaml

services:
    # ...

+     # alias ApiClient to one of our services below
+     # app.api_client_github will be used to autowire ApiClient type-hints
+     App\Service\ApiClient: '@app.api_client_github'

    app.api_client_github:
        # ...
    app.api_client_sl_connect:
        # ...

        这可以保证,如果您尝试自动装配ApiClient实例,app.api_client_github 将使用该实例。如果您没有这个,自动注册功能将尝试注册第三个ApiClient服务并将其用于自动装配(这将失败,因为该类具有不可自动装配的参数)。

5) Cleanup!  清理

        为了确保您的应用程序没有中断,您做了一些额外的工作。现在是时候清理了!首先,更新您的应用程序以使用旧的服务ID(在legacy_aliases.yaml中的那些服务)。这意味着在更新任何服务参数(例如 @app.github_notifier@App\Service\GitHubNotifier),并更新你的代码不直接从容器中获取服务。例如:

-     public function index()
+     public function index(GitHubNotifier $gitHubNotifier, MarkdownTransformer $markdownTransformer)
    {
-         // the old way of fetching services
-         $githubNotifier = $this->container->get('app.github_notifier');
-         $markdownTransformer = $this->container->get('markdown_transformer');

        // ...
    }

        执行此操作后,您可以删除legacy_aliases.yaml和它的导入。您应该对您公开的任何服务执行相同的操作,例如 app.api_client_githubapp.api_client_sl_connect。一旦你没有直接从容器中获取这些服务,你可以删除public: true标志:

# config/services.yaml
services:
    # ...

    app.api_client_github:
        # ...
-         public: true

    app.api_client_sl_connect:
        # ...
-         public: true

        最后,您可以从services.yaml中选择性地删除可以自动装配其参数的任何服务。最终配置如下所示:

services:
    _defaults:
        autowire: true
        autoconfigure: true
        public: false

    App\:
        resource: '../src/*'
        exclude: '../src/{Entity,Migrations,Tests}'

    App\Controller\:
        resource: '../src/Controller'
        tags: ['controller.service_arguments']

    App\Service\GitHubNotifier:
        # this could be deleted, or I can keep being explicit
        arguments:
            - '@app.api_client_github'

    # alias ApiClient to one of our services below
    # app.api_client_github will be used to autowire ApiClient type-hints
    App\Service\ApiClient: '@app.api_client_github'

    # keep these ids because there are multiple instances per class
    app.api_client_github:
        class: App\Service\ApiClient
        arguments:
            - 'https://api.github.com'

    app.api_client_sl_connect:
        class: App\Service\ApiClient
        arguments:
            - 'https://connect.symfony.com/api'

        您现在可以利用未来的新功能。

参考1:The Symfony 3.3 DI Container Changes Explained (autowiring, _defaults, etc)

参考2:Defining Services Dependencies Automatically (Autowiring)  自动定义服务依赖项(自动装配)

参考3:服务容器

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值