phpで簡単DiContainer

<?php

require_once 'Zend/Reflection/Method.php';

/**
 *
 * cocktail is ioc container
 *
 * @author aileron
 */
class Cocktail
{
	/**
	 * @param $module
	 * @return Cocktail_Injector
	 */
	public static function getInjector(&$module)
	{
		if($module instanceof Cocktail_Module)
		{
			$binder = new Cocktail_Binder();
			$module->configure($binder);
			return new Cocktail_Injector($binder);
		}

		if($module instanceof Cocktail_Binder)
		{
			return new Cocktail_Injector($module);
		}
	}
}

/**
 * @author aileron
 */
class Cocktail_Binder
{
	/**
	 * @param $name
	 * @return Cocktail_BinderTo
	 */
	function __get($name)
	{
		return new Cocktail_BinderTo($this,$name);
	}

	/**
	 * @var Array
	 */
	public $classMap = Array();

	/**
	 * @var Array
	 */
	public $instanceMap = Array();
}

/**
 * @author aileron
 */
class Cocktail_BinderTo
{
	/**
	 * @param $name
	 * @param $arg
	 */
	function __set($name,$arg)
	{
		switch ($name)
		{
			case 'to': $this->binder->classMap[$this->name] = $arg; break;
			case 'toInstance': $this->binder->instanceMap[$this->name] = $arg; break;
		}
	}

	/**
	 * @param $instance
	 */
	function toInstance(&$instance)
	{
		$this->binder->instanceMap[$this->name] = &$instance;
	}

	/**
	 * @param $className
	 * @param $filepath
	 */
	function to($className,$filepath=null)
	{
		$this->binder->classMap[$this->name] = Array( $className, $filepath );
	}

	/**
	 * @param Cocktail_Binder $self
	 * @param String $name
	 */
	function __construct(Cocktail_Binder &$binder,$name)
	{
		$this->name = strtolower($name);
		$this->binder = &$binder;
	}

	/**
	 * @var Cocktail_Binder
	 */
	private $binder;

	/**
	 * @var String
	 */
	private $name;
}

/**
 * @author aileron
 */
class Cocktail_Injector
{
	/**
	 * @param $name
	 * @return mixed
	 */
	function __get($name)
	{
		return $this->getInstance($name);
	}

	/**
	 * @param $name
	 * @return mixed
	 */
	private function getInstance($name)
	{
		$name = strtolower($name);
		$value = @$this->binder->instanceMap[$name];
		if(!empty($value))
		{
			return $value ;
		}
		$class = @$this->binder->classMap[$name];
		if(empty($class))
		{
			throw new Exception("undefined binding [$name]" );
		}

		/*
		 * ファイルパスが指定されていた時に、requireする
		 */
		if(!empty($class[1]))
		{
			require $class[1];
		}

		$args = Array();
		$constructor = null;
		try
		{
			$constructor = new Zend_Reflection_Method($class[0], '__construct');
		}
		catch(Exception $e)
		{
		}

		if(!empty($constructor))
		{
			foreach($constructor->getParameters() as $key => $p)
			{
				if (!$docblock = $p->getDeclaringFunction()->getDocblock())
				{
					throw new Exception("none docblok");
				}
				$params = $docblock->getTags('param');
				if (!isset($params[$p->getPosition()]))
				{
					throw new Exception("none Type");
				}
				$type = $params[$p->getPosition()]->getType();
				$args[] = $this->getInstance($type);
			}
			$ref = new ReflectionClass($class[0]);
			$value = $ref->newInstanceArgs($args);
		}
		else
		{
			$value = new $class[0] ;
		}



		$this->binder->instanceMap[$name] = &$value;
		return $value;
	}

	/**
	 * @param $binder
	 */
	function __construct(Cocktail_Binder &$binder)
	{
		$this->binder =&$binder ;
	}

	/**
	 * @var Cocktail_Binder
	 */
	private $binder ;
}

/**
 * @author aileron
 */
interface Cocktail_Module
{
	/**
	 * @param Cocktail_Binder $binder
	 * @return void
	 */
	function configure(Cocktail_Binder &$binder);
}

テストコード

<?php
require 'Cocktail.php';

class Db{}

interface Count
{
	function count(); 
}
class CountImpl implements Count
{ 
	private $i=0; 

	/**
	 * @param Db $db
	 */
	function __construct(Db &$db)
	{
		$db->test = 'test';
	}

	function count(){ return $this->i++; }
}

class CountModule implements Cocktail_Module
{
	function configure(Cocktail_Binder &$binder)
	{
		$binder->Db->to('Db');
		$binder->count->to('CountImpl');
	}
}

$injector = Cocktail::getInjector(new CountModule());

$count = $injector->count ;

print $count->count() . PHP_EOL ;
print $count->count() . PHP_EOL ;
print $count->count() . PHP_EOL ;
print $count->count() . PHP_EOL ;

print $injector->db->test;

リフレクションに、Zend_Reflectionを使用
とりあえず、コンストラクタインジェクションだけサポートしてみた。

あと、適当なイニシャライズメソッドを使った
メソッドインジェクションも対応すれば、okかな。