conteneur de service
Les programmes PHP modernes sont tous des objets. Un objet peut être responsable de l'envoi de l'e-mail et un autre objet vous permet de conserver les informations dans la base de données. Dans le programme, vous pouvez créer un objet pour gérer l'inventaire des produits ou utiliser un autre objet pour traiter les données d'une API tierce. La conclusion est que les programmes modernes peuvent faire beaucoup de choses et que les programmes sont composés de nombreux objets organisés ensemble pour gérer leurs propres tâches.
Ce chapitre parle d'un objet PHP spécial dans Symfony, qui vous aide à instancier, organiser et récupérer de nombreux objets dans votre programme. Cet objet, appelé « conteneur de services », permet de standardiser et de centraliser l'organisation des objets dans votre programme. Les conteneurs simplifient les choses, ils sont très rapides et mettent l’accent sur l’amélioration architecturale de la réutilisabilité du code tout en réduisant le couplage. Puisque toutes les classes Symfony utilisent des conteneurs, vous apprendrez comment étendre, configurer et utiliser des objets dans Symfony. D'un point de vue général, les conteneurs de services sont le principal contributeur à la vitesse et à l'évolutivité de Symfony.
Enfin, la configuration et l'utilisation des conteneurs de services sont simples. Après avoir étudié ce chapitre, vous pouvez facilement créer vos propres objets via le conteneur de services, et vous pouvez également personnaliser n'importe quel objet du bundle tiers. Vous commencerez à écrire du code réutilisable, testable et faiblement couplé, car les conteneurs de services facilitent l'écriture de bon code.
Si vous souhaitez en savoir plus après avoir lu ce chapitre, veuillez vous référer à Composants d'injection de dépendances.
Qu'est-ce qu'un service ¶
En termes simples, un service (Service) peut être n'importe quel objet qui effectue des tâches "globales". C'est un nom propre en informatique, utilisé pour décrire un objet créé « pour accomplir une certaine tâche (comme l'envoi d'un e-mail) ». Dans un programme, lorsque vous avez besoin d'une fonction spécifique fournie par un service, vous pouvez accéder à ce service à tout moment. Vous n'avez rien de spécial à faire lors de la création d'un service : écrivez simplement une classe PHP qui exécute une tâche. Félicitations, vous avez créé un service !
En principe, si un objet PHP veut devenir un service, il doit être disponible dans le périmètre global du programme. Un objet Mailer
服务被“全局”用于发送邮件信息,然而它所传输的这些 Message
信息对象并不是服务。类似的,一个 Product
indépendant n'est pas un service, mais un objet qui peut conserver des produits dans la base de données est un service.
Qu'est-ce que cela signifie ? L'avantage de considérer le problème du point de vue du « service » est que vous souhaitez déjà séparer chaque fonction du programme en une série de services. Étant donné que chaque service ne fait qu'une seule chose, vous pouvez facilement y accéder quand vous en avez besoin. Tester et configurer chaque service devient plus facile car ils sont séparés des autres fonctionnalités de votre application. Cette idée s'appelle Architecture Orientée Service et n'est pas exclusive à Symfony ou PHP. « Structurer » votre programme à travers un ensemble de classes de services indépendantes est une bonne pratique éprouvée et bien connue en programmation orientée objet. Cette compétence est la clé pour devenir un bon développeur dans n’importe quelle langue.
Qu'est-ce qu'un conteneur de services ? ¶
Un conteneur de services (Service Container/dependency injection containers) est un objet PHP qui gère l'instanciation de services (c'est-à-dire d'objets).
Par exemple, supposons que vous ayez une classe simple pour envoyer des e-mails. Sans conteneur de services, vous devrez créer manuellement des objets en cas de besoin.
use Acme\HelloBundle\Mailer; $mailer = new Mailer('sendmail'); $mailer->send('ryan@example.com', ...);
C'est facile. Une classe de service de messagerie Mailer
fictive qui vous permet de configurer la méthode d'envoi du courrier (telle que sendmail
, ou smtp
, etc.). Mais que se passe-t-il si vous souhaitez utiliser votre service de messagerie ailleurs ? Vous ne souhaitez certainement pas reconfigurer l'objet Mailer
à chaque fois. Que se passe-t-il si vous souhaitez modifier la façon dont le courrier est transmis et remplacer tous les sendmail
de l'ensemble du programme par smtp
? Vous devez trouver tous les endroits où Mailer
est créé et le mettre à jour manuellement. 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
). Le fichier de configuration correct à charger est indiqué par la méthode AppKernel::registerContainerConfiguration()
, qui charge un fichier de configuration "d'environnement spécifique" (tel que config_dev.yml est destiné à l'environnement de développement de développement, et < code >config_prod.yml est destiné à l'environnement de production). 🎜🎜🎜Une instance de l'objet AcmeHelloBundleMailer
est désormais disponible via le conteneur de services. Les conteneurs peuvent être obtenus directement à partir de n'importe quel contrôleur Symfony standard via la méthode de raccourci get(). 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; } // ...}Lorsque vous demandez le service
app.mailer
au conteneur, le conteneur construit l'objet et le renvoie (après instanciation). C'est un autre avantage de l'utilisation de conteneurs de services. Autrement dit, un service n’est construit que s’il est nécessaire. Si vous définissez un service mais qu'il n'est jamais utilisé lors d'une requête, le service ne sera pas créé. Cela économise de la mémoire et accélère l'exécution des programmes. Cela signifie également que les performances sont rarement affectées lors de la définition d'un grand nombre de services. Les services qui ne sont jamais utilisés ne seront jamais construits. app.mailer
. Services. Le résultat est le même qu'avant. La différence réside dans la façon dont vous définissez le service. En entourant app.mailer.transport de signes de pourcentage % , le conteneur sait rechercher le paramètre correspondant à ce nom. Lorsque le conteneur lui-même est généré, la valeur de chaque paramètre sera restaurée dans la définition du service. | Si vous souhaitez utiliser une chaîne commençant par @ comme valeur de paramètre dans un fichier YAML (comme un mot de passe de messagerie très sécurisé), vous devez ajouter un autre @ code> symbole pour échapper (cette situation n'est applicable que dans les fichiers de configuration au format 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'),));🎜🎜🎜🎜🎜
Le paramètre a pour but de transmettre des informations au service. Bien entendu, s’il n’y a pas de paramètres, il n’y aura aucun problème. Mais l'utilisation des paramètres présente les avantages suivants :
Séparez et organisez toutes les "options" de service sous une clé "paramètre" unifiée
#🎜🎜 #- Les valeurs des paramètres peuvent être utilisées dans plusieurs définitions de services
- Lors de la création d'un service dans un bundle, l'utilisation de paramètres peut créer le service dans le programme global La personnalisation devient facile dans
¶
Les paramètres peuvent contenir des tableaux, voirParamètres du tableau (Paramètres du tableau).
Service de référence (injection)¶
À ce stade, le serviceapp.mailer
d'origine est simple : il Il n'y a qu'un seul paramètre dans le constructeur, il est donc facile à configurer. Vous pouvez vous attendre à ce que la véritable puissance des conteneurs commence à se manifester lorsque vous créez un service qui dépend d'autres services dans un ou plusieurs conteneurs. 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
Par exemple, vous disposez d'un nouveau service, NewsletterManager
, qui vous aide à gérer et à envoyer des e-mails à un ensemble d'adresses. Le service app.mailer
peut déjà envoyer des emails, vous pouvez donc l'utiliser dans NewsletterManager
pour vous occuper de la partie livraison des messages. Cette classe ressemble à ceci :
rrreee
NewsletterManager
? Et si vous décidiez de refactoriser votre code et de renommer cette classe ? Dans les deux cas, vous devrez trouver chaque endroit où la classe NewsletterManager
est instanciée, puis la personnaliser manuellement. Il ne fait aucun doute que les conteneurs de service offrent un mode de traitement plus attractif : #🎜🎜##🎜🎜#rrreeerrreee#🎜🎜#rrreee#🎜🎜#Dans YAML, la syntaxe spéciale @app.mailer
indique au conteneur de trouver un service nommé app.mailer
puis de transmettre cet objet aux Gestionnaire de newsletter. Dans cet exemple, le service app.mailer
spécifié existe. S'il n'existe pas, une exception est levée. Vous pouvez cependant marquer les dépendances comme facultatives – ce sujet est abordé dans la section suivante. @app.mailer
语法,告诉容器去寻找一个名为 app.mailer
的服务,然后把这个对象传给 NewsletterManager
的构造器参数。本例中,指定的 app.mailer
服务是确实存在的。如果它不存在,则异常会抛出。不过你可以标记依赖可选 – 这个话题将在下一小节中讨论。
(对服务的)引用是个强大的工具,它允许你创建独立的服务,却拥有准确定义的依赖关系。在这个例子中, app.newsletter_manager
服务为了实现功能,需要依赖 app.mailer
app.newsletter_manager
doit s'appuyer sur le service app.mailer
afin d'implémenter ses fonctionnalités. Lorsque vous définissez cette dépendance dans le conteneur de services, le conteneur héberge tout le travail d'instanciation de cette classe. Dépendance facultative : injection de setter ¶
L'injection de l'objet de dépendance dans le constructeur est un moyen de garantir que la dépendance peut être utilisée (sinon le constructeur ne peut pas être exécuté). Mais pour une classe, si elle a une dépendance facultative, alors « l’injection setter » est une meilleure solution. Cela signifie utiliser une méthode de classe pour injecter des dépendances, plutôt qu'un constructeur. La classe pourrait ressembler à ceci :rrreee
La définition du service doit être ajustée en conséquence pour l'injection de passeur :rrreeerrreee
rrreee
#🎜🎜 #
Le processus mis en œuvre dans cette section est appelé « injection constructeur » et « injection setter ». De plus, le système de conteneurs de Symfony prend également en charge l'injection de propriétés.