DIコンテナ使ってみて思った雑文:その2

アプリケーションにテーブルゲートウェイがあったとして、もちろんDB接続に依存している。

class UserTable
{
    private $connection;

    public function __construct(Connection $connection)
    {
        $this->connection = $connection;
    }

    // select/insert/update/delete/etc...
}

テーブルゲートウェイはテーブルの数だけ存在するので、全部に同じ実装を書くのはDRYじゃないので抽象クラスを作る。

abstract class AbstractTableGateway
{
    private $connection;

    public function __construct(Connection $connection)
    {
        $this->connection = $connection;
    }

    // select/insert/update/delete/etc...
}

class UserTable extends AbstractTableGateway
{
    // UserTable spesific method...
}

特定のテーブルで Connection 以外のなにかに依存する場合、コンストラクタを拡張して DI する。

class HogeTable extends AbstractTableGateway
{
    private $hoge;

    public function __construct(Connection $connection, Hoge $hoge)
    {
        parent::__construct($connection);
        $this->hoge = $hoge;
    }

    // HogeTable spesific method...
}

おっと、ここでテーブルのメタデータを保存するために AbstractTableGateway でキャッシュを使いたくなった。

abstract class AbstractTableGateway
{
    private $connection;
    private $cache;

    public function __construct(Connection $connection, Cache $cache)
    {
        $this->connection = $connection;
        $this->cache = $cache;
    }

    // select/insert/update/delete/etc...
}

すると HogeTable のコンストラクタも弄らなければならない。

class HogeTable extends AbstractTableGateway
{
    private $hoge;

    public function __construct(Connection $connection, Cache $cache, Hoge $hoge)
    {
        parent::__construct($connection, $cache);
        $this->hoge = $hoge;
    }

    // HogeTable spesific method...
}

HogeTable 的には「キャッシュとか知らんがなそっちで勝手にやってくれ」なので HogeTable のコンストラクタにまで影響するのは違和感がある。

これはまあ Connection と Cache を併せたなにかを作るか(ConnectionFacade とか)、あるいは、Connection に Cache のインスタンスを持たせればスッキリする。

あるいは AbstractTableGateway のような抽象クラスを作って継承するのはやめて、委譲とかトレイトだけでどうにかするべきだろうか。

trait TableGatewayTrait
{
    abstract protected function getTableGateway();

    // select/insert/update/delete/etc...
}

class UserTable
{
    use TableGatewayTrait;

    private $tableGateway;

    public function __construct(TableGateway $tableGateway)
    {
        $this->tableGateway = $tableGateway;
    }

    protected function getTableGateway()
    {
        return $this->tableGateway;
    }

    // UserTable spesific method...
}

class HogeTable
{
    use TableGatewayTrait;

    private $tableGateway;
    private $hoge;

    public function __construct(TableGateway $tableGateway, Hoge $hoge)
    {
        $this->tableGateway = $tableGateway;
        $this->hoge = $hoge;
    }

    protected function getTableGateway()
    {
        return $this->tableGateway;
    }

    // HogeTable spesific method...
}

コンストラクタとかで定形パターンをなんども書くのが辛ければそれらもトレイトに含めて、必要に応じてコンストラクタをオーバーライドするとか。

trait TableGatewayTrait
{
    private $tableGateway;

    public function __construct(TableGateway $tableGateway)
    {
        $this->tableGateway = $tableGateway;
    }

    protected function getTableGateway()
    {
        return $this->tableGateway;
    }

    // select/insert/update/delete/etc...
}

class UserTable
{
    use TableGatewayTrait;

    // UserTable spesific method...
}

class HogeTable
{
    use TableGatewayTrait { __construct as constructTableGateway; }

    private $hoge;

    public function __construct(TableGateway $tableGateway, Hoge $hoge)
    {
        $this->constructTableGateway($tableGateway);
        $this->hoge = $hoge;
    }

    // HogeTable spesific method...
}

コンストラクタを as で別名にするのはさすがに変か。。。


コンストラクタインジェクション前提で考えると、継承元のコンストラクタを弄ったときに派生先のすべてに影響してしまうので継承させにくいなーと思ったけどよく考えたら DI とか関係なく継承してれば当たり前のことだった。