No-framework dependency injection framework

I’ve just read some threads about DI and someone complained about complexity involved with DI. I just wanted to clarify that the concept of DI is simple, the implementations of DI frameworks tend to be complex.

Following is a example how DI could work without any framework level code. With anonymous functions, it’s actually extremely flexible and simple.

Please note that the code is just written down to make some sense but it’s not perfect, nor error free.


$deps = new stdClass;
$deps->db = new DB('user', 'pass', 'etc');
$deps->auth = new Auth();
$deps->user = new User($db, $auth);
$deps->session = new Session('ttl');
$deps->session->setUser($user);
$deps->router = new Router();

$deps->behaviorFactory = function($behaviorClass, $model) {
	return new $behaviorClass($model);
}
$deps->modelFactory = function($modelClass) use(&$deps) {
	return new $modelClass($deps->db, $deps->behaviorFactory);
}
$deps->controllerFactory = function($controllerClass) use(&$deps) {
	return new $controllerClass($deps->session, $deps->modelFactory);
}
$deps->dispatcher = new Dispatcher($deps->controllerFactory, $deps->router);
// start the dispatcher somewhere

If someone is able to figure out some major flaws (in terms of functionality) in that approach, i’m happy to hear, i can’t imagine any right now.

how is that DI? Presumably to get objects for $deps you need to pass $deps around? That’s a registry rather than DI unless i’m missing something.

Nevermind, after a second look I can see what you’re trying to do.

Looks quite clever!

How can you handle situations where certain classes require a new instance of an object? For example if your model class wanted a different version of behaviorFactory.

Isn’t this:

$deps->modelFactory = function($modelClass) use(&$deps) { 
    return new $modelClass($deps->db, $deps->behaviorFactory); 
} 
$deps->controllerFactory = function($controllerClass) use(&$deps) { 
    return new $controllerClass($deps->session, $deps->modelFactory); 
} 
$deps->dispatcher = new Dispatcher($deps->controllerFactory, $deps->router); 
// start the dispatcher somewhere 

[COLOR=#000000][COLOR=#ff8000]

[/COLOR][/COLOR]suppose to be this:

$deps->modelFactory = function($modelClass) use(&$deps) { 
    return new $modelClass($deps->db, $deps->behaviorFactory()); 
} 
$deps->controllerFactory = function($controllerClass) use(&$deps) { 
    return new $controllerClass($deps->session, $deps->modelFactory()); 
} 
$deps->dispatcher = new Dispatcher($deps->controllerFactory(), $deps->router); 
// start the dispatcher somewhere  

?

Meaning instead of passing the anonymous function, you actually pass the result of the function (by calling it)? Or am I missing something?

@tomB, @acid24ro

You both refer to same problem from different perspectives. Note that the factories do accept class names, so you can retrieve different objects with them. This could also be extended with some flow control, asking which class is requested and injecting additional dependencies. It’s just a way to resolve dependencies at “runtime”.

Can you post a little code example of an implementation for a model class or controller class or Dispatcher class?

Not a major flaw, but I do see a small negative effect on using this code: you now always instantiate each and every possible dependency before starting to wire the objects together. It’s a minor issue, but it’s not that every request specifically uses the database, session or user, so this might become a bottleneck? Putting them in a (anonymous) function which returns a new instance when it’s called would probably fix the issue.

Hi…

You could place the instantiation in a block. I kind of think it would be simpler if you always did this. You would then basically have implemented “needle” (an old Ruby DI tool) in PHP. Fabien Potencier does something similar in his PHP talks.

yours, Marcus

Here is my latest incarnation… with optional Reflection usage, as opposed to always using it that the previous version I posted.


class Container
{
	protected $factories;

	function __construct(array $factories = array())
	{
		$this->factories = $factories;
		$this->registerInstance($this, __CLASS__);
	}

	function register($interface, $factory)
	{
		if (is_string($factory))
		{
			$this->register($interface,
				function(Container $container) use ($factory, $interface)
				{
					$factory = $container->getDefaultFactory($factory);
					$container->register($interface, $factory);
					return $factory($container);
				});
		}
		else
		{
			$this->factories[$interface] = $factory;
		}
	}

	function registerInstance($instance, $interface = null)
	{
		$factory = function() use ($instance) { return $instance; };
		if ($interface)
		{
			$this->factories[$interface] = $factory;
		}
		else
		{
			$this->factories[get_class($instance)] = $factory;
			foreach(class_implements($instance) as $interface)
				$this->factories[$interface] = $factory;
			foreach(class_parents($instance) as $interface)
				$this->factories[$interface] = $factory;
		}
		return $instance;
	}

	function registerShared($interface, $factory)
	{
		if (is_string($factory))
		{
			$this->register($interface,
				function(Container $container) use ($factory, $interface)
				{
					$factory = $container->getDefaultFactory($factory);
					return $container->registerInstance($factory($container), $interface);
				});
		}
		else
		{
			$this->register($interface,
				function(Container $container) use ($factory, $interface)
				{
					return $container->registerInstance($factory($container), $interface);
				});
		}
	}

	function create($interface)
	{
		if (isset($this->factories[$interface]))
			$factory = $this->factories[$interface];
		else
			$factory = $this->factories[$interface] = $this->getDefaultFactory($interface);

		return $factory($this);
	}

	function get($interface)
	{
		$container = $this;
		return function() use ($container, $interface)
		{
			return $container->create($interface);
		};
	}

	protected function getDefaultFactory($className)
	{
		$class = new ReflectionClass($className);
		try
		{
			$constructor = $class->getConstructor();
		}
		catch (ReflectionException $exception)
		{
			$constructor = null;
		}
		if ($constructor && $constructor->getNumberOfParameters())
		{
			return function(Container $container) use ($class, $constructor)
			{
				$args = array();
				foreach($constructor->getParameters() as $parameter)
				{
					$parameterClass = $parameter->getClass();
					if ($parameterClass)
						$args[] = $container->create($parameterClass->getName());
					else if ($parameter->isDefaultValueAvailable())
						$args[] = $parameter->getDefaultValue();
					else
						throw new DependencyCreationException($parameter);
				}
				return $class->newInstanceArgs($args);
			};
		}
		else
		{
			return function() use ($className)
			{
				return new $className();
			};
		}
	}
}

Sorry, little late :slight_smile:

Agreed with the closures for lazy loading.

@Ren

How do you handle cases where you need to pass constructor arguments to constructors? Do you try to avoid it in your application?