<?php
class Database {
public function query($query) {
return "Database#query($query) result.";
}
}
class SimpleKeyValCache implements Cache {
private $caches = array();
public function get($key) {
if (!array_key_exists($key, $this->caches)) {
throw new Exception("Key Does not exist");
}
return $this->caches[$key];
}
public function set($key, $value) {
$this->caches[$key] = $value;
}
}
class UselessKeyValCache implements Cache {
public function get($key) {
throw new Exception("Key Does not exist");
}
public function set($key, $value) {
// NOOP;
}
}
interface Cache {
public function get($key);
public function set($key, $value);
}
interface CacheManager {
public function get($key);
}
class SimpleCacheManager implements CacheManager {
private $cache;
private $db;
public function __construct(Cache $cache, Database $db) {
$this->cache = $cache;
$this->db = $db;
}
public function get($key) {
try {
return $this->cache->get($key);
} catch (Exception $e) {
$value = $this->db->query("$key");
$this->cache->set($key, $value);
return $value;
}
}
}
class Api {
private $manager;
public function __construct(CacheManager $manager) {
$this->manager = $manager;
}
public function getResponse() {
$result["A"] = $this->manager->get("One");
$result["B"] = $this->manager->get("Two");
$result["C"] = $this->manager->get("One");
return $result;
}
}
class DoStuff {
private $api;
public function __construct(Api $api, array $scalarValue) {
$this->api = $api;
}
public function doThatStuff() {
var_dump($this->api->getResponse());
}
}
class DoSomeOtherStuff {
private $api;
public function __construct(Api $api) {
$this->api = $api;
}
public function doThatOtherStuff() {
var_dump($this->api->getResponse() + $this->api->getResponse());
}
}
class ConstructorInjectionContainer {
private $types;
private $config;
public function __construct($config) {
$this->config = $config;
}
/**
* Construct a type using configuration information.
*
* The worst case time complexity of this algorithm is O(n^n) as we are making n recursive calls in n loops. Although in practice this _should_ never be a huge issue as n will never be larger than the sum of all dependency's constructor's arguments.
*
*/
public function resolve($type, $overrides = null) {
// TODO: error handling for invalid types.
// Get the concrete type name.
$concreteType = $this->config[$type]["concrete"];
// Reflect the concrete type so we can dynamically create arguments and subsequently instantiate it.
$reflectedType = new ReflectionClass($concreteType);
$constructor = $reflectedType->getConstructor();
$paramArray = array();
// If the class has a constructor implementation then generate and instantiate the dependencies dynamically.
if ($constructor !== null) {
$constructorParams = $constructor->getParameters();
foreach($constructorParams as $param) {
$paramArray[$param->name] = $this->getParamValue($type, $param, $overrides);
}
}
// PHP 5.3.0 - 5.3.3 compatibility hack
if (count($paramArray) === 0) {
return $reflectedType->newInstance();
}
return $reflectedType->newInstanceArgs($paramArray);
}
private function getParamValue($type, $param, $overrides) {
// Check overrides first, then check config.
if ($overrides !== null && isset($overrides[$type]) && array_key_exists($param->name, $overrides[$type])) {
return $overrides[$type][$param->name];
} elseif (array_key_exists($param->name, $this->config[$type]["constructor"])) {
return $this->resolve($this->config[$type]["constructor"][$param->name], $overrides);
} elseif (array_key_exists($this->config[$type]["concrete"], $this->config)) {
return $this->resolve($this->config[$type]["concrete"], $overrides);
} else {
throw new Exception("Value for $type constructor parameter {$param->name} was not defined in overrides or configuration.");
}
}
}
$config = <<<'JSON'
{
"Cache": {
"concrete": "SimpleKeyValCache",
"constructor": {
}
},
"CacheManager": {
"concrete": "SimpleCacheManager",
"constructor": {
"cache": "Cache"
}
},
"Api": {
"concrete": "Api",
"constructor": {
"manager": "CacheManager"
}
},
"DoStuff": {
"concrete": "DoStuff",
"constructor": {
"api": "Api"
}
},
"DoSomeOtherStuff": {
"concrete": "DoSomeOtherStuff",
"constructor": {
"api": "CacheManager"
}
}
}
JSON;
$di = new ConstructorInjectionContainer(json_decode($config, true));
$database = new Database();
$dostuff = $di->resolve("DoStuff", array(
"CacheManager" => array(
"db" => $database
),
"DoStuff" => array(
"scalarValue" => array()
)
)
)->doThatStuff();
exit;