PHP Dependency Injection

Simple but powerfull dependency injector based on phpdoc. It maps classes and instances into a injector instance. To inject classes and instances into a object instance just call the $injector->injectInto($instance);. This is a very simple aproach but when is used in a application si very powerfull.

class A {}
class B {}
class C { public $name; }

//THE BOOTSTRAP PART
$injector = Injector::getInstance();
$injector->mapClass("A");
$injector->mapSingleton("B");

$c = new C();
$c->name = "C Class";

$injector->mapInstance($c);

class YourClass
{
	/**
	 * @var B
	 */
	public $b_class;

	/**
	 * @var C
	 */
	public $c_class;

	public function init()
	{
	//use here $this->c_class and b_class
	}
}

$yc = new YourClass();

$injector->injectInto($yc);

$yc->init();

The injector takes all public properties and check the @var phpdoc param, if params match a class maped in the injector the value is passed to the property. Dependincy injection reduces significantly the the code you write, you dont need to create or pass instances for all classes just put it in the injector map set the php doc and thats all.

Click on the show source to view the injector class. If you have some questions just leave me a comment.

<?php

/**
 * Injector map item
 *
 * @autor		Albulescu Cosmin <albulescu.cosmin@gmail.com>
 * @version		1.0
 */
class Injector_Item
{

	/**
	 * The classname
	 * @var string
	 */
	public $class;

	/**
	 * The class is singleton
	 * @var boolean
	 */
	public $singleton;

	/**
	 * Holds instance name
	 * @var mixed
	 */
	public $instance;

	/**
	 * Array with params of constructing class
	 * @var array
	 */
	public $params;

	/**
	 * New injector map item
	 * @param string $class
	 * @param boolean $singleton
	 * @param object $instance
	 * @param array $params
	 */
	public function __construct($class, $singleton = false, $instance = null, $params = array())
	{
		$this->class = $class;
		$this->singleton = $singleton;
		$this->instance = $instance;
		$this->params = $params;
	}
}

/**
 * Injector manager
 *
 * @autor		Albulescu Cosmin <albulescu.cosmin@gmail.ro>
 * @version		1.0
 */
class Injector
{
	private static $_instance = null;

    /**
     * @return Injector
     */
    public static function getInstance()
    {
        if (null === self::$_instance)
        {
            self::$_instance = new self();
        }

        return self::$_instance;
    }

    private $map = array();

    /**
     * Map singleton class
     * @param string $class
     * @throws Exception
     */
    public function mapSingleton($class)
    {
    	if(!is_string($class))
    	{
    		throw new Exception("Class name must be a string");
    	}

    	$this->map[$class] = new Injector_Item($class, true);
    }

    /**
     * Map a class
     * @param string $class
     * @throws Exception
     */
    public function mapClass($class, $params = array())
    {
    	if(!is_string($class))
    	{
    		throw new Exception("Class name must be a string");
    	}

    	$this->map[$class] = new Injector_Item($class, false, null, $params);
    }

    /**
     * Map a object instance
     * @param object $instance
     * @throws Exception
     */
    public function mapInstance($instance)
    {
    	if(!is_object($instance))
    	{
    		throw new Exception("Instance must be an object " .gettype($instance). " given");
    	}

    	$item = new Injector_Item(get_class($instance), true, $instance);

    	$this->map[get_class($instance)] = $item;
    }

    /**
     * Get mapped instance of an object
     * @param string $class
     * @return mixed
     */
    public function getMappedInstance($class)
    {
    	if($this->hasMap($class))
    	{
    		$item = $this->map[ $class ];

    		if($item->singleton)
    		{
    			if(!$item->instance)
    			{
    				$item->instance = new $class;
    			}

    			return $item->instance;
    		}
    		else
    		{
	    		$r = new ReflectionClass( $item->class );

				if($r->hasMethod("__construct"))
				{
					$instance = $r->newInstance( $item->params );
				}
				else
				{
					$instance = $r->newInstance();
				}

				return $instance;
    		}
    	}

    	return null;
    }

    /**
     * Check if class mapped
     * @param string $class
     * @return boolean
     */
    public function hasMap($class)
    {
    	return array_key_exists($class, $this->map);
    }

    /**
     * Create new instance of a class with injected properties
     * @param string $class
     * @param array $params
     * @return mixed|NULL
     */
    public function newInstance($class, $params = array())
    {
    	if(class_exists($class,true))
    	{
    		$r = new ReflectionClass($class);

			if($r->hasMethod("__construct"))
			{
				$instance = $r->newInstance($params);
			}
			else
			{
				$instance = $r->newInstance();
			}

			$this->injectInto($instance);

			return $instance;
    	}

    	return null;
    }

    /**
     * Inject dependinces into a object instances
     * @param object $instance
     */
    public function injectInto($instance)
    {
    	$r = new ReflectionClass($instance);
    	$properties = $r->getProperties(ReflectionProperty::IS_PUBLIC);

    	foreach($properties as $property)
    	{
    		$doc = $property->getDocComment();

    		$doc =  trim(preg_replace('/\r|\r\n *\* */', '', $doc));

    		$lines = explode("\n", $doc);

    		//remove start and and of comment block
    		array_shift($lines);
    		array_pop($lines);

    		$propertyName = $property->getName();

    		foreach($lines as $line)
    		{
    			preg_match_all('/@([a-z]+)\s+(.*?)\s*(?=$|@[a-z]+\s)/s', $line, $matches);

    			if(count($matches) >=3 && count($matches[1]) && count($matches [2]))
    			{
	    			$info = array_combine($matches[1], $matches[2]);

	    			if(isset($info['var'])) {
	    				if($this->hasMap($info['var'])) {
	    					$instance->$propertyName = $this->getMappedInstance($info['var']);
	    				}

	    				break;//stop iterating, var found and injected if has a reference
	    			}
    			}
    		}
    	}
    }

    /**
     * Clear the map
     */
    public function clearMap()
    {
    	$this->map = array();
    }
}

Leave a Comment.