Drupal8におけるサービスとDependency Injection(DI)

Drupal8では、サービスはサービスコンテナで管理されます。

Drupal8では、再利用可能な機能を切り離すことができるサービス概念を導入しました。サービスコンテナに登録してサービスを入れたり入れ替えたりすることができます。開発者として、システムが切り離されているという考え方を尊重して、サービスコンテナを通してDrupalの提供するサービスを使用することをお薦めします。サービスの紹介については、Symfony2の資料を参照してください。

開発者としては、データベースアクセスやメール送信といった操作を行うためにサービスを利用します。PHPネイティブのMySQL機能を使うのではなく、サービスコンテナを通してコアの提供するサービスを利用しましょう。これによって、データベースの種類(MySQL や SQLlite)を考慮せずにデータベースアクセスのコードを書くことができ、メール送信の仕組みがSMTPかどうかを考慮せずにすみます。

コアサービス

コアサービスは、CoreServiceProvider.php およびcore.services.ymlに定義されています。

例:

  ...
  language_manager:
    class: Drupal\Core\Language\LanguageManager
    arguments: ['@language.default']
  ...
  path.alias_manager:
    class: Drupal\Core\Path\AliasManager
    arguments: ['@path.crud', '@path.alias_whitelist', '@language_manager']
  ...
  string_translation:
    class: Drupal\Core\StringTranslation\TranslationManager
  ...
  breadcrumb:
    class: Drupal\Core\Breadcrumb\BreadcrumbManager
    arguments: ['@module_handler']
  ...

各サービスは他のサービスに依存させることができます。上記の例の場合、「path.alias_manager 」はargumentsリストに定義されている「path.crud」と「 path.alias_whitelist 」と「language_manager 」サービスに依存しています。サービスの依存性は、依存するサービス名の先頭に「@」を付けて定義します。
Drupalで何かしらのコードによって「path.alias_manager」サービスがリクエストされると、サービスコンテナは、最初に「path.alias_manager」サービスのコンストラクタを呼び出すために「path.crud」「path.alias_whitelist」「language_manager」サービスにリクエストを投げ、「path.alias_manager」サービスのコンストラクタの戻り値として返されます。一方、「language_manager」は、language.defaultサービスなどに依存します。

Drupal8に多数のサービスがあり、「CoreServiceProvider.php」および「core.services.yml」ファイルを参照すると、どのようなサービスが利用できるかが分かるでしょう。
サービスコンテナ(または、DIコンテナ)は、サービスのインスタンス化を管理するPHP オブジェクトです。DrupalのサービスコンテナはSymfony2のサービスコンテナに基づいています。これらのファイルの構成、特殊文字、任意的な依存性等の詳細については、Symfony2サービスコンテナを参照してください。

DIを使ってオブジェクト内サービスにアクセスする

DIは、Drupal8のサービスにアクセスし、使用するのにおすすめの方法であり、可能な限り活用すべきです。グローバルサービスコンテナを呼び出すより、サービスはコンスタラクタの引数として渡されるか、セッターメソッドで注入されます。コアのモジュールによって提供されるコントローラやプラグインのクラスの多くは上記のパターンを使用しており、動作中に良いリソースとして役立つでしょう。 グローバルなDrupalクラスは、グローバルファンクションとして扱われるべきですが、Drupal8の基本的なアプローチは、コントローラやプラグインなどのようなクラスが中心となります。最も良い例としては、グローバルサービスコンテナを呼び出すのではなく、必要なサービスをコンスタラクタの引数として渡すか、または、サービスのセッターメソッドを通して必要なサービスを注入することです。 依存するオブジェクトを明示的にサービスに渡すことをDIと呼んでいます。多くの場合、依存性は、コンスタラクタへ明示的に渡されます。例えば、ルートアクセスチェッカー は、サービス生成時に現在のユーザ情報を受け取り、アクセスチェック時に渡された現在のリクエスト情報を受け取ります。依存性を設定するためにセッターの使用も可能です。

グローバルファンクション内のサービスにアクセスする

グローバルなDrupalクラス によって、共通サービスにアクセスする静的メソッドが提供されます。例えば、 Drupal::moduleHandler() はモジュールハンドラサービスを返します。また、Drupal::translation() は、文字列翻訳サービスを返します。利用したいサービスの専用メソッドがない場合、Drupal::service() メソッドを使って、特定のサービスを検索することができます。

例:専用の\Drupal::database() アクセサでデータベースサービスにアクセスします。

// Returns a Drupal\Core\Database\Connection object.
$connection = \Drupal::database();

$result = $connection->select('node', 'n')
  ->fields('n', array('nid'))
  ->execute();

例:一般的な\Drupal::service() メソッドでdateサービスにアクセスします。

// Returns a Drupal\Core\Datetime\Date object.
$date = \Drupal::service('date');

グローバルファンクションを利用するコードを最小にして、実際にDIが注入されるコントローラやリスナーやプラグインなどに適切な形でリファクタを行うのが理想的です。以下を参照してください。
コードの例については、Symfony2の資料を参照してください。

独自のサービスを定義する

example.services.yml ファイルを使用して、独自のサービスを定義することが可能です。この場合、example が、サービスを定義するモジュール名となります。このファイルの構造はcore.services.yml ファイルと同じです。
自分でサービスを定義する必要のあるサブシステムがあります。例えば、custom route access checkerクラスcustom parameter upcastingプラグインマネジャの定義などクラスをサービスとして登録する必要があります。
$GLOBALS['conf']['container_yamls'] を利用してサービスを検索するために、YAMLファイルを追加ことも可能です。ただし、こちらを利用することはあまりありません。

Drupal7 のグローバルファンクションとDrupal8の サービスの比較

Drupal7とDrupal8の違いの例として、モジュールのフック関数を呼び出すために必要なソースコードを見てみましょう。Drupal7では、全てのhook_help()の実装を呼び出すには module_invoke_all('help') を使います。module_invoke_all()関数を直接コードで呼び出しているため、コアの関数を変更しないとDrupalのモジュールの呼び出し方を簡単な形に改良することができません。

Drupal8では、module_* 関数は「ModuleHandler」サービスに置き換えられています。したがって、Drupal8 では、\Drupal::moduleHandler()->invokeAll('help')を利用することになります。この例の場合、\Drupal::moduleHandler() は、サービスコンテナを通してモジュールハンドラサービスの実装個所を探し出し、サービス内の「invokeAll()」メソッドを呼び出します。
Drupalのディスリビューションやホストプロバイダーや他のモジュールにとって、モジュールハンドラサービスとして登録されたクラスをModuleHandlerInterfaceが実装れされている別のクラスに変更することでモジュールを呼び出す方法をオーバーライドすることができるので、 Drupal7よりも良いアプローチです。この変更は、Drupalのコードの残りの部分の透明性を高めます。すなわち、コアを変更せずに、Drupalの多くの部分を変更することが可能になります。ソースコードの依存性もよりよくドキュメント化され、懸念事項も明確になります。最後に、統合テストと比較して、よりコンパクトで簡単なテストのインタフェースを使ってサービスの単体テストを行うことができます。

Drupal7 のグローバル変数とDrupal8の サービスの比較

Drupal7のglobal $language や global $user 等のようなグローバル変数はDrupal8の(グローバル変数ではなく)サービスを通してアクセス可能になります。
Drupal::languageManager()->getLanguage(Language::TYPE_INTERFACE)Drupal::currentUser() を参照してください。

翻訳元URL:https://www.drupal.org/node/2133171