Контейнер для инъекций зависимостей PHP

Недавно я узнал о преимуществах использования Dependency Injection (DI) в моем приложении PHP.

Однако я все еще не уверен, как создать контейнер для зависимостей. Раньше я использовал контейнер из фреймворка, и я хочу понять, как он делает что-то в обратном направлении и воспроизводит его.

Например:

Контейнер из Zend 2.I понимаю, что контейнер делает класс динамичным, он не должен знать о них с самого начала, он проверяет, имеет ли он этот класс в своем реестре, и если он не проверял, существует ли этот класс и какие параметры имеют внутри конструктора и помещают его в свой собственный реестр, так что в следующий раз можно взять его оттуда, практический делает все динамично и завершает свой собственный реестр, поэтому нам не нужно ничего не заботиться, как только мы реализуем контейнер, который он может дать как любой класс, который нам нужен, даже если мы просто сделаем этот класс.

Также, если я хочу getInstance для A, для которого нужны B и B, CI понимает, что он делает это рекурсивно, и он идет и создает экземпляр C, затем B и, наконец, A.

Поэтому я понимаю общую картину и то, что он должен делать, но я не настолько уверен в ее реализации.

Solutions Collecting From Web of "Контейнер для инъекций зависимостей PHP"

Возможно, вам лучше использовать один из существующих контейнеров Dependency Containers, например PHP-DI или Pimple. Однако, если вы ищете более простое решение, я внедрил контейнер зависимостей в качестве части статьи, которую я написал здесь: http://software-architecture-php.blogspot.com/

Вот код для контейнера

class Container implements \DecoupledApp\Interfaces\Container\ContainerInterface { /** * This function resolves the constructor arguments and creates an object * @param string $dataType * @return mixed An object */ private function createObject($dataType) { if(!class_exists($dataType)) { throw new \Exception("$dataType class does not exist"); } $reflectionClass = new \ReflectionClass($dataType); $constructor = $reflectionClass->getConstructor(); $args = null; $obj = null; if($constructor !== null) { $block = new \phpDocumentor\Reflection\DocBlock($constructor); $tags = $block->getTagsByName("param"); if(count($tags) > 0) { $args = array(); } foreach($tags as $tag) { //resolve constructor parameters $args[] = $this->resolve($tag->getType()); } } if($args !== null) { $obj = $reflectionClass->newInstanceArgs($args); } else { $obj = $reflectionClass->newInstanceArgs(); } return $obj; } /** * Resolves the properities that have a type that is registered with the Container. * @param mixed $obj */ private function resolveProperties(&$obj) { $reflectionClass = new \ReflectionClass(get_class($obj)); $props = $reflectionClass->getProperties(); foreach($props as $prop) { $block = new \phpDocumentor\Reflection\DocBlock($prop); //This assumes that there is only one "var" tag. //If there are more than one, then only the first one will be considered. $tags = $block->getTagsByName("var"); if(isset($tags[0])) { $value = $this->resolve($tags[0]->getType()); if($value !== null) { if($prop->isPublic()) { $prop->setValue($obj, $value); } else { $setter = "set".ucfirst($prop->name); if($reflectionClass->hasMethod($setter)) { $rmeth = $reflectionClass->getMethod($setter); if($rmeth->isPublic()){ $rmeth->invoke($obj, $value); } } } } } } } /** * * @param string $dataType * @return object|NULL If the $dataType is registered, the this function creates the corresponding object and returns it; * otherwise, this function returns null */ public function resolve($dataType) { $dataType = trim($dataType, "\\"); $obj = null; if(isset($this->singletonRegistry[$dataType])) { //TODO: check if the class exists $className = $this->singletonRegistry[$dataType]; $obj = $className::getInstance(); } else if(isset($this->closureRegistry[$dataType])) { $obj = $this->closureRegistry[$dataType](); } else if(isset($this->typeRegistry[$dataType])) { $obj = $this->createObject($this->typeRegistry[$dataType]); } if($obj !== null) { //Now we need to resolve the object properties $this->resolveProperties($obj); } return $obj; } /** * @see \DecoupledApp\Interfaces\Container\ContainerInterface::make() */ public function make($dataType) { $obj = $this->createObject($dataType); $this->resolveProperties($obj); return $obj; } /** * * @param Array $singletonRegistry * @param Array $typeRegistry * @param Array $closureRegistry */ public function __construct($singletonRegistry, $typeRegistry, $closureRegistry) { $this->singletonRegistry = $singletonRegistry; $this->typeRegistry = $typeRegistry; $this->closureRegistry = $closureRegistry; } /** * An array that stores the mappings of an interface to a concrete singleton class. * The key/value pair corresond to the interface name/class name pair. * The interface and class names are all fully qualified (ie, include the namespaces). * @var Array */ private $singletonRegistry; /** * An array that stores the mappings of an interface to a concrete class. * The key/value pair corresond to the interface name/class name pair. * The interface and class names are all fully qualified (ie, include the namespaces). * @var Array */ private $typeRegistry; /** * An array that stores the mappings of an interface to a closure that is used to create and return the concrete object. * The key/value pair corresond to the interface name/class name pair. * The interface and class names are all fully qualified (ie, include the namespaces). * @var Array */ private $closureRegistry; } 

Вышеупомянутый код можно найти здесь: https://github.com/abdulla16/decoupled-app (в папке / Контейнер)

Вы можете регистрировать свои зависимости как одноэлементный, как тип (каждый раз, когда создается новый объект) или как закрытие (контейнер будет вызывать функцию, которую вы регистрируете, и эта функция должна возвращать экземпляр).

Например,

 $singletonRegistry = array(); $singletonRegistry["DecoupledApp\\Interfaces\\UnitOfWork\\UnitOfWorkInterface"] = "\\DecoupledApp\\UnitOfWork\\UnitOfWork"; $typeRegistry = array(); $typeRegistry["DecoupledApp\\Interfaces\\DataModel\\Entities\\UserInterface"] = "\\DecoupledApp\\DataModel\\Entities\\User"; $closureRegistry = array(); $closureRegistry["DecoupledApp\\Interfaces\\DataModel\\Repositories\\UserRepositoryInterface"] = function() { global $entityManager; return $entityManager->getRepository("\\DecoupledApp\\DataModel\\Entities\\User"); }; $container = new \DecoupledApp\Container\Container($singletonRegistry, $typeRegistry, $closureRegistry); 

Этот контейнер разрешает свойства класса, а также параметры конструктора.

Я сделал очень простой класс IoC, который работает по назначению. Я исследовал шаблон IoC и DI и особенно после прочтения этого ответа . Дайте мне знать, если что-то не так, или у вас есть вопросы.

 <?php class Dependency { protected $object = null; protected $blueprint = null; /** * @param $instance callable The callable passed to the IoC object. */ public function __construct($instance) { if (!is_object($instance)) { throw new InvalidArgumentException("Received argument should be object."); } $this->blueprint = $instance; } /** * (Magic function) * * This function serves as man-in-the-middle for method calls, * the if statement there serves for lazy loading the objects * (They get created whenever you call the first method and * all later calls use the same instance). * * This could allow laziest possible object definitions, like * adding annotation parsing functionality which can extract everything during * the call to the method. once the object is created it can get the annotations * for the method, automatically resolve its dependencies and satisfy them, * if possible or throw an error. * * all arguments passed to the method get passed to the method * of the actual code dependency. * * @param $name string The method name to invoke * @param $args array The array of arguments which will be passed * to the call of the method * * @return mixed the result of the called method. */ public function __call($name, $args = array()) { if (is_null($this->object)) { $this->object = call_user_func($this->blueprint); } return call_user_func_array(array($this->object, $name), $args); } } /* * If the object implements \ArrayAccess you could * have easier access to the dependencies. * */ class IoC { protected $immutable = array(); // Holds aliases for write-protected definitions protected $container = array(); // Holds all the definitions /** * @param $alias string Alias to access the definition * @param $callback callable The calback which constructs the dependency * @param $immutable boolean Can the definition be overriden? */ public function register ($alias, $callback, $immutable = false) { if (in_array($alias, $this->immutable)) { return false; } if ($immutable) { $this->immutable[] = $alias; } $this->container[$alias] = new Dependency($callback); return $this; } public function get ($alias) { if (!array_key_exists($alias, $this->container)) { return null; } return $this->container[$alias]; } } class FooBar { public function say() { return 'I say: '; } public function hello() { return 'Hello'; } public function world() { return ', World!'; } } class Baz { protected $argument; public function __construct($argument) { $this->argument = $argument; } public function working() { return $this->argument->say() . 'Yep!'; } } /** * Define dependencies */ $dic = new IoC; $dic->register('greeter', function () { return new FooBar(); }); $dic->register('status', function () use ($dic) { return new Baz($dic->get('greeter')); }); /** * Real Usage */ $greeter = $dic->get('greeter'); print $greeter->say() . ' ' . $greeter->hello() . ' ' . $greeter->world() . PHP_EOL . '<br />'; $status = $dic->get('status'); print $status->working(); ?> 

Я думаю, что код довольно понятен, но дайте мне знать, если что-то непонятно

Поскольку я не нашел ничего близкого к тому, что хотел, я попытался реализовать на себе контейнер, и я хочу услышать мнение о том, как выглядит, потому что я начал изучать php и oop месяц назад. важно для меня, потому что я знаю, что у меня есть много вещей, чтобы учиться, поэтому, пожалуйста, не стесняйтесь запугивать мой код :))

 <!DOCTYPE html> <!-- To change this license header, choose License Headers in Project Properties. To change this template file, choose Tools | Templates and open the template in the editor. --> <?php class ioc { private $defs; static $instance; private $reflection; private function __construct() { $defs = array(); $reflection = array(); } private function __clone() { ; } public static function getInstance() { if (!self::$instance) { self::$instance = new ioc(); } return self::$instance; } public function getInstanceOf($class) { if (is_array($this->defs) && key_exists($class, $this->defs)) { if (is_object($this->defs[$class])) { return $this->defs[$class]; } } else { if (class_exists($class)) { if (is_array($this->reflection) && key_exists($class, $this->reflection)) { $reflection = $this->reflection[$class]; } else { $reflection = new ReflectionClass($class); $this->reflection[$class] = $reflection; } $constructor = $reflection->getConstructor(); if ($constructor) { $params = $constructor->getParameters(); if ($params) { foreach ($params as $param) { $obj[] = $this->getInstanceOf($param->getName()); } $class_instance = $reflection->newInstanceArgs($obj); $this->register($class, $class_instance); return $class_instance; } } if (!$constructor || !$params) { $class_instance = new $class; $this->register($class, $class_instance); return $class_instance; } } } } public function register($key, $class) { $this->defs[$key] = $class; } } ?>