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

Buat dan konfigurasi perkhidmatan dalam bekas
🎜Jawapan terbaik untuk soalan di atas ialah membenarkan bekas perkhidmatan mencipta objek Mailer untuk awak. Untuk membolehkan bekas berfungsi dengan betul, anda perlu mengajarnya cara membuat perkhidmatan Mailer terlebih dahulu. Ini dicapai melalui konfigurasi, menggunakan YAML, XML atau 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>
🎜
🎜Apabila Symfony dimulakan, ia mencipta bekas perkhidmatan berdasarkan maklumat konfigurasi (fail konfigurasi lalai ialah 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.
Sebenarnya, perkhidmatan Mailer hanya dibuat sekali dan contoh yang sama dikembalikan setiap kali anda memintanya. Ini sepatutnya memenuhi kebanyakan keperluan anda (tingkah laku adalah fleksibel dan berkuasa), tetapi kemudian anda perlu belajar cara mengkonfigurasi perkhidmatan dengan berbilang kejadian, lihat Cara untuk mentakrifkan artikel perkhidmatan bukan kongsi.

Dalam contoh ini, pengawal mewarisi kelas asas Pengawal Symfony, memberi anda peluang untuk menggunakan bekas perkhidmatan Anda boleh mencari dan mengalih keluar app.mailer

Parameter Perkhidmatan

Proses mencipta perkhidmatan baharu melalui bekas adalah sangat mudah dan terus terang. Parameter boleh menjadikan definisi perkhidmatan lebih fleksibel dan teratur:
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']

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 @ simbol untuk melarikan diri (keadaan ini hanya terpakai dalam fail konfigurasi format YAML)
🎜🎜Tanda peratus dalam parameter konfigurasi (parameter) atau parameter kaedah (argumen) juga mesti digunakan Satu lagi% untuk melarikan diri: 🎜 🎜🎜🎜🎜🎜🎜🎜
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:

rrreee
Jika anda tidak menggunakan bekas perkhidmatan, anda boleh membuat NewsletterManager dengan mudah dalam pengawal:
🎜rrreee🎜Tidak mengapa untuk melaksanakannya dengan cara ini, tetapi apabila anda ingin menggunakan NewsletterManager kemudian Apa yang perlu dilakukan apabila kelas menambah parameter pembina kedua atau ketiga? Bagaimana jika anda memutuskan untuk memfaktorkan semula kod anda dan menamakan semula kelas ini? Dalam kedua-dua kes, anda perlu mencari setiap tempat di mana kelas 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

Rujukan (kepada perkhidmatan) ialah alat berkuasa yang membolehkan anda membuat perkhidmatan bebas tetapi dengan kebergantungan yang jelas. Dalam contoh ini, perkhidmatan 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

Proses yang dilaksanakan dalam bahagian ini dipanggil "suntikan pembina" dan "suntikan setter" . Selain itu, sistem kontena Symfony juga menyokong suntikan harta benda.
🎜