Home » Insights » Automatic Dependency Injection With PHP’s Reflection API

28th April 2015 in Web Development

Automatic Dependency Injection With PHP’s Reflection API

Diagram of light reflection on a mirror, showing an incident ray at an angle, reflecting off the surface. Arrows indicate the incident and reflected rays, with a perpendicular line marking the normal.

Reflection is the ability to introspect and reverse-engineer functions, classes, methods, and interfaces during runtime. This makes it possible to find specific information about your code, such as a class’s internal properties, methods & even doc blocks for those methods.

One of my favourite uses for the Reflection API is to have all of my class dependencies automatically injected when possible. If you’re new to dependency injection, don’t worry – it’s quite simple. This makes my code cleaner to look at and far more maintainable as the codebase grows. We’ve recently updated our company starter theme/framework to do just this. I figure it’s something I can show you how to do. The complete code & example can be found at the bottom of this post (TL/DR).

Let’s get to work. First, you’ll want to set up a class to perform the automatic resolution. I’ve opted to call mine Resolver due to lack of a better name. This class should contain a method called resolve which takes one property:

class Resolver {

	public function resolve($class)
	{
		
	}
}

We need to create an instance of our reflected class. For this, we can use the ReflectionClass. It should look exactly like the following example:

$reflector = new \ReflectionClass($class);

The $reflector variable now contains various information about our class, such as checking to see if our class is instantiable. For example, we need to ensure people don’t attempt to instantiate an interface or abstract class.

if( ! $reflector->isInstantiable())
{
	throw new \Exception("[$class] is not instantiable");
}

If our class is instantiable, we can proceed to check if it contains a __construct() method. If it doesn’t, then we can just return an instance of the class, as we have no dependencies to inject into it.

if(is_null($constructor))
{
	return new $class;
}

If we have a constructor, we will need to loop through all the parameters and determine if each is a resolvable class. If it is, we can run it back through our initial “resolve” method, allowing us to indefinitely nest our automatic class resolution. If the parameter is not a class, and no default value was supplied, we have nothing left to do but throw an Exception, as we cannot resolve an unknown parameter.

$parameters = $constructor->getParameters();

$dependencies = array();

foreach($parameters as $parameter)
{
	$dependency = $parameter->getClass();
	
	if(is_null($dependency))
	{
		if($parameter->isDefaultValueAvailable())
		{
			$dependencies[] = $parameter->getDefaultValue();
		}
		
		throw new \Exception("Erm.. Cannot resolve the unkown!?");
	}
	else
	{
		$dependencies[] = $this->resolve($dependency->name);
	}
}

If all goes well and no Exceptions errors are thrown, we need to return a new instance of our resolved class. This should be the last line of our resolve method.

return $reflector->newInstanceArgs($dependencies);

And that’s everything. I’ve cleaned up the code a bit, split it into several other methods, and added doc blocks. You should be good to copy/paste this into your project.

class Resolver {

	/**
 	* Build an instance of the given class
	* 
 	* @param string $class
 	* @return mixed
 	*
 	* @throws Exception
 	*/
	public function resolve($class)
	{
		$reflector = new \ReflectionClass($class);

		if( ! $reflector->isInstantiable())
 		{
 			throw new \Exception("[$class] is not instantiable");
 		}
		
 		$constructor = $reflector->getConstructor();
		
 		if(is_null($constructor))
 		{
 			return new $class;
 		}
		
 		$parameters = $constructor->getParameters();
 		$dependencies = $this->getDependencies($parameters);
		
 		return $reflector->newInstanceArgs($dependencies);
	}
	
	/**
	 * Build up a list of dependencies for a given methods parameters
	 *
	 * @param array $parameters
	 * @return array
	 */
	public function getDependencies($parameters)
	{
		$dependencies = array();
		
		foreach($parameters as $parameter)
		{
			$dependency = $parameter->getClass();
			
			if(is_null($dependency))
			{
				$dependencies[] = $this->resolveNonClass($parameter);
			}
			else
			{
				$dependencies[] = $this->resolve($dependency->name);
			}
		}
		
		return $dependencies;
	}
	
	/**
	 * Determine what to do with a non-class value
	 *
	 * @param ReflectionParameter $parameter
	 * @return mixed
	 *
	 * @throws Exception
	 */
	public function resolveNonClass(ReflectionParameter $parameter)
	{
		if($parameter->isDefaultValueAvailable())
		{
			return $parameter->getDefaultValue();
		}
		
		throw new Exception("Erm.. Cannot resolve the unkown!?");
	}
}

Using Our Resolve Class

Now that we’ve put together a class to automatically inject our dependencies, let’s find out how to use it with a simple example. I’ve put together five classes which are, in some way, reliant on one of the others. The Foo class expects to receive instances of both Bar and Baz, Baz expects an instance of Qux, and Qux expects an instance of Norf.

If you copy/paste the following dummy classes into your project, then attempt to resolve them using our new class, you should find that everything automatically injects itself. How cool is that?

class Foo {
	
	protected $bar;
	protected $baz;
	
	public function __construct(Bar $bar, Baz $baz)
	{
		$this->bar = $bar;
		$this->baz = $baz;
	}
}

class Bar {}

class Baz {
	
	protected $qux;
	
	public function __construct(Qux $qux)
	{
		$this->qux = $qux;
	}
}

class Qux {
	
	protected $norf;
	
	public funciton __construct(Norf $norf)
	{
		$this->norf = $norf;
	}
}

class Norf {}

If we weren’t using the Reflection API, we would need to have some code similar to this example in order to create a working instance of Foo.

$foo = new Foo(new Bar, new Baz(new Qux(new Norf)));

But, as we have the power of Reflection at our fingertips we can simply use:

$foo = (new Resolver)->resolve('Foo');

This will also work with name-spaced classes; you simply pass through the full class namespace & class name like so:

$foo = (new Resolver)->resolve('Acme\Repositories\Foo');

Besides the noticeable readability improvement here, we are also gaining a significant improvement in the maintainability of our code. Let’s assume you’ve used the class Foo throughout your project. What happens if Foo is modified to accept a third dependency? Without the Reflection API, you would likely find yourself changing your code in multiple locations (potentially hundreds, if it were a heavily relied-upon class). You don’t have to do that anymore, the Reflection API will do that for you!

If you wanted to take this a step further, we could introduce new functionality to our class, such as the ability to bind interfaces to concrete implementations of these classes or bind a class name to an existing instance of the given class (singleton pattern). Perhaps another article is in order.