サービスコンテナ


現代の PHP プログラムはすべてオブジェクトです。 1 つのオブジェクトは電子メールの送信を担当し、別のオブジェクトでは情報をデータベースに保存できます。プログラムでは、製品在庫を管理するオブジェクトを作成したり、別のオブジェクトを使用してサードパーティ API からのデータを処理したりできます。結論としては、最新のプログラムは多くのことを実行でき、プログラムは独自のタスクを処理するために一緒に編成された多くのオブジェクトで構成されています。

この章では、プログラム内の多くのオブジェクトをインスタンス化、整理、取得するのに役立つ Symfony の特別な PHP オブジェクトについて説明します。 「サービス コンテナ」と呼ばれるこのオブジェクトを使用すると、プログラム内のオブジェクトの編成を標準化および一元化できます。コンテナーは物事をシンプルにし、非常に高速であり、結合を減らしながらコードの再利用性をアーキテクチャ的に向上させることに重点を置いています。すべての Symfony クラスはコンテナを使用するため、Symfony でオブジェクトを拡張、設定、使用する方法を学びます。大きな観点から見ると、サービスコンテナは Symfony の速度とスケーラビリティに最も大きく貢献しています。

最後に、サービス コンテナの構成と使用は簡単です。この章を学習すると、サービス コンテナを通じて独自のオブジェクトを簡単に作成できるようになり、サードパーティ バンドルのオブジェクトをカスタマイズすることもできます。サービス コンテナーを使用すると優れたコードを簡単に作成できるため、再利用可能でテスト可能な疎結合コードの作成を開始できます。

この章を読んだ後にさらに詳しく知りたい場合は、Dependency Injection コンポーネントを参照してください。

サービスとは

簡単に言えば、サービス (Service) は、「グローバル」タスクを実行する任意のオブジェクトです。これはコンピュータ サイエンスにおける固有名詞で、「特定のタスク (電子メールの送信など) を完了するために」作成されたオブジェクトを表すために使用されます。プログラムでは、サービスが提供する特定の機能が必要なときに、いつでもサービスにアクセスできます。サービスを作成するときに特別なことをする必要はありません。タスクを実行する PHP クラスを記述するだけです。おめでとうございます。サービスが作成されました。

原則として、PHP オブジェクトをサービスにしたい場合は、プログラムのグローバル スコープで使用できる必要があります。独立した Mailer サービスは、電子メール メッセージの送信に「グローバルに」使用されますが、送信される Message 情報オブジェクトは not サービスではありません。同様に、Product オブジェクトはサービスではありませんが、製品をデータベースに永続化できるオブジェクトはサービスです。

これは何を示していますか? 「サービス」の観点から問題を考えることの利点は、プログラム内の各機能を一連のサービスにすでに分離しておくことです。各サービスは 1 つのことだけを実行するため、必要なときにいつでもサービスに簡単にアクセスできます。各サービスはアプリケーション内の他の機能から分離されているため、テストと構成が簡単になります。この概念は サービス指向アーキテクチャ と呼ばれ、Symfony や PHP に限定されたものではありません。一連の独立したサービス クラスを通じてプログラムを「構造化」することは、オブジェクト指向プログラミングにおける長年の実績とよく知られたベスト プラクティスです。このスキルは、どの言語でも優れた開発者になるための鍵です。

サービス コンテナとは何ですか?

サービス コンテナ (サービス コンテナ/依存関係注入コンテナ) は、サービス (オブジェクト) のインスタンス化を管理する PHP オブジェクトです。 。

たとえば、電子メール メッセージを送信するための単純なクラスがあるとします。サービス コンテナーがなければ、必要に応じてオブジェクトを手動で作成する必要があります。

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

簡単です。メール送信方法 (sendmailsmtp など) を構成できるようにする架空の Mailer メール サービス クラス。しかし、メール サービスを別の場所で使用したい場合はどうすればよいでしょうか? Mailer オブジェクトを毎回再構成する必要はありません。メールの送信方法を変更し、プログラム全体のすべての sendmailsmtp に変更する場合はどうすればよいでしょうか? 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 などは開発開発環境用であり、 config_prod.yml は実稼働環境用です)。

Acme\HelloBundle\Mailer オブジェクトのインスタンスは、サービス コンテナを通じてすでに利用可能です。コンテナーは、get() ショートカット メソッドを通じて標準の Symfony コントローラーから直接取得できます。

class HelloController extends Controller{
    // ...     public function sendEmailAction()
    {
        // ...
        $mailer = $this->get('app.mailer');
        $mailer->send('ryan@foobar.net', ...);
    }}

コンテナから

app.mailer サービスをリクエストすると、コンテナはオブジェクトを構築し、それを返します (インスタンス化後)。これは、サービス コンテナーを使用することのもう 1 つの利点です。つまり、必要な場合を除き、サービスは構築されません。サービスを定義しても、リクエスト中にそのサービスが使用されなかった場合、そのサービスは作成されません。これによりメモリが節約され、プログラムの実行が高速になります。これは、多数のサービスを定義してもパフォーマンスが低下することはほとんどないことも意味します。決して使用されないサービスは決して構築されません。

ちなみに、Mailer サービスは一度だけ作成され、リクエストするたびに同じインスタンスが返されます。これでほとんどのニーズが満たされます (動作は柔軟で強力です)。ただし、後ほど、複数のインスタンスを使用してサービスを構成する方法を学習する必要があります。

非共有サービスを定義する方法の記事を参照してください。

この例では、コントローラーは Symfony のコントローラー基本クラスを継承し、サービス コンテナーを使用する機会を与えます。get() メソッドを通じてコン​​テナーからサービス コンテナーを検索して削除できます

app.mailer サービス。

サービス パラメーター

コンテナを使用して新しいサービスを作成するプロセスは、非常にシンプルで簡単です。パラメーターを使用すると、サービスの定義をより柔軟かつ整然としたものにすることができます。

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>
YAML:# app/config/services.ymlparameters:
    app.mailer.transport:  sendmailservices:
    app.mailer:
        class:        AppBundle\Mailer
        arguments:    ['%app.mailer.transport%']


結果は前と同じです。違いは、サービスを定義する方法にあります。

app.mailer.transport% パーセント記号で囲むことにより、コンテナはその名前に対応するパラメータを探すことができます。コンテナ自体が生成されると、各パラメータの値がサービス定義に復元されます。

@ で始まる文字列を YAML ファイルのパラメーター値 (非常に安全な電子メール パスワードなど) として使用する場合は、次のことを行う必要があります。エスケープ用の別の # 記号を追加します (この状況は YAML 形式の構成ファイルにのみ適用されます)

# 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 サービスは単純です。パラメータは 1 つだけです。コンストラクターがあるため、設定が簡単です。 1 つ以上のコンテナー内の他のサービスに依存するサービスを作成すると、コンテナーの真の力が発揮され始めることが期待できます。

たとえば、電子メール メッセージを管理し、一連のアドレスに送信するのに役立つ新しいサービス NewsletterManager があるとします。 app.mailer このサービスはすでに電子メールを送信できるため、NewsletterManager でこのサービスを使用してメッセージ配信部分を処理できます。このクラスは次のようになります:

// 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;
    }     // ...}

サービス コンテナを使用しない場合は、コントローラーで簡単に NewsletterManager を作成できます:

use Acme\HelloBundle\Newsletter\NewsletterManager; // ... public function sendNewsletterAction(){
    $mailer = $this->get('app.mailer');
    $newsletter = new NewsletterManager($mailer);
    // ...}

これを実装することができます。しかし、将来 NewsletterManager クラスに 2 番目または 3 番目のコンストラクター パラメーターを追加したい場合はどうすればよいでしょうか?コードをリファクタリングして、このクラスの名前を変更する場合はどうなるでしょうか?どちらの場合も、NewsletterManager クラスがインスタンス化されている各場所を見つけて、手動でカスタマイズする必要があります。サービス コンテナーがより魅力的な処理方法を提供していることは疑いの余地がありません:

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']

YAML では、特殊な @app.mailer 構文により、コンテナに app.mailer という名前のサービスを見つけて、このオブジェクトを NewsletterManager に渡すよう指示します。 ■ コンストラクターのパラメーター。この例では、指定された app.mailer サービスは存在します。存在しない場合は、例外がスローされます。ただし、依存関係をオプションとしてマークすることはできます。このトピックについては次のセクションで説明します。

(サービスへの) 参照は、依存関係が明確に定義された独立したサービスを作成できる強力なツールです。この例では、app.newsletter_manager サービスは、その機能を実装するために app.mailer サービスに依存する必要があります。この依存関係をサービス コンテナーで定義すると、コンテナーはこのクラスをインスタンス化するすべての作業をホストします。

オプションの依存関係: Setter 注入

依存オブジェクトをコンストラクターに注入することは、依存関係を確実に利用できるようにする 1 つの方法です (そうしないとコンストラクターは実行できません)。ただし、クラスの場合、オプションの依存関係がある場合は、「セッター注入」の方が良い解決策です。これは、コンストラクターではなくクラス メソッドを使用して依存関係を注入することを意味します。クラスは次のようになります:

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'),));
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>

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

このセクションで実装される処理は、「コンストラクター インジェクション」および「セッター インジェクション」と呼ばれます。さらに、Symfony のコンテナ システムはプロパティ インジェクションもサポートしています。