I wrote a simple Dependency Resolver in PHP that I'm hoping to get some input on.
The main problem that gave me some trouble was when it had to resolve typed arguments ( like type hinting a constructor argument with an interface, I don't remember exactly what they call it ). The only work around was to let the DependencyResolver
know about such dependencies that are not directly resolvable (instantiable).
Note: The class name of this Dependency Resolver
is ObjectManager
// dependencies.php
// this is where I define all my dependencies in case if they are not directly instantiable
$deps = array(
'DependentClass' => array(
'depclass' => 'DependencyClass'
)
);
ObjectManager::getInstance()->setDependencies($deps);
ObjectManager
uses singleton pattern so I can use the same object throughout the flow
// mainfile.php
use DependencyClassInterface;
use AnotherClass;
Class DependentClass {
protected $depClass;
public function __construct(DependencyClassInterface $depclass, AnotherClass $another) {
$this->depClass = $depClass;
}
public function doSomething() { echo "Something"; }
}
ObjectManager::getInstance()
->get('DependentClass')
->doSomething();
And finally the implementation of Dependency Resolver.
<?php
class ObjectManager
{
protected $dependencies;
protected static $omInstance;
public static function getInstance()
{
if (!self::$omInstance) {
self::$omInstance = new ObjectManager();
}
return self::$omInstance;
}
public function setDependencies(array $dependencies)
{
$this->dependencies = $dependencies;
}
public function getDependencies()
{
return $this->dependencies;
}
public function get($class)
{
$finalizedParams = array();
$deps = array();
// check if deps are defined
if (array_key_exists($class, $this->dependencies)) {
$deps = $this->dependencies[$class];
}
$reflector = new ReflectionClass($class);
if (!$reflector->isInstantiable()) {
throw new Exception("Class {$class} is not instantiable");
}
$constructor = $reflector->getConstructor();
if ($constructor == null) {
return $reflector->newInstance();
}
$parameters = $constructor->getParameters();
foreach ($parameters as $key => $parameter) {
$parameterType = $this->getParameterType($parameter);
$parameterName = $parameter->getName();
if ($parameterType == null) {
if ($parameter->isDefaultValueAvailable()) {
$finalizedParams[$key] = $parameter->getDefaultValue();
} else {
throw new Exception("
Failed resolving argument (dependency) \"$parameterName\" for class $class.
Not directly instantiatable.
No default value specified.");
}
continue;
}
$classReflection = new ReflectionClass($parameterType);
// if dep is directly instantiable; save and continue
if ($classReflection->isInstantiable()) {
$finalizedParams[$key] = $classReflection->newInstance();
continue;
}
// if defined in di.php
if (array_key_exists($parameterName, $deps)) {
$argName = $deps[$parameterName];
$finalizedParams[$key] = ObjectManager::getInstance()->get($argName);
continue;
}
}
return $reflector->newInstanceArgs($finalizedParams);
}
protected function getParameterType(ReflectionParameter $parameter)
{
$export = ReflectionParameter::export(
array(
$parameter->getDeclaringClass()->name,
$parameter->getDeclaringFunction()->name,
),
$parameter->name,
true
);
return preg_match('/[>] ([A-z]+) /', $export, $matches)
? $matches[1] : null;
}
}
Hopefully, you got the idea. It might be an overkill to resolve dependencies this way, but I like the option of an Object Manager being able to do that.