第二回:サービスコンテナ

「今回のブログは「Drupal8を理解する」の二回目となります。前回のブログでは、Drupal8の全体構造とSymfonyとの関連について説明をしました。Symfony (とDrupal8)には複数のコンポーネントが含まれていることを紹介しました。今回は、サービスコンテナの紹介とDrupal8でどのように利用されているかを説明します。ルーティングを理解する前に、サービスコンテナを理解することは重要なことです。

Symfonyは、サービスコンテナを利用してアプリケーション内のサービスを効率的に管理します。これはDIとも呼ばれています。

サービスコンテナは、リクエストを処理する前にカーネルによって作成・保存されるグローバルオブジェクトです。サービスコンテナは、サービスが必用な際に遅延読込されたサービスを取込み利用することができます。サービスは、グローバルオブジェクトでメール配信機能やデータベース接続のような特定の処理を実行するために利用します。一つのサービスは一つのクラスに相当します。サービスコンテナは利用可能なサービスを含んでいて、サービスの関連性と構成を把握し、サービス自体を構築することが可能である非常に重要なコンポーネントです。

依存性および引数

サービスが、他のサービスに依存することもあります。Symfonyドキュメントは、メール配信の際に利用するNewsletterManagerサービスの例を用いて説明しています。サービスコンテナはこれらの依存性も管理します。サービスを作成する際に、クラスコンストラクタに引数を渡して依存性の設定をします。インタフェースに依存するサービスがどのメソッドを提供していをかを定義することで、必要な際にはサービスの実行を他のサービスに交換することができます。

構成

サービスコンテナ内のサービスは、コード(拡張子)・XML・YAMLなどで構成されます。Symfonyはこれらの組み合わせを利用しますが、コアサービスのほとんどは、フレームワークでバンドルされたコードに任せます。Drupalのサービス構成は、それとは異なYAMLファイルに基づいています。コアに関する全の構成を「services.core.yml」ファイルで管理し、モジュールや「ServiceProvider」クラスを通して拡張することができます。拡張方法はSymfonyと同じです。カーネルによって、これらの構成がロードされます。

YAMLは、読みやすくて、柔軟な構文を提供します。以下にDrupal8 (services.core.yml)の例を紹介します。

services:
  ...
  router_listener:
  class: Symfony\Component\HttpKernel\EventListener\RouterListener
  tags:
    - { name: event_subscriber }
  arguments: ['@router']
  ...
  router:
    class: Symfony\Cmf\Component\Routing\ChainRouter
    calls:
      - [setContext, ['@router.request_context']]
      - [add, ['@router.dynamic']]
  ...

上記は、「router_listener」サービスを定義します。該当のクラスは、listenerをロードする必要があります。引数のプロパティは、RouterListener コンスタラクタ(URLあるいはrequestで合致させる)の最初の引数が「@router」で、サービスIDが「router」として定義されます。このrouterはコアの構成としても定義され、ちょうど私が説明したとおりSymfonyが利用するクラスとは違うクラスとして定義されます。

タグ付けのサービス

タグは、「タグ付けされた」サービスをロードするのに利用します。例えば、Drupalの 'hooky' という手法の中で時々使われます。次の例(node.services.yml)の場合、ノードモジュールは、access_checkサービスパターンに基づいた「add node」ページ向けにアクセスチェッカーを追加します。access_checkサービスパターンは、ルーティング後にアクセス権限が存在するか判断するために利用されます。

サービス:

services:
 ...
  access_check.node.add:
    class: Drupal\node\Access\NodeAddAccessCheck
    arguments: ['@plugin.manager.entity']
    tags:
      - { name: access_check }
        ...

コンパイラパス

Drupalはタグをどのように認識して、どのように処理するのでしょうか?Symfonyにはコンパイラパス」というサービスコンテナを動的に構成する方法があります。サービスコンテナは、静的な構成からビルドされた後にコンパイルされます。そのコンパイル期間中に「CompilerPassInterface」を実行するオブジェクトに構成変更を許可します。 Drupalでは、「RegisterAccessChecksPass」のようないくつかの重要なコンパイラパスを登録します。「RegisterAccessChecksPass」はアクセスチェック(前例を参照)やAccessManager(コンテナサービスを直接追加するaddMethodCallを利用する)に追加されたサービスの中からタグ付けされた全サービスを見つけようとします。ここでは、ルーティングの段階で「AccessManager」サービスにアクセスを確認してもらい、「NodeAddAccessCheck」を確認します。このようにDrupalコアでは、ルートパラメータをオブジェクトに変換するものや、文字列を翻訳する様々なコンパイラパスが存在します。実際に、これらのタグ付けされたサービスは、カスタマイズしたアクセスチェックを追加したりパスのパラメータを変換したりするために、モジュールで必要な時があります。

サービスの交換

サービスコンテナは、柔軟なサービス構成を可能とします。Drupal8は、サービス引数を利用して、Symfony自身のもともとの機能を部分的に変更することができます。例えば、DrupalはSymfonyと違うURLルーティング手法が必要です。それは、Symfony(Symfony\Bundle\FrameworkBundle\Routing\Router)で使われているものの代替としてルーターサービス(Symfony\Cmf\Component\Routing\ChainRouter)を提供することで実現しています。これは、「router_listener」サービス(Symfony\Component\HttpKernel\EventListener\RouterListener) のコード修正をなくして実現できます。両方のルーターが「RequestMatcherInterface」インタフェースを実装することにより可能となります。

まとめ

今回Drupal8内のサービスコンテナについて説明しました。コードを変えずにDrupal8はどのように 自分のサービスを導入するかが理解できたかと思います。これで Drupalのコア開発者がSymfony2を簡単に更新することができます。Drupal8を勉強している間は「core.services.yml」ファイルを確認して使用中のサービスを探す必要性が出てくると思います。

次のブログでは、Drupal8とルーティングの全体的な流れについて説明します。

翻訳元URL:https://cipix.nl/understanding-drupal-8-part-2-service-container