Automatic Dependency Injection With PHP’s Reflection API
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 classes 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, then 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 to. The full code & example can be found at the bottom of this post (TL/DR).
Lets get to work. First you’ll want to setup 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 go ahead an check to see 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 do have a constructor, we will need to loop through all of the parameters and determine if each parameter is a resolvable class. If it is, we can run it back through our initial “resolve” method allowing us to indefinately nest our automatic class resoultion. 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 unkown 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
were thrown, we just 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 a few 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, lets find out how to use it with a simple example. I’ve put together 5 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 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 obvious readability improvement here, we are gaining a huge improvement in how maintainable our code has become. Lets assume you’ve used the class Foo
throughout your project, what happens if Foo
was modified to accept a third dependency? Well, without the Reflection API you would likely find yourself modifying your code in multiple locations (potentially hundreds, if it were a heavily relied upon class). You don’t have to do that anymore, 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.
Leave A Comment
Great article.
Reflection-based DI is something I’m looking to use in a project I’m working on. I’ve been looking at Auryn, but I like your minimal resolver class.
So, my question is; I have a service class that requires several dependencies that all implement the same interface (they’re database mapper classes). Currently I have it working by instantiating the concrete classes in a controller and then passing them into the service class constructor. The service class constructor declares its dependencies by interface.
I’d like to be able to use reflection to do the DI but short of making a new interface for each type of mapper I can’t figure out how it could work.
Any ideas?
Thanks Alan,
I can think of 2 potential ways of doing this. The first method I don’t like and probably isn’t a good option but i’ll list it anyway incase it sparks more ideas..
1. You can use reflection to inspect the individual variable names. So you could attach a particular implementation of an interface based on the name of the variable (and if it has the Mapper interface). So if you have a class
MapperA
you could inject that wherever the variable$mapperA
existed.2. Another option would be a little more manual, but you could pre-define the dependencies of a particular class beforehand and then bind them into the resolver class. Then whenever you request your service class, it can check to see if you bound anything into the resolver class, and give you that back. This would still allow you to type hint with interfaces. I’ve put a usage example together below which I hope makes enough sense. Of course, you’ll have to modify the resolver class for this functionality.
Hi Christopher,
Thanks for the suggestions. I think there’s no way to do what I want other than to have an interface for each type of mapper class, so I’m using your second suggestion but using interfaces instead of abstract classes. It does make more sense that way as each type of mapper (I’ve since discovered) can offer specialised queries on top the common one’s they all supply.