依赖注入容器PHP

时间:2014-07-24 07:15:06

标签: php dependency-injection frameworks ioc-container

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

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

例如:

来自Zend 2的容器。我理解容器使类动态化,他从一开始就不必知道它们,他检查他是否已经在他的注册表中有该类,如果他没有,他检查是否class存在,构造函数中有哪些参数,并将它放在自己的注册表中,所以下次可以从那里获取它,实际上是动态完成所有事情并且它正在完成自己的注册表,所以一旦我们实现它们就不需要处理任何事情了即使我们只是制作那个课程,他也能按照我们想要的任何课程给出容器。

此外,如果我想要{A}需要B和B需要C,我知道他这样做是递归的,然后他去实例化C然后B,最后是A。

所以我理解大局以及他想做什么,但我不太确定如何实施它。

3 个答案:

答案 0 :(得分:5)

最好使用其中一个现有的依赖容器,例如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);

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

答案 1 :(得分:1)

因为我没有找到任何我想要的东西,我试着在我自己的容器上实现,我想听听一些关于如何看的意见,因为我已经开始学习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;
    }

}
?>

答案 2 :(得分:1)

我做了一个非常简单的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();
?>

我认为代码非常明显,但如果有什么不清楚,请告诉我