bekas perkhidmatan
Program PHP moden adalah semua objek. Satu objek boleh bertanggungjawab untuk menghantar e-mel, dan objek lain membolehkan anda mengekalkan maklumat ke pangkalan data. Dalam program ini, anda boleh mencipta objek untuk mengurus inventori produk atau menggunakan objek lain untuk memproses data daripada API pihak ketiga. Kesimpulannya ialah program moden boleh melakukan banyak perkara, dan program terdiri daripada banyak objek yang disusun bersama untuk mengendalikan tugas mereka sendiri.
Bab ini bercakap tentang objek PHP khas dalam Symfony, yang membantu anda membuat instantiate, menyusun dan mendapatkan semula banyak objek dalam program anda. Objek ini, dipanggil "bekas perkhidmatan", membolehkan anda menyeragamkan dan memusatkan organisasi objek dalam program anda. Bekas menjadikan perkara mudah, ia sangat pantas, dan ia menekankan kebolehgunaan semula kod yang meningkatkan seni bina sambil mengurangkan gandingan. Memandangkan semua kelas Symfony menggunakan bekas, anda akan belajar cara melanjutkan, mengkonfigurasi dan menggunakan objek dalam Symfony. Dari perspektif yang besar, bekas perkhidmatan adalah penyumbang terbesar kepada kelajuan dan kebolehskalaan Symfony.
Akhir sekali, mengkonfigurasi dan menggunakan bekas perkhidmatan adalah mudah. Selepas mempelajari bab ini, anda boleh mencipta objek anda sendiri dengan mudah melalui bekas perkhidmatan dan anda juga boleh menyesuaikan sebarang objek dalam himpunan pihak ketiga. Anda akan mula menulis kod yang boleh diguna semula, boleh diuji dan digandingkan secara longgar kerana bekas perkhidmatan memudahkan untuk menulis kod yang baik.
Kalau nak tahu lebih lanjut lepas baca bab ni, sila rujuk Dependency Injection Components.
Apakah perkhidmatan ¶
Ringkasnya, perkhidmatan (Perkhidmatan) boleh menjadi sebarang objek yang melaksanakan tugas "global". Ia adalah kata nama khas dalam sains komputer, digunakan untuk menerangkan objek yang dicipta "untuk menyelesaikan tugas tertentu (seperti menghantar e-mel)." Dalam program, apabila anda memerlukan fungsi khusus yang disediakan oleh perkhidmatan, anda boleh mengakses perkhidmatan pada bila-bila masa. Anda tidak perlu melakukan sesuatu yang istimewa semasa membuat perkhidmatan: hanya tulis kelas PHP yang melaksanakan tugas. Tahniah, anda telah mencipta perkhidmatan!
Sebagai prinsip, jika objek PHP ingin menjadi perkhidmatan, ia mesti tersedia dalam skop global program. Objek Mailer
服务被“全局”用于发送邮件信息,然而它所传输的这些 Message
信息对象并不是服务。类似的,一个 Product
bebas bukan perkhidmatan, tetapi objek yang boleh mengekalkan produk ke dalam pangkalan data ialah perkhidmatan.
Apakah maksudnya? Kelebihan memikirkan masalah dari perspektif "perkhidmatan" ialah anda sudah mahu memisahkan setiap fungsi dalam program ke dalam satu siri perkhidmatan. Memandangkan setiap perkhidmatan hanya melakukan satu perkara, anda boleh mengakses perkhidmatan dengan mudah pada bila-bila masa anda memerlukannya. Menguji dan mengkonfigurasi setiap perkhidmatan menjadi lebih mudah kerana ia dipisahkan daripada fungsi lain dalam aplikasi anda. Idea ini dipanggil Seni Bina Berorientasikan Perkhidmatan dan tidak eksklusif untuk Symfony atau PHP. "Menstrukturkan" program anda melalui satu set kelas perkhidmatan bebas ialah amalan terbaik yang diuji masa dan terkenal dalam pengaturcaraan berorientasikan objek. Kemahiran ini adalah kunci untuk menjadi pembangun yang baik dalam mana-mana bahasa.
Apakah itu bekas perkhidmatan? ¶
Bekas perkhidmatan (Bekas Perkhidmatan/bekas suntikan kebergantungan) ialah objek PHP yang menguruskan instantiasi perkhidmatan (iaitu objek).
Sebagai contoh, katakan anda mempunyai kelas mudah untuk menghantar mesej e-mel. Tanpa bekas perkhidmatan, anda perlu membuat objek secara manual apabila diperlukan.
use Acme\HelloBundle\Mailer; $mailer = new Mailer('sendmail'); $mailer->send('ryan@example.com', ...);
Mudah sahaja. Kelas perkhidmatan mel Mailer
fiksyen yang membolehkan anda mengkonfigurasi kaedah penghantaran mel (seperti sendmail
, atau smtp
, dsb.). Tetapi bagaimana jika anda ingin menggunakan perkhidmatan mel anda di tempat lain? Anda pastinya tidak mahu mengkonfigurasi semula objek Mailer
setiap kali. Bagaimana jika anda ingin menukar cara penghantaran mel dan menukar semua sendmail
dalam keseluruhan program kepada smtp
? Anda perlu mencari semua tempat di mana Mailer
dibuat dan mengemas kininya secara manual. 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
). Fail konfigurasi yang betul untuk dimuatkan ditunjukkan oleh kaedah AppKernel::registerContainerConfiguration()
, yang memuatkan fail konfigurasi "persekitaran khusus" (seperti config_dev.yml adalah untuk persekitaran pembangunan dev dan < kod >config_prod.yml adalah untuk persekitaran pengeluaran). 🎜🎜🎜Satu contoh objek AcmeHelloBundleMailer
kini tersedia melalui bekas perkhidmatan. Bekas boleh diperolehi terus daripada mana-mana pengawal Symfony standard melalui kaedah pintasan 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; } // ...}Apabila anda meminta perkhidmatan
app.mailer
daripada bekas, bekas itu membina objek dan mengembalikannya (selepas instantiasi). Ini adalah satu lagi faedah menggunakan bekas perkhidmatan. Iaitu, perkhidmatan tidak dibina melainkan diperlukan. Jika anda mentakrifkan perkhidmatan tetapi ia tidak pernah digunakan semasa permintaan, perkhidmatan itu tidak akan dibuat. Ini menjimatkan memori dan menjadikan program berjalan lebih cepat. Ini juga bermakna bahawa prestasi jarang berlaku apabila menentukan sejumlah besar perkhidmatan. Perkhidmatan yang tidak pernah digunakan tidak akan dibina. app.mailer
Hasilnya sama seperti sebelumnya. Perbezaannya terletak pada cara anda menentukan perkhidmatan. Dengan mengelilingi app.mailer.transport dengan % tanda peratus, bekas tahu untuk mencari parameter yang sepadan dengan nama itu. Apabila bekas itu sendiri dijana, nilai setiap parameter akan dipulihkan kepada definisi perkhidmatan. | Jika anda ingin menggunakan rentetan yang bermula dengan @ sebagai nilai parameter dalam fail YAML (seperti kata laluan e-mel yang sangat selamat), anda perlu menambah @ kod> simbol untuk melarikan diri (keadaan ini hanya terpakai dalam fail konfigurasi 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'),));🎜🎜🎜🎜🎜Tujuan parameter
adalah untuk menghantar maklumat kepada perkhidmatan. Sudah tentu, jika tiada parameter, tidak akan ada masalah. Tetapi menggunakan parameter mempunyai faedah berikut:
Asingkan dan susun semua "pilihan" perkhidmatan di bawah kunci "Parameter" bersatu
Nilai parameter boleh digunakan dalam pelbagai definisi perkhidmatan
Apabila membuat perkhidmatan dalam satu himpunan, menggunakan parameter boleh memudahkan untuk menyesuaikan perkhidmatan dalam program global
Pilihan di tangan anda sama ada untuk menggunakan parameter. Himpunan pihak ketiga berkualiti tinggi sentiasa menggunakan parameter kerana mereka mahu menjadikan perkhidmatan yang disimpan dalam bekas lebih "boleh dikonfigurasikan". Sudah tentu, anda mungkin tidak memerlukan fleksibiliti yang dibawa oleh parameter.
Parameter Tatasusunan ¶
Parameter boleh mengandungi tatasusunan, lihat Parameter Tatasusunan(Parameter Tatasusunan).
Merujuk (menyuntik) perkhidmatan ¶
Pada ketika ini, perkhidmatan app.mailer
asal adalah mudah: ia hanya mempunyai satu parameter dalam pembina, jadi ia mudah untuk dikonfigurasikan. Anda boleh menjangkakan bahawa kuasa sebenar bekas mula muncul apabila anda membuat perkhidmatan yang bergantung pada perkhidmatan lain dalam satu atau lebih bekas. 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
Sebagai contoh, anda mempunyai perkhidmatan baharu, NewsletterManager
, yang membantu anda mengurus dan menghantar mesej e-mel ke satu set alamat. Perkhidmatan app.mailer
sudah boleh menghantar e-mel, jadi anda boleh menggunakannya dalam NewsletterManager
untuk menjaga bahagian penghantaran mesej. Kelas ini kelihatan seperti ini:
NewsletterManager
digunakan dan kemudian menyesuaikannya secara manual. Tidak dinafikan bahawa bekas perkhidmatan menyediakan cara pengendalian yang lebih menarik: 🎜🎜rrreeerrreee🎜rrreee🎜Dalam YAML, sintaks @app.mailer
khas memberitahu bekas untuk mencari perkhidmatan bernama app.mailer
dan kemudian hantar objek ini kepada NewsletterManager
parameter pembina. Dalam contoh ini, perkhidmatan app.mailer
yang ditentukan memang wujud. Jika ia tidak wujud, pengecualian dilemparkan. Walau bagaimanapun anda boleh menandakan kebergantungan sebagai pilihan - topik ini dibincangkan dalam bahagian seterusnya. @app.mailer
语法,告诉容器去寻找一个名为 app.mailer
的服务,然后把这个对象传给 NewsletterManager
的构造器参数。本例中,指定的 app.mailer
服务是确实存在的。如果它不存在,则异常会抛出。不过你可以标记依赖可选 – 这个话题将在下一小节中讨论。
(对服务的)引用是个强大的工具,它允许你创建独立的服务,却拥有准确定义的依赖关系。在这个例子中, app.newsletter_manager
服务为了实现功能,需要依赖 app.mailer
app.newsletter_manager
perlu bergantung pada perkhidmatan app.mailer
untuk melaksanakan fungsinya. Apabila anda mentakrifkan kebergantungan ini dalam bekas perkhidmatan, bekas itu mengehoskan semua kerja membuat instantiating kelas ini. Kebergantungan Pilihan: Suntikan Setter ¶
Menyuntik objek kebergantungan ke dalam pembina ialah satu cara untuk memastikan kebergantungan boleh digunakan (jika tidak, pembina tidak boleh dilaksanakan). Tetapi untuk kelas, jika ia mempunyai pergantungan pilihan, maka "suntikan penetap" adalah penyelesaian yang lebih baik. Ini bermakna menggunakan kaedah kelas untuk menyuntik kebergantungan, bukannya pembina. Kelas mungkin kelihatan seperti ini:rrreee
Definisi perkhidmatan perlu diselaraskan dengan sewajarnya untuk suntikan penetap:rrreeerrreee
rrreee