2

I've recently learned about the advantages of using Dependency Injection (DI) in my PHP application.

However, I'm still unsure how to create my container for the dependencies. Before, I use a container from a framework and I want to understand how is he doing things in back and reproduce it.

For example:

The container from Zend 2. I understand that the container make class dynamic, he does not have to know about them from the beginning, he checks if he already has that class in his registry and if he has not he check if that class exist and what parameters has inside constructor and put it in his own registry so next time could take it from there, practical is doing everything dynamic and it is completing his own registry, so we do not have to take care of nothing once we implement the container as he can give as any class we want even if we just make that class.

Also if I want to getInstance for A which needs B and B needs C I understand that he doing this recursive and he goes and instantiate C then B and finally A.

So I understand the big picture and what is he suppose to do but I am not so sure about how to implement it.

ReynierPM
  • 17,594
  • 53
  • 193
  • 363
Serban Alexandru
  • 165
  • 2
  • 13
  • Ive been on your place, I would advice you to look in to [Pimple DI](http://pimple.sensiolabs.org/) it is a single class (as far as I remember) and it is relatively simple. Take a look at it and understand how it works. This should help you in spinning your own variation of DIC – DaGhostman Dimitrov Jul 30 '14 at 14:40
  • Could you please take a look and my first answer here,is the solution I came up with a few days ago afer I post the question,but no one said anything and I still don't know if is any good of it,I would realy like to hear you opinion about it,if you have some time to see it.Thanks! – Serban Alexandru Jul 30 '14 at 14:58
  • One of the first things I noticed is that you are heavily using reflections, could you explain a bit why? (I haven't reviewed it deeply though) – DaGhostman Dimitrov Jul 30 '14 at 15:20
  • Sure.My DIC doign everything dinamic,he can give an instance of a class you just made because when you ask for instance checks if he already had an instance of that class in $defs if now is check if that class exist and then with reflection I take all his constructor parameters and recall the same function for every one of his constructor parameters(dependencies) and if the object we want doesn't have any parameter in constructor he make new and also put it in $defs so because everything is recursive it gets back and every object is made after his dependencies were take care of. – Serban Alexandru Jul 30 '14 at 15:31
  • And because I so that I will always need reflection I made an array of reflection also so that I don't make a new reflection every time. – Serban Alexandru Jul 30 '14 at 15:31
  • I hope you understand it,I am not so good to explain things :)) if you have other question please ask. – Serban Alexandru Jul 30 '14 at 15:32
  • Well I agree on the dynamic part, but what if you have 2 copies of the same object for different purposes, take Registry class for example? If one is holding the application environment definitions and another holding specific request parameters? Or you need to communicate with 2 different databases? – DaGhostman Dimitrov Jul 30 '14 at 15:47
  • I thought about that to,for the moment I have evrything made with unic instance but for my application I thought at two method one to make an singleton array last say and singleton[Registry]=false/true if this particular object I need always the same instance or always new or the second method I can put a parameter in getInstanceOf to say if I want the same instance or a new one.What you thing about those method and what other better way it is? – Serban Alexandru Jul 30 '14 at 18:20
  • But the rest of it the dinamic part and recursivity and the fact that is doing all for general and can give any class anytime is corect and is made close to the way a framework did it?Because I made this one from 0 and I was curious if I manage to get close to the method pro use because that means a lot for me :) – Serban Alexandru Jul 30 '14 at 18:21
  • I think you will mainly runing issues with the array keys (name collision). Let me think a bit about the container and will get back to you – DaGhostman Dimitrov Jul 30 '14 at 18:25

3 Answers3

5

You may be better off using one of the existing Dependency Containers out there, such as PHP-DI or Pimple. However, if you are looking for a simpler solution, then I've implemented a Dependency Container as part of an article that I wrote here: http://software-architecture-php.blogspot.com/

Here is the code for the container

    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;

}

The above code can be found here: https://github.com/abdulla16/decoupled-app (under the /Container folder)

You can register your dependencies as a singleton, as a type (every time a new object will be instantiated), or as a closure (the container will call the function that you register and that function is expected to return the instance).

For example,

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

This Container resolves properties of a class as well as the constructor parameters.

Abdul
  • 399
  • 5
  • 15
2

I have done a very simple IoC class which works as intended. I've investigated the IoC and DI pattern and especially after reading this answer. Let me know if something is not right or you have any questions .

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

I think the code is pretty self-explanatory, but let me know if something is not clear

Community
  • 1
  • 1
DaGhostman Dimitrov
  • 1,608
  • 20
  • 44
  • I understand the function,my first method was something like this also because something read here but I wanted to make something dinamic ass I said,because I don't want to have to tell IOC every single one class that I may be want for him,I want to ask him a class and he give me without tell him about it before. – Serban Alexandru Jul 31 '14 at 07:24
  • Also you need to tell all the dependencies a object has when you register it and that is the role of a DIC that you don't have to know all the dependencies he has,in a large application it can have 20,30 dependencies you have to say $dic->register('status', function () use ($dic) { return new Baz($dic->get('greeter'),dep2,dep3,dep4,.....); }); I don't what this,I want as I said to do something the same way frameworks did it and with some performance. – Serban Alexandru Jul 31 '14 at 07:26
  • I know that I can't get the same method as the people who made frameworks because they think many time at it,that's why I ask here if anyone knows the pro method ,the method that frameworks use in back and also told me if I am close to it, because I want to understand how things are work in back before use a DIC in a framework I think this way is gone be more easy for me to deal with it on the actual framework. – Serban Alexandru Jul 31 '14 at 07:27
  • So in that case, when automatic dependency resolution is mandatory you will have to comfort with something like annotations parser to resolve them as well as having it to resolve array configurations, like username and password for configurations and is a bit off from keeping everything separated. – DaGhostman Dimitrov Jul 31 '14 at 07:30
  • In case of my DIC class let's say that I just build an User class somewhere in my application know without doing anything I can anywhere in my application get a User instance just like that: $ioc=ioc::getInstance(); $user_object=$ioc->getInstanceOf("User"); that's how I want things to happen but I am not so sure if I choose the best way to do this happens,I am sure there are better ways. – Serban Alexandru Jul 31 '14 at 07:30
  • I know it isn't perfect,I hope someone could give me a hint to the perfect ways,I have some bugs in mine like the fact that I have a foreach in constructor parameters but nothing says that those parameters have always to be dependenc,they can be simple variable also because I use reflection to get the parameters from constructor I can't get the class type of the parameters I can only get their name so if I have a class Bazz who needs a greeter object in constructor I have to put in the Bazz constructor Greeter as parameter so it has the same name as his class and I can do new $parameter->name – Serban Alexandru Jul 31 '14 at 07:34
  • PS: my idea with the Dependency class is to add resolving per method but still handle class instantiation, there is no magic way for you app or any framework to know what was supposed to happen without having to write something to it. (annotations, xml class maps, array definitions and etc.) – DaGhostman Dimitrov Jul 31 '14 at 07:35
  • But if you pun in constructer Greeter $G let say it wont gona work because the reflection object of $G only keeps his name not his original class and the ioc class tries to do new G and actually is new Greeter – Serban Alexandru Jul 31 '14 at 07:36
  • In every framework there is somebody of definition. Take a look at php-di, pimple and dice dic – DaGhostman Dimitrov Jul 31 '14 at 07:39
  • Yes you can,I read on the internet and also see some example,you can get a class without register her before,the IOC class register it when you ask for it and then it looks what dependencies have in constructor and try to get them and so on in a recursive way and it gets back to the initial class with all the dependencies on the tree already get .Imagine that you had a tree of dependencies class A needs B and C and B needs D and D need E and so on ,you going recursive down in the dependency tree and try to return instance of that dependency and when all the tree is complet it gets back – Serban Alexandru Jul 31 '14 at 07:39
  • In theory is realy simple,but I need to find a way to do it without any bugs or situation where it's not gona work,my IOC works perfectly as I said but you have to create some special condition for it like all the dependencies in constructor to have the same name as their class – Serban Alexandru Jul 31 '14 at 07:41
  • You can put it in any of your application and try to get a class and see that will work – Serban Alexandru Jul 31 '14 at 07:41
  • I already use it in 3,4 application with just copy paste ,Is a universal IOC :)) as long as you complete the condition that paremeters to have the same name as their class if Bazz need Green object in constructor you put $Green – Serban Alexandru Jul 31 '14 at 07:42
  • But i try to find a way to get the type of the parameters class not only their name,but without luck until now – Serban Alexandru Jul 31 '14 at 07:43
1

Because I haven't find anything near what I wanted,I tried to implement on my own a container and I want to hear some opinion about how is looking,because I've start to learn php and oop a month ago a feedback is very important for me because I know I have many things to learn,so please feel free to bully my code :))

<!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;
    }

}
?>
Badal
  • 3,738
  • 4
  • 33
  • 60
Serban Alexandru
  • 165
  • 2
  • 13