目录
3) Make the Services Private 使服务私有
4) Auto-registering Services 自动注册服务
如果你看一下services.yaml
在新的Symfony 3.3或更高版本的项目文件,你会发现一些大的变化:_defaults
,autowiring
,autoconfigure
等等。这些功能旨在自动化配置并使开发更快,而不会牺牲可预测性,这非常重要!另一个目标是使控制器和服务表现得更加一致。在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文件中,
你也可以设置autoconfigure
为false
,或者为了特定服务而禁用它。
这是不是意味着标签已经死了?这适用于所有标签吗?
这种自动配置并不是对所有标签都有效。许多标记都具有必需的属性,例如事件 侦听器,它还需要您在标记中指定事件名称和方法。自动配置仅适用于没有任何必需标记属性的标记,当您查阅该特性的文档时,它会告诉您是否需要标记。您还可以查看扩展类(例如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
首先添加一个_defaults
带autowire
和的部分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_notifier
to 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\GitHubNotifier
和App\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_github
和app.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:服务容器