PHPのメソッド(コンストラクタ)引数のタイプヒンティングから型情報(クラス)を取得する(ボツ案)
DIコンテナを使用するプロジェクトについて、以下のような場合について考えてみる。
①リポジトリ:DBのテーブルと1対1で結びつき、CRUDを提供する
②サービス:ビジネスロジックを記述する。内部でリポジトリを使用し、コンストラクタから注入されるものとする
ざっくり以下のようなコードになる。
<?php class PostRepository { public function create() { ~ } // 略 } class PostService { protected $postRepository; public function __construct($postRepository) { $this->postRepository = $postRepository; } public function publish() { // 何かする } } $container['postRepository'] = $container->factory(function ($c) { return new PostRepository(); }); $container['postService'] = $container->factory(function ($c) { return new PostService($c['postRepository']); });
このとき、PostServiceがPostRepositoryに加え、UserRepositoryも必要になったする。
その場合、コンストラクタの引数に追加する必要がある。
class PostService { protected $postRepository; protected $userRepository; # 追加 public function __construct($postRepository, $userRepository) { $this->postRepository = $postRepository; $this->userRepository = $userRepository; } // 略 $container['postService'] = $container->factory(function ($c) { return new PostService($c['postRepository'], $c['userRepository']); });
このとき、リポジトリを追加するのがとてもメンドーなのでやめたい。
これを回避する方法がないか考えてみたところ、
コンストラクタの引数にタイプヒンティングを書いておき、型情報からコンテナを取得できれば、 対応するクラスをDIコンテナから引っ張って自動でセットできないだろうか、と考えた。
例えば、DIの生成部分で以下のように書いておき、
$container['postService'] = $container->factory(function ($c) { $service = new PostService(); // コンストラクタの引数のタイプヒンティングを解析し、postRepositoryとuserRepositoryを自動でコンテナから取得&セット $service->inject($c); return $service; });
コンストラクタでは、デフォルトでnullにしておく。あとから注入する。
class PostService { public function __construct(PostRepository $postRepository, UserRepository $userRepository) { $this->postRepository = $postRepository; $this->userRepository = $userRepository; } public function inject( $container ) { $ref_class = new ReflectionClass($this); $ref_constructor = $ref_class->getMethod('__construct'); foreach ($ref_constructor->getParameters() as $regl_param) { // 引数(typehint)の型(クラス名) $refl_args_class = $regl_param->getClass(); // typehint未指定 if (!$refl_args_class) { echo "typehintが未指定です。"; continue; } // クラス名 $class_name = $refl_args_class->getName(); // セット先のプロパティ $property_name = $regl_param->getName(); if (!property_exists($this, $property_name)) { echo "プロパティが見つかりませんでした。"; continue; } if (!isset($container[$class_name])) { echo "コンテナにインスタンスが入ってないよ。"; continue; } // セットぅ! $this->$property_name = $container[$class_name]; } } }
勢いでやってみたものの、以下の懸念が…。
懸念1 タイプヒンティングに実装クラス書いてるけど、普通はインタフェースを書かないとだよね… 懸念2 クラス名でコンテナセットしたら同じクラスのインスタンスを複数セットできないのては…
上記の案はボツになりました。 他にいい方法があったら教えてくださいませ。