서비스 컨테이너
현대 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
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>🎜
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
서비스를 요청하면 컨테이너가 객체를 구성하고 이를 반환합니다(인스턴스화 후). 이는 서비스 컨테이너 사용의 또 다른 이점입니다. 즉, 필요한 경우가 아니면 서비스가 구성되지 않습니다. 서비스를 정의했지만 요청 중에 사용되지 않은 경우 서비스가 생성되지 않습니다. 이렇게 하면 메모리가 절약되고 프로그램이 더 빠르게 실행됩니다. 이는 또한 많은 수의 서비스를 정의할 때 성능 저하가 거의 발생하지 않음을 의미합니다. 한번도 사용되지 않는 서비스는 절대로 구축되지 않습니다. app.mailer
를 찾아 제거할 수 있습니다. 서비스. 결과는 이전과 동일합니다. 차이점은 서비스를 정의하는 방법에 있습니다. app.mailer.transport 를 % 퍼센트 기호로 둘러싸면 컨테이너는 해당 이름에 해당하는 매개변수를 찾는다는 것을 알게 됩니다. 컨테이너 자체가 생성되면 각 매개변수의 값이 서비스 정의로 복원됩니다. | @ 로 시작하는 문자열을 YAML 파일의 매개변수 값으로 사용하려면(예: 매우 안전한 이메일 비밀번호) 다른 @를 추가해야 합니다. code> 이스케이프 기호(이 상황은 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
에서 이를 사용하여 메시지 전달 부분을 처리할 수 있습니다. 이 클래스는 다음과 같습니다.
를 사용하려는 경우 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