【问题标题】:Dependency Injection Container PHP依赖注入容器 PHP
【发布时间】:2014-09-15 15:07:33
【问题描述】:

我最近了解到在我的 PHP 应用程序中使用依赖注入 (DI) 的优势。

但是,我仍然不确定如何为依赖项创建容器。之前,我使用框架中的容器,我想了解他在后面是如何做事并重现它。

例如:

来自 Zend 2 的容器。我知道容器使类动态化,他不必从一开始就知道它们,他检查他是否已经在他的注册表中拥有该类,如果他没有,他检查是否类存在,构造函数内部有什么参数并将其放入他自己的注册表中,以便下次可以从那里获取它,实际是动态地做所有事情并且它正在完成他自己的注册表,所以我们一旦实现就不必关心任何事情容器,因为他可以提供我们想要的任何课程,即使我们只是制作那个课程。

另外,如果我想为需要 B 和 B 需要 C 的 A 发送 getInstance,我知道他这样做是递归的,他去实例化 C,然后是 B,最后是 A。

所以我了解大局以及他应该做什么,但我不太确定如何实施。

【问题讨论】:

  • 我一直在你的位置上,我建议你查看Pimple DI 它是一个单一的类(据我所知)并且相对简单。看看它并了解它是如何工作的。这应该可以帮助您旋转自己的 DIC 变体
  • 你能看看我的第一个答案,是我几天前提出的问题后提出的解决方案,但没有人说什么,我仍然不知道是否有什么好处吗,我真的很想听听你的意见,如果你有时间看看。谢谢!
  • 我注意到的第一件事是你大量使用反射,你能解释一下为什么吗? (虽然我还没有深入审查它)
  • 当然。我的 DIC 处理所有动态,他可以提供您刚刚创建的类的实例,因为当您询问实例时,如果现在检查他是否已经在 $defs 中拥有该类的实例如果该类存在,然后通过反射,我将获取他的所有构造函数参数并为他的每个构造函数参数(依赖项)调用相同的函数,如果我们想要的对象在构造函数中没有任何参数,他会创建新的并将其放入在 $defs 中,所以因为一切都是递归的,所以它会返回,并且每个对象都是在处理完他的依赖项之后生成的。
  • 因为我总是需要反射,所以我也做了一个反射数组,这样我就不会每次都进行新的反射。

标签: php dependency-injection frameworks ioc-container


【解决方案1】:

您最好使用现有的依赖容器之一,例如 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 (i.e., 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 (i.e., 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 (i.e., include the namespaces).
     * @var Array
     */
    private $closureRegistry;

}

上面的代码可以在这里找到:https://github.com/abdulla16/decoupled-app(在/Container文件夹下)

您可以将依赖项注册为单例、类型(每次实例化新对象时)或闭包(容器将调用您注册的函数,并且该函数应返回实例) .

例如,

$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);

此容器解析类的属性以及构造函数参数。

【讨论】:

    【解决方案2】:

    我已经完成了一个非常简单的 IoC 类,它可以按预期工作。我研究了 IoC 和 DI 模式,尤其是在阅读 this answer 之后。如果有问题或有任何问题,请告诉我。

    <?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();
    ?>
    

    我认为代码是不言自明的,但如果有不清楚的地方请告诉我

    【讨论】:

    • 我理解这个功能,我的第一个方法是这样的,因为这里读到了一些东西,但我想做一些我说过的动态的东西,因为我不想告诉 IOC 每个人我可能想要给他上一堂课,我想问他一堂课,他给了我,之前没有告诉他。
    • 另外,您需要在注册对象时告诉它所有的依赖关系,这就是 DIC 的作用,您不必知道他拥有的所有依赖关系,在大型应用程序中它可以有 20,30 个依赖项 你必须说 $dic->register('status', function () use ($dic) { return new Baz($dic->get('greeter'),dep2,dep3,dep4, .....); });我不这样做,我想像我说的那样做一些与框架相同的事情并具有一些性能。
    • 我知道我无法获得与制作框架的人相同的方法,因为他们思考了很多时间,这就是为什么我在这里问是否有人知道 pro 方法,框架使用的方法在后面并告诉我我是否接近它,因为我想在框架中使用 DIC 之前了解事情是如何工作的.
    • 因此,在这种情况下,当自动依赖解析是强制性的时,您将不得不使用诸如注释解析器之类的东西来解析它们,并让它解析数组配置,例如配置的用户名和密码,并且是有点远离保持一切分开。
    • 在我的 DIC 类的情况下,假设我只是在我的应用程序的某个地方构建了一个 User 类,而无需执行任何操作,我可以在我的应用程序的任何地方获取一个 User 实例,就像这样:$ioc=ioc: :getInstance(); $user_object=$ioc->getInstanceOf("用户");这就是我希望事情发生的方式,但我不确定我是否选择了最好的方式来做到这一点,我相信有更好的方式。
    【解决方案3】:

    因为我没有找到任何接近我想要的东西,所以我尝试自己实现一个容器,我想听听一些关于外观的意见,因为一个月前我已经开始学习 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;
        }
    
    }
    ?>
    

    【讨论】:

      猜你喜欢
      • 2018-09-13
      • 1970-01-01
      • 1970-01-01
      • 2013-04-30
      • 1970-01-01
      • 1970-01-01
      • 2012-11-17
      • 2016-11-13
      • 1970-01-01
      相关资源
      最近更新 更多