서비스 컨테이너


현대 PHP 프로그램은 모두 객체입니다. 한 개체는 이메일 전송을 담당할 수 있고, 다른 개체는 데이터베이스에 정보를 유지하는 데 사용됩니다. 프로그램에서 개체를 생성하여 제품 재고를 관리하거나 다른 개체를 사용하여 타사 API의 데이터를 처리할 수 있습니다. 결론은 현대 프로그램은 많은 일을 할 수 있고 프로그램은 자신의 작업을 처리하기 위해 함께 구성된 많은 개체로 구성된다는 것입니다.

이 장에서는 프로그램에서 많은 개체를 인스턴스화, 구성 및 검색하는 데 도움이 되는 Symfony의 특수 PHP 개체에 대해 설명합니다. "서비스 컨테이너"라고 불리는 이 개체를 사용하면 프로그램의 개체 구성을 표준화하고 중앙 집중화할 수 있습니다. 컨테이너는 작업을 단순하게 만들고 매우 빠르며 결합을 줄이면서 코드 재사용성을 구조적으로 향상시키는 데 중점을 둡니다. 모든 Symfony 클래스는 컨테이너를 사용하므로 Symfony에서 객체를 확장, 구성 및 사용하는 방법을 배우게 됩니다. 큰 관점에서 볼 때 서비스 컨테이너는 Symfony의 속도와 확장성에 가장 큰 기여를 합니다.

마지막으로 서비스 컨테이너를 구성하고 사용하는 것은 간단합니다. 이 장을 공부한 후에는 서비스 컨테이너를 통해 자신만의 객체를 쉽게 생성할 수 있고 타사 번들의 객체를 사용자 정의할 수도 있습니다. 서비스 컨테이너를 사용하면 좋은 코드를 쉽게 작성할 수 있으므로 재사용 가능하고 테스트 가능하며 느슨하게 결합된 코드를 작성하기 시작할 것입니다.

이 장을 읽은 후 더 자세히 알고 싶다면 종속성 주입 구성 요소를 참조하세요.

서비스란 무엇입니까

간단히 말해서, 서비스(서비스)는 "전역" 작업을 수행하는 모든 개체가 될 수 있습니다. 이는 "특정 작업(예: 이메일 보내기)을 완료하기 위해" 생성된 개체를 설명하는 데 사용되는 컴퓨터 과학의 고유 명사입니다. 프로그램에서는 서비스에서 제공하는 특정 기능이 필요할 때 언제든지 해당 서비스에 접근할 수 있습니다. 서비스를 생성할 때 특별한 작업을 수행할 필요는 없습니다. 작업을 수행하는 PHP 클래스만 작성하면 됩니다. 축하합니다. 서비스를 만들었습니다!

원칙적으로 PHP 개체가 서비스가 되려면 프로그램의 전역 범위에서 사용할 수 있어야 합니다. 독립적인 Mailer 服务被“全局”用于发送邮件信息,然而它所传输的这些 Message 信息对象并是服务。类似的,一个 Product 개체는 서비스가 아니지만 제품을 데이터베이스에 유지할 수 있는 개체는 서비스입니다.

이게 무슨 뜻인가요? "서비스" 관점에서 문제를 생각하는 것의 장점은 이미 프로그램의 각 기능을 일련의 서비스로 분리하기를 원한다는 것입니다. 각 서비스는 한 가지 작업만 수행하므로 필요할 때마다 쉽게 서비스에 액세스할 수 있습니다. 각 서비스는 애플리케이션의 다른 기능과 분리되어 있으므로 테스트 및 구성이 더 쉬워집니다. 이 아이디어는 서비스 지향 아키텍처라고 하며 Symfony 또는 PHP에만 국한되지 않습니다. 일련의 독립적인 서비스 클래스를 통해 프로그램을 "구조화"하는 것은 객체 지향 프로그래밍에서 오랜 시간 테스트를 거쳐 잘 알려진 모범 사례입니다. 이 기술은 어떤 언어에서든 좋은 개발자가 되는 열쇠입니다.

서비스 컨테이너란 무엇인가요?

서비스 컨테이너(서비스 컨테이너/종속성 주입 컨테이너)는 서비스(예: 개체)의 인스턴스화를 관리하는 PHP 개체입니다.

예를 들어 이메일 메시지를 보내는 간단한 클래스가 있다고 가정해 보겠습니다. 서비스 컨테이너가 없으면 필요할 때 수동으로 개체를 만들어야 합니다.

use Acme\HelloBundle\Mailer;
 $mailer = new Mailer('sendmail');
$mailer->send('ryan@example.com', ...);

쉽습니다. 메일 전송 방법(예: sendmail 또는 smtp 등)을 구성할 수 있는 가상의 Mailer 메일 서비스 클래스입니다. 하지만 다른 곳에서 메일 서비스를 사용하고 싶다면 어떻게 해야 할까요? 매번 메일러 개체를 재구성하고 싶지는 않을 것입니다. 메일이 전송되는 방식을 변경하고, 전체 프로그램의 sendmail을 모두 smtp로 변경하고 싶다면 어떻게 해야 할까요? 메일러가 생성된 모든 위치를 찾아 수동으로 업데이트해야 합니다. Mailer 邮件服务类,允许你配置邮件的发送方法(比如 sendmail ,或 smtp ,等等)。但如果你想在其他地方使用邮件服务怎么办?你当然不希望每次都重新配置 Mailer 对象。如果你想改变邮件的传输方式,把整个程序中所有的 sendmail 改成 smtp 怎么办?你不得不找到所有创建了 Mailer 的地方,手动去更新。

在容器中创建和配置服务 

上面问题的最佳答案,是让服务容器来为你创建Mailer对象。为了让容器正常工作,你先要教会它如何创建Mailer服务。这是通过配置来实现的,配置方式有YAML,XML或PHP:

PHP:// app/config/services.phpuse Symfony\Component\DependencyInjection\Definition; $container->setDefinition('app.mailer', new Definition(
    'AppBundle\Mailer',
    array('sendmail')));
XML:<!-- app/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.mailer" class="AppBundle\Mailer">
            <argument>sendmail</argument>
        </service>
    </services></container>
YAML:# app/config/services.ymlservices:
    app.mailer:
        class:        AppBundle\Mailer
        arguments:    [sendmail]

当Symfony初始化时,它要根据配置信息来建立服务容器(默认配置文件是 app/config/config.yml )。被加载的正确配置文件是由 AppKernel::registerContainerConfiguration() 方法指示,该方法加载了一个“特定环境”的配置文件(如config_dev.yml是dev开发环境的,而 config_prod.yml

컨테이너
🎜에서 서비스 생성 및 구성 위 질문에 대한 가장 좋은 대답은 서비스 컨테이너가 메일러 개체를 생성하도록 하는 것입니다. 너. 컨테이너가 제대로 작동하려면 먼저 메일러 서비스를 만드는 방법을 가르쳐야 합니다. 이는 YAML, XML 또는 PHP를 사용하는 구성을 통해 달성됩니다: 🎜
class HelloController extends Controller{
    // ...     public function sendEmailAction()
    {
        // ...
        $mailer = $this->get('app.mailer');
        $mailer->send('ryan@foobar.net', ...);
    }}
PHP:// app/config/services.phpuse Symfony\Component\DependencyInjection\Definition; $container->setParameter('app.mailer.transport', 'sendmail'); $container->setDefinition('app.mailer', new Definition(
    'AppBundle\Mailer',
    array('%app.mailer.transport%')));
XML:<!-- app/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="app.mailer.transport">sendmail</parameter>
    </parameters>     <services>
        <service id="app.mailer" class="AppBundle\Mailer">
            <argument>%app.mailer.transport%</argument>
        </service>
    </services></container>
🎜
🎜Symfony가 초기화되면 구성 정보를 기반으로 서비스 컨테이너가 생성됩니다(기본 구성 파일은 app/config/config.yml). 로드할 올바른 구성 파일은 "특정 환경" 구성 파일(예: config_dev.yml은 개발 개발 환경용이고 AppKernel::registerContainerConfiguration() 메서드에 의해 표시됩니다. code >config_prod.yml는 프로덕션 환경용입니다. 🎜🎜🎜

이제 서비스 컨테이너를 통해 AcmeHelloBundleMailer 개체의 인스턴스를 사용할 수 있습니다. 컨테이너는 get() 단축 메소드를 통해 표준 Symfony 컨트롤러에서 직접 얻을 수 있습니다. AcmeHelloBundleMailer 对象的实例已经可以通过服务容器来使用了。容器在任何一个标准的Symfony控制器中可以通过get()快捷方法直接获得。

YAML:# app/config/services.ymlparameters:
    app.mailer.transport:  sendmailservices:
    app.mailer:
        class:        AppBundle\Mailer
        arguments:    ['%app.mailer.transport%']

当你从容器中请求 app.mailer 服务时,容器构造了该对象并返回(实例化之后的)它。这是使用服务容器的又一个好处。即,一个服务不会被构造(constructed),除非在需要时。如果你定义了一个服务,但在请求(request)过程中从未用到,该服务不会被创建。这可节省内存并提高程序运行速度。这也意味着在定义大量服务时,很少会对性能有冲击。从不使用的服务绝对不会被构造。

附带一点,Mailer服务只被创建一次,每次你请求它时返回的是同一实例。这可满足你的大多数需求(该行为灵活而强大),但是后面你要了解怎样才能配置一个拥有多个实例的服务,参考 如何定义非共享服务 一文。

本例中,控制器继承了Symfony的Controller基类,给了你一个使用服务容器的机会,通过get()方法就可从容器中找到并取出 app.mailer 服务。

服务参数 

通过容器建立新服务的过程十分简单明了。参数(Parameter)可以令服务的定义更加灵活、有序:

# app/config/parameters.ymlparameters:    # This will be parsed as string '@securepass'
    mailer_password: '@@securepass'
1
<argument type="string">http://symfony.com/?foo=%%s&bar=%%d</argument>


结果就和之前一样。区别在于,你是如何定义的服务。通过把 app.mailer.transport% 百分号给括起来,容器就知道应该去找对应这个名字的参数。容器自身生成时,会把每个参数的值,还原到服务定义中。

如果你要把一个由 @ 开头的字符串,在YAML文件中用做参数值的话(例如一个非常安全的邮件密码),你需要添加另一个 @

// src/Acme/HelloBundle/Newsletter/NewsletterManager.phpnamespace Acme\HelloBundle\Newsletter; use Acme\HelloBundle\Mailer; class NewsletterManager{
    protected $mailer;     public function __construct(Mailer $mailer)
    {
        $this->mailer = $mailer;
    }     // ...}

컨테이너에서 app.mailer 서비스를 요청하면 컨테이너가 객체를 구성하고 이를 반환합니다(인스턴스화 후). 이는 서비스 컨테이너 사용의 또 다른 이점입니다. 즉, 필요한 경우가 아니면 서비스가 구성되지 않습니다. 서비스를 정의했지만 요청 중에 사용되지 않은 경우 서비스가 생성되지 않습니다. 이렇게 하면 메모리가 절약되고 프로그램이 더 빠르게 실행됩니다. 이는 또한 많은 수의 서비스를 정의할 때 성능 저하가 거의 발생하지 않음을 의미합니다. 한번도 사용되지 않는 서비스는 절대로 구축되지 않습니다.
그런데 메일러 서비스는 한 번만 생성되며 요청할 때마다 동일한 인스턴스가 반환됩니다. 이는 대부분의 요구 사항을 충족해야 하지만(동작은 유연하고 강력함) 나중에 여러 인스턴스로 서비스를 구성하는 방법을 배워야 합니다. 을 참조하세요. 비공유 서비스를 정의하는 방법 문서.

이 예에서 컨트롤러는 Symfony의 Controller 기본 클래스를 상속하여 서비스 컨테이너를 사용할 수 있는 기회를 제공합니다. get() 메소드를 통해 컨테이너에서 app.mailer를 찾아 제거할 수 있습니다. 서비스.

서비스 매개변수

컨테이너를 통해 새로운 서비스를 생성하는 과정은 매우 간단합니다. 그리고 간단합니다. 매개변수는 서비스 정의를 더욱 유연하고 질서 있게 만들 수 있습니다.
use Acme\HelloBundle\Newsletter\NewsletterManager; // ... public function sendNewsletterAction(){
    $mailer = $this->get('app.mailer');
    $newsletter = new NewsletterManager($mailer);
    // ...}
PHP:// app/config/services.phpuse Symfony\Component\DependencyInjection\Definition;use Symfony\Component\DependencyInjection\Reference; $container->setDefinition('app.mailer', ...); $container->setDefinition('app.newsletter_manager', new Definition(
    'AppBundle\Newsletter\NewsletterManager',
    array(new Reference('app.mailer'))));
XML:<!-- app/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.mailer">
        <!-- ... -->
        </service>         <service id="app.newsletter_manager" class="AppBundle\Newsletter\NewsletterManager">
            <argument type="service" id="app.mailer"/>
        </service>
    </services></container>
YAML:# app/config/services.ymlservices:
    app.mailer:        # ...
    app.newsletter_manager:
        class:     AppBundle\Newsletter\NewsletterManager
        arguments: ['@app.mailer']

결과는 이전과 동일합니다. 차이점은 서비스를 정의하는 방법에 있습니다. app.mailer.transport% 퍼센트 기호로 둘러싸면 컨테이너는 해당 이름에 해당하는 매개변수를 찾는다는 것을 알게 됩니다. 컨테이너 자체가 생성되면 각 매개변수의 값이 서비스 정의로 복원됩니다.
@로 시작하는 문자열을 YAML 파일의 매개변수 값으로 사용하려면(예: 매우 안전한 이메일 비밀번호) 다른 @ 이스케이프 기호(이 상황은 YAML 형식 구성 파일에만 적용 가능)
🎜🎜구성 매개변수(매개변수) 또는 메소드 매개변수(인수)의 백분율 기호도 사용해야 합니다. 이스케이프할 또 다른 %: 🎜 🎜🎜🎜🎜🎜🎜🎜
namespace AppBundle\Newsletter; use AppBundle\Mailer; class NewsletterManager{
    protected $mailer;     public function setMailer(Mailer $mailer)
    {
        $this->mailer = $mailer;
    }     // ...}
🎜🎜🎜🎜
PHP:// app/config/services.phpuse Symfony\Component\DependencyInjection\Definition;use Symfony\Component\DependencyInjection\Reference; $container->setDefinition('app.mailer', ...); $container->setDefinition('app.newsletter_manager', new Definition(
    'AppBundle\Newsletter\NewsletterManager'))->addMethodCall('setMailer', array(
    new Reference('app.mailer'),));
🎜🎜🎜🎜🎜

매개변수의 목적은 서비스에 정보를 전달하는 것입니다. 물론 매개변수가 없으면 문제가 없습니다. 그러나 매개변수를 사용하면 다음과 같은 이점이 있습니다.

  • 통합된 "매개변수" 키에서 모든 서비스 "옵션"을 분리하고 구성합니다.

  • 매개변수 값은 여러 서비스 정의에서 사용할 수 있습니다.

  • 서비스 생성 시 번들에서 매개변수를 사용하면 글로벌 프로그램에서 서비스를 쉽게 사용자 정의할 수 있습니다

매개변수 사용 여부는 귀하의 선택입니다. 고품질 타사 번들은 컨테이너에 저장된 서비스를 보다 "구성 가능"하게 만들기 위해 항상 매개변수를 사용합니다. 물론 매개변수가 제공하는 유연성이 필요하지 않을 수도 있습니다.

배열 매개변수

매개변수에는 배열이 포함될 수 있습니다. 배열 매개변수(배열 매개변수)를 참조하세요.

서비스 참조(주입)

이 시점에서 원래 app.mailer 서비스는 간단합니다. 생성자에 매개변수가 하나만 있으므로 구성하기 쉽습니다. 하나 이상의 컨테이너에서 다른 서비스에 의존하는 서비스를 만들면 컨테이너의 진정한 힘이 나타나기 시작할 것으로 예상할 수 있습니다. app.mailer 服务是简单的:它在构造器中只有一个参数,因此很容易配置。你可以预见到,在你创建一个需要依赖一个或多个容器中的其他服务时,容器的真正威力开始体现出来。

例如,你有一个新的服务, NewsletterManager ,它帮助你管理和发送邮件信息到地址集。 app.mailer 服务已经可以发邮件了,因此你可以把它用在 NewsletterManager 中来负责信息传送的部分。这个类看上去像下面这样:

XML:<!-- app/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.mailer">
        <!-- ... -->
        </service>         <service id="app.newsletter_manager" class="AppBundle\Newsletter\NewsletterManager">
            <call method="setMailer">
                <argument type="service" id="app.mailer" />
            </call>
        </service>
    </services></container>

如果不使用服务容器,你可以在controller中很容易地创建一个NewsletterManager:

YAML:# app/config/services.ymlservices:
    app.mailer:        # ...
    app.newsletter_manager:
        class:     AppBundle\Newsletter\NewsletterManager
        calls:            - [setMailer, ['@app.mailer']]

这样去实现是可以的,但当你以后要对 NewsletterManager 类增加第二或第三个构造器参数时怎么办?如果你决定重构代码并且重命名这个类时怎么办?这两种情况,你都需要找到每一个 NewsletterManager
예를 들어, 이메일 메시지를 관리하고 여러 주소로 보내는 데 도움이 되는 NewsletterManager라는 새로운 서비스가 있습니다. app.mailer 서비스는 이미 이메일을 보낼 수 있으므로 NewsletterManager에서 이를 사용하여 메시지 전달 부분을 처리할 수 있습니다. 이 클래스는 다음과 같습니다.

rrreee
서비스 컨테이너를 사용하지 않는 경우 컨트롤러에서 NewsletterManager를 쉽게 만들 수 있습니다.
🎜rrreee🎜이런 방식으로 구현해도 괜찮지만 를 사용하려는 경우 NewsletterManager 나중 클래스가 두 번째 또는 세 번째 생성자 매개변수를 추가하면 어떻게 해야 합니까? 코드를 리팩터링하고 이 클래스의 이름을 바꾸기로 결정했다면 어떻게 될까요? 두 경우 모두 NewsletterManager 클래스가 인스턴스화되는 각 위치를 찾은 다음 수동으로 사용자 정의해야 합니다. 서비스 컨테이너가 더 매력적인 처리 방법을 제공한다는 것은 의심의 여지가 없습니다. 🎜🎜rrreeerrreee🎜rrreee🎜

YAML에서 특수 @app.mailer 구문은 컨테이너에게 app.mailer라는 서비스를 찾은 다음 이 개체를 NewsletterManager 에 전달하도록 지시합니다. 의 생성자 매개변수입니다. 이 예에서는 지정된 app.mailer 서비스가 존재합니다. 존재하지 않는 경우 예외가 발생합니다. 그러나 종속성을 선택 사항으로 표시할 수 있습니다. 이 주제는 다음 섹션에서 설명합니다. @app.mailer 语法,告诉容器去寻找一个名为 app.mailer 的服务,然后把这个对象传给 NewsletterManager 的构造器参数。本例中,指定的 app.mailer 服务是确实存在的。如果它不存在,则异常会抛出。不过你可以标记依赖可选 – 这个话题将在下一小节中讨论。

(对服务的)引用是个强大的工具,它允许你创建独立的服务,却拥有准确定义的依赖关系。在这个例子中,  app.newsletter_manager  服务为了实现功能,需要依赖  app.mailer

(서비스에 대한) 참조는 독립적인 서비스를 만들 수 있지만 잘 정의된 종속성이 있는 강력한 도구입니다. 이 예에서 app.newsletter_manager 서비스는 해당 기능을 구현하기 위해 app.mailer 서비스에 의존해야 합니다. 서비스 컨테이너에서 이 종속성을 정의하면 컨테이너는 이 클래스를 인스턴스화하는 모든 작업을 호스팅합니다.

선택적 종속성: Setter 주입

종속성 개체를 생성자에 삽입하는 것은 종속성을 활용할 수 있는지 확인하는 방법입니다(그렇지 않으면 생성자를 실행할 수 없음). 그러나 클래스의 경우 선택적 종속성이 있는 경우 "setter 주입"이 더 나은 솔루션입니다. 이는 생성자 대신 클래스 메서드를 사용하여 종속성을 주입하는 것을 의미합니다. 클래스는 다음과 같습니다.

rrreee

서비스 정의는 setter 주입에 맞게 조정되어야 합니다.

rrreeerrreee


rrreee

이 섹션에서 구현된 프로세스를 "생성자 주입" 및 "setter 주입"이라고 합니다. . 또한 Symfony의 컨테이너 시스템은 속성 주입도 지원합니다.
🎜