Featured Image

Dependency Injection using PHP’s Reflection API

So, I had a HUGE problem with my website phanteye.com. Just to give you a little background, it runs on Zend Framework 2 with PHP 5.5. We only use ZF2 for its MVC and I18n components. We don’t use its database functionality. Instead we built our own composer library that contains all of the interfaces to our databases and specialized classes/methods that use them.

We were originally using the MySQL extension. The cause of the problem was because of poor coding that relied on the behavior of the MySQL extension that returns the same MySQL connection link when mysql_connect() is called multiple times with the same parameters. This sounds great until you migrate to the MySQLi extension which does not exhibit this same behavior. MySQLi will create a new connection to your db each time you try to establish a connection. So, when we migrated over to MySQLi, we started getting MySQL errors stating there were too many connections on certain pages… womp womp. Based on this, you can tell another part of the problem was our library was written in a way that causes several of our classes to make a new database connection call whenever it was instantiated… another womp womp.

We have a class called IObject that is extended by other classes and is used for general MySQL functionality (fetching rows from tables, getting column names, etc). It is essentially a model class. It used to look something like this:

Class IObject
(
	public function __construct($objectID = null)
	{
		$this->Db = new Db;
		...
	}
...

Our classes looked like this:

Class User extends IObject
(
	public function __construct($userID = null)
	{
		parent::__construct($userID);
		...
	}
...

We would instantiate our classes like so:

$User = new User(1234);

There were a few ways we could solve this problem:

  1. Let ZF2 create an instance of the db connection and use the ZF2 service locator in our classes
  2. Use an old class we had called Spawn to inject a single Db instance wherever it was needed. Spawn was used to instantiate all other classes using a method called init().
  3. Create our own service locator

No matter which solution we went with, it would require major code rewriting, but we did have one requirement for whatever solution we implemented:

Our library must retain its independence from ZF2. So immediately #1 was out. Option #3 was more than what we needed at the moment. #2 would quickly solve the problem and at the same time give us the ability to implement #3 easily in the future.

So we re-tooled Spawn::init() like so using PHP’s reflection API:

class Spawn
{
	/**
	 * @var Db connection
	 */	
	public $Db;
 
	/**
	 * construct all other class objects needed for this class
	 */	
	public function __construct()
	{
		$this->Db = new Db();
	}
 
	public function init($class)
	{	
		//get info on the __construct method of the class being called
		$m = new \ReflectionMethod($class, '__construct');
		$mParamCount = $m->getNumberOfParameters();
 
		//get the properties of the class being called
		$c = new \ReflectionClass($class);
		$props = $c->getProperties();
 
		//get the $deps property so we can fetch the other dependencies for the class being called
		if($props[0]->name == "deps"):
			$prop = $c->getProperty('deps');
			$deps = $prop->getValue();
		endif;
 
 
		//find the position of the $Spawn parameter in the __construct method so we can inject this instance into it
		foreach($m->getParameters() as $key => $p):
			if($p->name == "Spawn"):
				$spawnPos = $key;
			endif;
		endforeach;
 
		//inject Spawn instance into the correct parameter position
		$i = 0;
		while($mParamCount - 1 >= $i):
			if(isset($spawnPos) && $spawnPos == $i):
				$arg[$spawnPos] = $this;
			else:
				if(@func_get_arg($i + 1)):
					$arg[$i] = func_get_arg($i + 1);
				endif;
			endif;
			$i++;
		endwhile;
 
		$Class = new $class($arg[0], $arg[1], $arg[2], $arg[3]);
 
		if(count($deps)):
			foreach($deps as $name => $dep):
				if($name == "Db"):
					$Class->$name = $this->Db;
				elseif($name == "Spawn"):
					$Class->$name = $this;
				else:
					$Class->$name = $this->init($dep);
				endif;
			endforeach;
		endif;
 
		return $Class;
	}
}

To use this, we have to implement an instance of Spawn inside IObject’s __construct method so it now looks like this:

Class IObject
(
	public function __construct($Spawn = null, $objectID = null)
	{
		if(!($Spawn instanceof Spawn)):
			$this->Spawn = new Spawn();
		else:
			$this->Spawn = $Spawn;
		endif;
 
		$this->setDb($this->Spawn->Db);
	}

For more flexibility, it was built like this to allow classes to be called on their own without instantiating them using Spawn.

Our classes now start off like this:

Class User extends IObject
(
	public function __construct($userID = null, $Spawn = null)
	{
		parent::__construct($Spawn, $this->userID);
	}
...

Now we call our classes like so inside of our ZF2 controllers:

$User = $this->Spawn->init("\PhantEye\User", 1234);

So when this is called, Spawn::init() uses the reflection API to look for a Spawn parameter inside of the __construct method of the class being called. If it finds it, then the Spawn class injects itself into that parameter’s position inside of that class’s __construct() method and the class then passes it into IObject’s __construct() method.

So yes, we’re using constructor injection, but we also have setter injection using a method called setDb() inside the IObject class which you can see in the code above.

BUT WAIT! For those of you who are wondering how we got Spawn into our controllers:

/config/autoload/global.config
...
	'service_manager' => array(
		'invokables' => array(
			'Spawn' => '\PhantEye\Spawn',
		),
	),
...
/module/Application/Module.php

namespace Application;

use Zend\Mvc\ModuleRouteListener;

use Zend\Mvc\MvcEvent;

use Zend\Debug\Debug;

class Module
{
	public function onBootstrap(MvcEvent $e)
	{
		$eventManager->getSharedManager()->attach('Zend\Mvc\Controller\AbstractActionController', 'dispatch', function($e)
		{
			$controller      = $e->getTarget();
			$controller->Spawn = $e->getApplication()->getServiceManager()->get('Spawn');
...

There are some bits I left out. For example each class has a property called $deps which is an array of its dependencies. Spawn::init() looks at $deps and instantiates them and injects them into the class. A service locator would be useful here when a certain class is a dependency for multiple classes being instantiated through Spawn. All of this is to come later as I mentioned though. Honestly, we’re reinventing the wheel, but it’s necessary for us since our library must be able to operate completely independently of any frameworks we decide to use.

Have any of you been in this predicament? How did you solve it?

Any thoughts?