LoginSignup
5
10

More than 3 years have passed since last update.

概要

DIやDIコンテナについて、自分が調べたことをまとめます。

DIとは何か

依存性の注入(Dependency injection)を略してDIと言います。
依存性とは具体的にはクラスのことです。
以下の例を見てみます。

class ProductController {
  public function mainAction() {
    $model = new ProductModel();
    $product = $model->getById(1);
  }
}
class ProductModel {
  public function getById($id) {
    $db = new MySqlConnect();
    $query = $db->query('select * from product where id = ?', $id);
    $product = $query->getResult();
    // ここに様々な処理があるとする
    return $product;
  }
}

ProductModelではMySqlConnectをnewして利用しています。
この状態をProductModelMySqlConnectに依存している、と言い、以下のようなデメリットがあります。

  • ProductModelMySqlConnectは互いに密結合になるため、修正による影響を受けやすくなる。
  • MySqlConnectをモックで置き換えられないため、ProductModelがテストしづらくなる。
  • ProductModelMySqlConnectが実装されていないと動かないため、分業がしづらい。

例えばデータベースシステムの移行などで、新たにOracleConnectを実装することを考えてみます。
上記のデメリットがあるため、MySqlConnectのインスタンスをProductControllerから渡します。

class ProductController {
  public function mainAction() {
    $model = new ProductModel(new MySqlConnect());
    $product = $model->getById(1);
  }
}
class ProductModel {
  private $db;
  pubic function __construct(MySqlConnect $db) {
    $this->db = $db;
  }
  public function getById($id) {
    $query = $this->db->query('select * from product where id = ?', $id);
    $product = $query->getResult();
    // ここに様々な処理があるとする
    return $product;
  }
}

上記の手法、デザインパターンをDIと呼びます。
コンストラクタでセットしているので、コンストラクタ注入と呼びます。

続いてMySqlConnectOracleConnectに置き換えます。
単純に置き換えると、将来的にPgSqlConnectが登場した際に困りますので、DatabaseConnectインターフェースを実装します。

interface DatabaseConnect {}
class MySqlConnect implements DatabaseConnect {}
class OracleConnect implements DatabaseConnect {}

class ProductController {
  public function mainAction() {
    // $model = new ProductModel(new MySqlConnect());
    $model = new ProductModel(new OracleConnect());
    $product = $model->getById(1);
  }
}
class ProductModel {
  private $db;
  pubic function __construct(DatabaseConnect $db) {
    $this->db = $db;
  }
  public function getById($id) {
    $query = $this->db->query('select * from product where id = ?', $id);
    $product = $query->getResult();
    // ここに様々な処理があるとする
    return $product;
  }
}

これで新たにPgSqlConnectが登場しても、ProductModelを修正する必要はありません。

DIコンテナとは何か

ですが、毎回この作業を手動で実装するのは大変です。
そこでDIコンテナという仕組みを使います。

以下はpimpleを使った例です。

require_once __DIR__.'/vendor/autoload.php';
use Pimple\Container;

interface DatabaseConnect {}
class MySqlConnect implements DatabaseConnect {}
class OracleConnect implements DatabaseConnect {}

class Settings {
  private $container;
  public function __construct() {
    $container = new Container();
    $container['db'] = function ($c) {
      return new OracleConnect();
    }
    $this->container = $container;
  }
  public function get($name) {
    return $this->container[$name];
  }
}

class ProductController {
  public function mainAction() {
    $settings = new Settings();
    $model = new ProductModel($settings->get('db'));
    $product = $model->getById(1);
  }
}
class ProductModel {
  private $db;
  pubic function __construct(DatabaseConnect $db) {
    $this->db = $db;
  }
  public function getById($id) {
    $query = $this->db->query('select * from product where id = ?', $id);
    $product = $query->getResult();
    // ここに様々な処理があるとする
    return $product;
  }
}

これでSettingsを変更するだけで、すべてのクラスにOracleConnectを適用させることができます。
DIコンテナ、インターフェースを使うことで、以下のメリットが得られます。

  • クラス同士の結合度を下げ、互いの修正が影響し合わないように設計できる。
  • DatabaseConnectインターフェースを実装したテスト用のクラスを作成することで、ProductModelを簡単にテストすることができる。
  • OracleConnectが未実装であっても、ProductModelを開発することができるので、分業ができる。

Symfonyではどのようになっているのか

SymfonyはDIコンテナを備えたフレームワークです。
Services.ymlという設定ファイルがDIコンテナの設定ファイルの役割を果たしています。

services:
  product_model:
    class: AppBundle\SomeDirectory\ProductModel
    arguments: ["@db"]
  db:
    # ここを変えれば別のクラスをProductModelに渡せる
    class: AppBundle\SomeDirectory\MySqlConnect
class ProductController {
  public function mainAction() {
    $model = $this->get('product_model');
    $product = $model->getById(1);
  }
}
class ProductModel {
  private $db;
  pubic function __construct(DatabaseConnect $db) {
    $this->db = $db;
  }
  public function getById($id) {
    $query = $this->db->query('select * from product where id = ?', $id);
    $product = $query->getResult();
    // ここに様々な処理があるとする
    return $product;
  }
}

まとめ

自分自身あまりよく背景を知らずにSymfonyを使っていたのですが、DIコンテナを知ることでSymfony自体の理解も進みました。
ご参考になれば幸いです。

5
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
10