0

Symfony服务

zhouxy 2月8日 发布于后端 www.fly1991.com

symfony4服务容器组件的使用

Symfony服务

点击阅读原文

Symfony服务

2月8日 发布,来源:www.fly1991.com

服务容器

你的应用程序中充满了有用的对象:“Mailer”对象可能会帮助你发送电子邮件,而另一个对象可能会帮助你将事情保存到数据库。 几乎所有的应用程序“做”实际上是由这些对象之一完成的。 而且每次你安装一个新的软件包,你都可以访问更多!

在Symfony中,这些有用的对象被称为服务,每个服务都在一个非常特殊的对象(称为服务容器)中。 容器允许你把对象的构建集中。 它使你的工作更轻松,结构更强大和更快!

获取和使用服务¶

当你启动一个Symfony应用程序的时候,你的容器已经包含了许多服务。 这些就像工具:等着你利用它们。 在你的控制器中,你可以通过键入与服务的类或接口名称的参数来“请求”来自容器的服务。

// src/Controller/ProductController.php
// ...

use Psr\Log\LoggerInterface;

/**
 * @Route("/products")
 */
public function list(LoggerInterface $logger)
{
    $logger->info('Look! I just used a service');

    // ...
}

还有什么其他的服务? 通过运行找出:

 php bin/console debug:autowiring
类/接口类型 别名服务ID
Psr\Cache\CacheItemPoolInterface alias for "cache.app.recorder"
Psr\Log\LoggerInterface alias for "monolog.logger"
Symfony\Component\EventDispatcher\EventDispatcherInterface alias for "debug.event_dispatcher"
Symfony\Component\HttpFoundation\RequestStack alias for "request_stack"
Symfony\Component\HttpFoundation\Session\SessionInterface alias for "session"
Symfony\Component\Routing\RouterInterface alias for "router.default"

当在控制器方法或自己的服务中使用这些类型提示时,Symfony会自动向你传递与该类型匹配的服务对象。

在整个文档中,你将看到如何使用容器中存在的许多不同的服务。

容器中实际上有更多的服务,每个服务在容器中都有一个唯一的ID,如session或router.default。 对于完整的列表,你可以运行

php bin / console debug:container。 

但是大多数情况下,不需要担心这一点。

在容器中创建/配置服务¶

您也可以将自己的代码组织到服务中。 例如,假设你需要向用户展示一个随机的,愉快的信息。 如果你将此代码放入你的控制器中,则无法重新使用。 相反,你可以创建一个新的类:

// src/Service/MessageGenerator.php
namespace App\Service;

class MessageGenerator
{
    public function getHappyMessage()
    {
        $messages = [
            'You did it! You updated the system! Amazing!',
            'That was one of the coolest updates I\'ve seen all day!',
            'Great work! Keep going!',
        ];

        $index = array_rand($messages);

        return $messages[$index];
    }
}

创建了你的第一个服务类! 你可以在控制器中立即使用它:

use App\Service\MessageGenerator;

public function new(MessageGenerator $messageGenerator)
{
    // thanks to the type-hint, the container will instantiate a
    // new MessageGenerator and pass it to you!
    // ...

    $message = $messageGenerator->getHappyMessage();
    $this->addFlash('success', $message);
    // ...
}

当你要求MessageGenerator服务时,该容器将构造一个新的MessageGenerator对象并将其返回。 如果你不请求这项服务,就不会建立,可以节省内存和提高速度。 MessageGenerator服务仅创建一次:每次请求时都会返回同一个实例。

services.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}'

    # ...

资源和排除选项的值可以是任何有效的glob模式。

得益于这种配置,你可以自动将src/目录中的任何类用作服务,而无需手动配置它。 稍后,你将了解更多通过资源一次性导入许多服务的信息。

如果你希望手动连接你的服务,可以显式配置服务和参数。

将服务/配置注入服务¶

如果你需要从MessageGenerator内部访问记录器服务,使用具有LoggerInterface类型提示的$logger参数创建一个__construct()方法。 将其设置为新的$ logger属性,稍后使用它:

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

use Psr\Log\LoggerInterface;

class MessageGenerator
{
    private $logger;

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

    public function getHappyMessage()
    {
        $this->logger->info('About to find a happy message!');
        // ...
    }
}

实例化MessageGenerator时,容器将知道传递记录器服务。 并使用自动装配。 Key是__construct()方法中的LoggerInterface的类型,需要配置services.yaml中的autowire:true。 当键入提示参数时,容器将自动查找匹配的服务。 如果不能,你会看到一个明确的异常提示,并提供有用的建议。

这种将依赖关系添加到__construct()方法的方法称为依赖注入。

怎么知道使用LoggerInterface的类型提示呢? 可以阅读所使用的任何功能的文档,也可以运行以下命令获取自动发布的类型提示列表:

php bin/console debug:autowiring

处理多个服务¶

假设呢每次进行网站更新时还想通过电子邮件发送给网站管理员。 要做到这一点,你创建一个新的类:

// src/Updates/SiteUpdateManager.php
namespace App\Updates;

use App\Service\MessageGenerator;

class SiteUpdateManager
{
    private $messageGenerator;
    private $mailer;

    public function __construct(MessageGenerator $messageGenerator, \Swift_Mailer $mailer)
    {
        $this->messageGenerator = $messageGenerator;
        $this->mailer = $mailer;
    }

    public function notifyOfSiteUpdate()
    {
        $happyMessage = $this->messageGenerator->getHappyMessage();

        $message = \Swift_Message::newInstance()
            ->setSubject('Site update just happened!')
            ->setFrom('admin@example.com')
            ->setTo('manager@example.com')
            ->addPart(
                'Someone just updated the site. We told them: '.$happyMessage
            );

        return $this->mailer->send($message) > 0;
    }
}

这需要MessageGenerator和Swift_Mailer服务。 实际上,这个新服务已经可以使用了。 例如,在控制器中,可以键入提示新的SiteUpdateManager类并使用它:

// src/Controller/SiteController.php

// ...
use App\Updates\SiteUpdateManager;

public function new(SiteUpdateManager $siteUpdateManager)
{
    // ...

    if ($siteUpdateManager->notifyOfSiteUpdate()) {
        $this->addFlash('success', 'Notification mail was sent successfully.');
    }

    // ...
}

由于自动装配和__construct()中的类型提示,容器创建了SiteUpdateManager对象并将正确的参数传递给它。 在大多数情况下,这是完美的。

手动布置参数¶

有一些情况下,一个服务的参数不能被自动装配。 例如,假设你要使管理员电子邮件可配置:

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

class SiteUpdateManager
{
    // ...
+    private $adminEmail;

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

    public function notifyOfSiteUpdate()
    {
        // ...

        $message = \Swift_Message::newInstance()
            // ...
-            ->setTo('manager@example.com')
+            ->setTo($this->adminEmail)
            // ...
        ;
        // ...
    }
}

如果你进行此更改并刷新,则会看到一个错误:

无法自动装载服务“App\Updates\SiteUpdateManager”:方法“__construct()”的参数“$adminEmail”必须具有类型提示或显式给定值。

容器不知道你想在这里传递什么样的值。 在你的配置中,你可以明确地设置这个参数:

# 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'

由此,容器将在创建SiteUpdateManager服务时将manager@example.com传递给__construct的$adminEmail参数。 其他的参数仍然是自动装配的。

如果将$adminEmail参数重命名为其他内容 - 例如 $mainEmail - 当你重新加载下一个页面(即使这个页面没有使用这个服务),你会得到一个明确的异常。

服务参数¶

除了保存服务对象之外,容器还包含称为参数的配置。 要创建参数,请将其添加到参数键下,并使用%parameter_name%语法引用它:

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

services:
    # ...

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

实际上,一旦你定义了一个参数,就可以通过任何其他配置文件中的%parameter_name%语法来引用它。 许多参数在config /services.yaml文件中定义。

然后你可以在服务中获取参数:

class SiteUpdateManager
{
    // ...

    private $adminEmail;

    public function __construct($adminEmail)
    {
        $this->adminEmail = $adminEmail;
    }
}

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

public function new()
{
    // ...

    // this ONLY works if you extend the base Controller
    $adminEmail = $this->container->getParameter('admin_email');

    // or a shorter way!
    // $adminEmail = $this->getParameter('admin_email');
}

更多可以参考参数

选择特定服务¶

如果之前创建的MessageGenerator服务需要一个LoggerInterface参数:

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

use Psr\Log\LoggerInterface;

class MessageGenerator
{
    private $logger;

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

但是,实现LoggerInterface的容器中有多个服务,例如logger,monolog.logger.request,monolog.logger.php等。容器如何知道使用哪一个?

在这些情况下,容器通常被配置为在这种情况下自动选择其中一种服务 - 记录器(详细了解使用别名启用自动装配的原因)。 但是,你可以控制这个,并通过一个不同的记录器:

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

    # explicitly configure the service
    App\Service\MessageGenerator:
        arguments:
            $logger: '@monolog.logger.request'

这告诉容器,__construct的$logger参数应该使用id为monolog.logger.request的服务。

有关容器中所有可能服务的完整列表,请运行:

php bin/console debug:container --show-private

@符号很重要:这就是告诉容器你想传递的服务的id是monolog.logger.request,而不是字符串monolog.logger.request。

按名称或类型绑定参数¶

你还可以使用bind关键字按名称或类型绑定特定的参数:

# config/services.yaml
services:
    _defaults:
        bind:
            # pass this value to any $adminEmail argument for any service
            # that's defined in this file (including controller arguments)
            $adminEmail: 'manager@example.com'

            # pass this service for any LoggerInterface type-hint for any
            # service that's defined in this file
            Psr\Log\LoggerInterface: '@monolog.logger.request'

    # ...

通过在_defaults下面放置绑定键,你可以为在这个文件中定义的任何服务指定任何参数的值! 可以通过名称(例如$adminEmail)或类型(例如Psr\Log\LoggerInterface)绑定参数。

绑定配置也可以应用于特定服务或一次加载多个服务(即一次性使用资源导入多个服务)。

autowire选项¶

在上面,services.yaml文件在_defaults部分具有autowire:true,所以它适用于在该文件中定义的所有服务。 使用此设置,你可以在服务的__construct()方法中键入提示参数,容器将自动向你传递正确的参数。 这整个条目都是围绕自动装配写的。

有关自动装配的更多详细信息,请查看自动定义服务依赖关系(自动装配)

自动配置选项¶

在上面,services.yaml文件在_defaults部分有autoconfigure:true,所以它适用于在该文件中定义的所有服务。 使用此设置,容器将根据您的服务的类自动将某些配置应用于您的服务。 这主要用于自动标记你的服务。

例如,要创建一个Twig扩展名,你需要创建一个类,将其注册为服务,并使用twig.extension对其进行标记。

但是,使用自动配置:true,则不需要标签。 事实上,如果你使用默认的services.yaml配置,你不需要做任何事情:服务将被自动加载。 然后,自动配置会为你添加twig.extension标签,因为你的类实现了Twig_ExtensionInterface。 感谢autowire,你甚至可以添加构造参数而不需要任何配置。

公共与私人服务¶

由于services.yaml中的_defaults部分,此文件中定义的每个服务都是public:false。

这是什么意思? 当一个服务是公共的时候,你可以直接从容器对象访问它,可以从任何扩展Controller的控制器访问:

use App\Service\MessageGenerator;

// ...
public function new()
{
    // there IS a public "logger" service in the container
    $logger = $this->container->get('logger');

    // this will NOT work: MessageGenerator is a private service
    $generator = $this->container->get(MessageGenerator::class);
}

作为最佳做法,你只应创建将自动发生的私人服务。 此外,你不应该使用$container-> get()方法来获取公共服务。

但是,如果你确实需要公开服务,请覆盖公开设置:

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

    # explicitly configure the service
    App\Service\MessageGenerator:
        public: true

用资源一次导入多个服务¶

你已经看到,你可以使用资源密钥一次导入多个服务。 例如,默认的Symfony配置包含这个:

# 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}'

资源和排除选项的值可以是任何有效的glob模式。

这可以用来快速将许多类作为服务提供并应用一些默认配置。 每个服务的id是完全合格的类名。 你可以覆盖通过使用其下面的id(类名称)导入的任何服务(例如,上面提到的手动配置参数)。 如果覆盖一个服务,则没有任何选项(例如public)从导入继承(但覆盖的服务仍然继承自_defaults)。

你也可以排除某些路径。 这是可选的,但会稍微提高开发环境中的性能:排除的路径不被跟踪,因此修改它们不会导致容器被重建。

等等,这是否意味着src/中的每个类都被注册为服务? 即使是模型类? 其实没有 只要你在_defaults键下有public:false(或者你可以在特定的导入下添加它),所有导入的服务都是私有的。 由于这个原因,src/中没有明确用作服务的所有类都被自动从最终的容器中移除。 实际上,导入意味着所有的类都可以“用作服务”而不需要手动配置。

显式配置服务和参数¶

在Symfony 3.3之前,所有的服务和(通常)参数都被明确地配置了:不可能自动加载服务,自动装配也不那么常见。

这两个功能都是可选的。 即使你使用它们,也可能会出现一些你想手动连接服务的情况。 例如,假设你要为SiteUpdateManager类注册2个服务 - 每个服务都使用不同的管理员电子邮件。 在这种情况下,每个需要有一个唯一的服务ID:

# 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'

在这种情况下,注册两个服务:site_update_manager.superadmin和site_update_manager.normal_users。 多亏了别名,如果你键入提示SiteUpdateManager第一个(site_update_manager.superadmin)将被传递。 如果你想通过第二个,你需要手动连接服务。

如果你不创建别名并从src/加载所有服务,则创建了三个服务(自动服务+你的两个服务),并且当您键入SiteUpdateManager时,自动加载的服务将被默认传递。 这就是为什么创建别名是一个好主意。

38 浏览 收藏 报告 阅读模式
Flybeta · 3 天前

不错!!!

回复

载入中...