依存関係の挿入については、誰もが頻繁に触れるか、少なくとも聞いたことがあると思います。Java の Spring、PHP の Laravel、Symfony など、比較的よく知られたフレームワークはすべて依存関係の挿入をサポートしています。 、など。次に、単純な DI コンテナを手動で実装してみましょう。
最初に車を運転して例を挙げましょう:
class Driver{ public function drive() { $car = new Car(); echo '老司机正在驾驶', $car->getCar(), PHP_EOL; } }class Car{ protected $name = '普通汽车'; public function getCar() { return $this->name; } }
経験者向けには、ドライバーと車の 2 つのカテゴリがありますdrivers Driver にはメソッド driver があり、呼び出すときは、まず車全体 $car を取得してから車を起動します。ほとんどの学生がこのコードまたは同様のコードを作成したことがありますが、このコードには何も問題はなく、ごく普通のコードです。でも、車を乗り換えるなら普通の車では女の子を惹きつけることはできません。
class Benz extends Car{ protected $name = '奔驰'; }
現時点では、かなり嫌な操作を行う必要があり、古いドライバーのコードを変更する必要があります。 (老ドライバー「何を間違えたんだろう?車を乗り換えたら、運転免許証を勉強し直さなければいけない…)」したがって、経験豊富なドライバーが運転時に独自の車を構築する必要がなくなるように、車を外部の世界に注入し、ドライバーと車を切り離す必要があります。したがって、次の結果が得られます。
class Driver{ protected $car; public function __construct(Car $car) { $this->car = $car; } public function drive() { echo '老司机正在驾驶', $this->car->getCar(), PHP_EOL; } }
現時点では、Driver クラスと Car クラスは分離されており、これら 2 つのクラスの依存関係は上位層のコードによって管理されています。このとき、経験豊富なドライバーは次のように「運転」します。
$car = new Car(); $driver = new Driver($car); $driver->drive();
この時点で、ドライバーの依存関係のインスタンスを作成し、それを注入します。上記の例では依存性注入を実装しましたが、手動であるため、やはり書くのに違和感がありました。このような大変な作業をどうやって手動で行うことができるでしょうか? プログラムにそれを自動的に行わせる必要があります。それ以来、DI コンテナーが誕生しました。
Dependency Injection は、IoC モデルやファクトリ モデルと同様に、呼び出し元と呼び出し先の間の依存関係の結合関係を解決するモデルです。オブジェクト間の依存関係を解決し、オブジェクトが IoC/DI コンテナのみに依存し、相互に直接依存しなくなることで疎結合が実現され、オブジェクトの作成時に IoC/DI コンテナがその依存関係 (依存関係) を注入します。 ) オブジェクト (注入)、これにより最大限の疎結合を実現できます。率直に言うと、依存関係の注入とは、コンテナが、特定のクラスが依存する他のクラスのインスタンスを、このクラスのインスタンスに注入することを意味します。
この段落は少し抽象的かもしれませんが、先ほどの例に戻りましょう。私は依存関係の注入を手動で完了しましたが、これは非常に面倒ですが、これを大規模なプロジェクトで実行すると、間違いなく非常に煩雑で十分な洗練度が得られません。したがって、これを行うスチュワードが必要であり、このスチュワードがコンテナです。クラスの依存関係管理はすべてコンテナーに任せられます。したがって、一般的に言えば、コンテナは全員が共有するグローバル オブジェクトです。
関数を作成するには、まず問題を分析する必要があります。そのため、まず、単純な DI コンテナにどのような関数が必要かを理解する必要があります。これは、次のことに直接関係します。私たちの準備規定。単純なコンテナの場合、少なくとも次の点を満たす必要があります:
必要なクラスのインスタンスを作成する
完全な依存関係管理 (DI) )
class Container{ /** * 单例 * @var Container */ protected static $instance; /** * 容器所管理的实例 * @var array */ protected $instances = []; private function __construct(){} private function __clone(){} /** * 获取单例的实例 * @param string $class * @param array ...$params * @return object */ public function singleton($class, ...$params) {} /** * 获取实例(每次都会创建一个新的) * @param string $class * @param array ...$params * @return object */ public function get($class, ...$params) {} /** * 工厂方法,创建实例,并完成依赖注入 * @param string $class * @param array $params * @return object */ protected function make($class, $params = []) {} /** * @return Container */ public static function getInstance() { if (null === static::$instance) { static::$instance = new static(); } return static::$instance; } }一般的なスケルトンが決定され、コアの make メソッドに入ります:
protected function make($class, $params = []){ //如果不是反射类根据类名创建 $class = is_string($class) ? new ReflectionClass($class) : $class; //如果传的入参不为空,则根据入参创建实例 if (!empty($params)) { return $class->newInstanceArgs($params); } //获取构造方法 $constructor = $class->getConstructor(); //获取构造方法参数 $parameterClasses = $constructor ? $constructor->getParameters() : []; if (empty($parameterClasses)) { //如果构造方法没有入参,直接创建 return $class->newInstance(); } else { //如果构造方法有入参,迭代并递归创建依赖类实例 foreach ($parameterClasses as $parameterClass) { $paramClass = $parameterClass->getClass(); $params[] = $this->make($paramClass); } //最后根据创建的参数创建实例,完成依赖的注入 return $class->newInstanceArgs($params); } }コンテナを使いやすくするために、次のようにしました。 シングルトン インスタンスを配列から直接取得できるように、ArrayAccess インターフェイスを実装します。インスタンスが存在しない場合は、インスタンスを作成します。
class Container implements ArrayAccess{ /** * 单例 * @var Container */ protected static $instance; /** * 容器所管理的实例 * @var array */ protected $instances = []; private function __construct(){} private function __clone(){} /** * 获取单例的实例 * @param string $class * @param array ...$params * @return object */ public function singleton($class, ...$params) { if (isset($this->instances[$class])) { return $this->instances[$class]; } else { $this->instances[$class] = $this->make($class, $params); } return $this->instances[$class]; } /** * 获取实例(每次都会创建一个新的) * @param string $class * @param array ...$params * @return object */ public function get($class, ...$params) { return $this->make($class, $params); } /** * 工厂方法,创建实例,并完成依赖注入 * @param string $class * @param array $params * @return object */ protected function make($class, $params = []) { //如果不是反射类根据类名创建 $class = is_string($class) ? new ReflectionClass($class) : $class; //如果传的入参不为空,则根据入参创建实例 if (!empty($params)) { return $class->newInstanceArgs($params); } //获取构造方法 $constructor = $class->getConstructor(); //获取构造方法参数 $parameterClasses = $constructor ? $constructor->getParameters() : []; if (empty($parameterClasses)) { //如果构造方法没有入参,直接创建 return $class->newInstance(); } else { //如果构造方法有入参,迭代并递归创建依赖类实例 foreach ($parameterClasses as $parameterClass) { $paramClass = $parameterClass->getClass(); $params[] = $this->make($paramClass); } //最后根据创建的参数创建实例,完成依赖的注入 return $class->newInstanceArgs($params); } } /** * @return Container */ public static function getInstance() { if (null === static::$instance) { static::$instance = new static(); } return static::$instance; } public function __get($class) { if (!isset($this->instances[$class])) { $this->instances[$class] = $this->make($class); } return $this->instances[$class]; } public function offsetExists($offset) { return isset($this->instances[$offset]); } public function offsetGet($offset) { if (!isset($this->instances[$offset])) { $this->instances[$offset] = $this->make($offset); } return $this->instances[$offset]; } public function offsetSet($offset, $value) { } public function offsetUnset($offset) { unset($this->instances[$offset]); } }次に、コンテナーを使用して、上記を記述してみましょうコード:
$driver = $app->get(Driver::class); $driver->drive();//output:老司机正在驾驶普通汽车复制代码シンプルで経験豊富な運転手が電車を発車させることができます。ここでのデフォルトの注入は Car のインスタンスです。メルセデス ベンツを運転する必要がある場合は、これを実行するだけです:
$benz = $app->get(Benz::class); $driver = $app->get(Driver::class, $benz); $driver->drive();//output:老司机正在驾驶奔驰复制代码PSR-11 の要件に従って、依存関係注入コンテナーは次の実装を行う必要があります。 Psr\Container\ContainerInterface インターフェイス。これは単なるデモンストレーションです。これを実装するには、Psr 依存関係ライブラリの導入が必要なので、より面倒ですが、実際には非常に簡単です。メソッドがいくつかあるだけです。興味のある方は、PSR-11 (ポータル) の要件について自分で学ぶことができます。 これは非常に単純な DI コンテナです。実際には、考慮すべきことがたくさんありますが、ここでのコンテナ関数は依然として非常に単純です。循環依存関係への対処方法や遅延読み込みの仕組みなど、まだ対処できていない落とし穴がいくつかあります... ここでは私の週末の練習記録を少しだけ載せておきます。 Laravel または Symfony コンテナーのソース コードについては以下を読むか、Spring のコンテナーについて学ぶことができます。時間があるときに引き続き改良していきます。 [推奨学習:「
PHP ビデオ チュートリアル
」]以上がPHP DI コンテナを手動で作成する方法を説明します。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。