スコープの使用方法

このエントリは全て サービスコンテナ に関連する高度なトピックであるスコープに関してになります。このエントリは次の2つのケースで役に立ちます。まず、サービスの作成時に “scopes” を言及しているエラーに遭遇したときです。次に request サービスに依存するサービスを作成する必要があったときです。

スコープを理解する

サービスのスコープは、サービスのインスタンスがコンテナによって使われる期間を制御します。 DI コンポーネントは、2つの一般的なスコープを提供します。 :

  • container (デフォルトはこちら): このコンテナからサービスを呼び出す際は、毎回同じインスタンスが使用されます。
  • prototype: サービスを呼び出す度に、新しいインスタンスが作成されます。

FrameworkBundle も3つ目のスコープである request を定義しています。 request スコープは、リクエストに関係しており、サブリクエスト毎に新しいインスタンスが作成されます。また、このスコープは CLI などリクエスト以外からの利用は不可能になっています。

スコープは、サービスの依存に制約を追加します。サービスはより狭いスコープのサービスに依存することはできません。例えば、一般的な my_foo サービスを作成して、 request コンポーネントに注入(inject)を試みると、コンテナのコンパイル時に Symfony\Component\DependencyInjection\Exception\ScopeWideningInjectionException 例外が投げられます。詳細は、下のサイドバーの情報を読んでください。

Note

もちろんサービスは、より大きなスコープのサービスには問題無く依存することができます。

コンフィギュレーションでスコープを設定する

サービスのスコープは、サービスのコンフィギュレーションで定義することができます。

  • YAML
    # src/Acme/HelloBundle/Resources/config/services.yml
    services:
        greeting_card_manager:
            class: Acme\HelloBundle\Mail\GreetingCardManager
            scope: request
    
  • XML
    <!-- src/Acme/HelloBundle/Resources/config/services.xml -->
    <services>
        <service id="greeting_card_manager" class="Acme\HelloBundle\Mail\GreetingCardManager" scope="request" />
    </services>
    
  • PHP
    // src/Acme/HelloBundle/Resources/config/services.php
    use Symfony\Component\DependencyInjection\Definition;
    
    $container->setDefinition(
        'greeting_card_manager',
        new Definition('Acme\HelloBundle\Mail\GreetingCardManager')
    )->setScope('request');
    

スコープを指定しなければ、デフォルトの container が使われますし、ほとんどの場合これで問題がありません。サービスがより狭いスコープの他のサービス(一般的には request サービス)に依存しているときのみ、スコープを設定する必要が出てきます。

狭いスコープのサービスを使用する

他のサービスに依存したサービスを作成する際に、最も良い方法は、同じスコープに入れるか、より狭いスコープに入れてください。ほとんどの場合、 request サービス内に新しいサービスを入れることになります。

しかし、これが毎回可能であるとは限りません。例えば、 Twig エクステンションでは、Twig 環境の依存のため、 container スコープ内にいる必要があります。こういったケースでは、コンテナ全体をサービスに渡し、正しいインスタンスを持っているか確認するため、毎回コンテナから依存を参照する必要があります。

namespace Acme\HelloBundle\Mail;

use Symfony\Component\DependencyInjection\ContainerInterface;

class Mailer
{
    protected $container;

    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }

    public function sendEmail()
    {
        $request = $this->container->get('request');
        // Do something using the request here
    }
}

Warning

最初のセクションで説明した同じ問題を避けるために、サービスの将来の呼び出しに備えて、オブジェクトのプロパティにリクエストを保管しないでください(symfony が間違いを検出できないとき以外は)。

このクラスのサービスコンフィギュレーションは次のようになります。

  • YAML
    # src/Acme/HelloBundle/Resources/config/services.yml
    parameters:
        # ...
        my_mailer.class: Acme\HelloBundle\Mail\Mailer
    services:
        my_mailer:
            class:     %my_mailer.class%
            arguments:
                - "@service_container"
            # scope: container can be omitted as it is the default
  • XML
    <!-- src/Acme/HelloBundle/Resources/config/services.xml -->
    <parameters>
        <!-- ... -->
        <parameter key="my_mailer.class">Acme\HelloBundle\Mail\Mailer</parameter>
    </parameters>
    
    <services>
        <service id="my_mailer" class="%my_mailer.class%">
             <argument type="service" id="service_container" />
        </service>
    </services>
    
  • PHP
    // src/Acme/HelloBundle/Resources/config/services.php
    use Symfony\Component\DependencyInjection\Definition;
    use Symfony\Component\DependencyInjection\Reference;
    
    // ...
    $container->setParameter('my_mailer.class', 'Acme\HelloBundle\Mail\Mailer');
    
    $container->setDefinition('my_mailer', new Definition(
        '%my_mailer.class%',
        array(new Reference('service_container'))
    ));
    

Note

全てのコンテナをサービスに注入(inject)するのは、良い方法ではありません。必要なものだけにしてください。しかし、ごく稀に request スコープ内でサービスが必要な container スコープがあり、そしてさらにその container スコープ内にサービスがあるときに必要になります。

コントローラをサービスとして定義すれば、アクションメソッドの引数を渡してコンテナに注入しなくても Request オブジェクトを取得できます。詳細は、 book-controller-request-argument を参照してください。