Created
May 2, 2014 11:55
-
-
Save krisanalfa/e86e007df24f0a4074f0 to your computer and use it in GitHub Desktop.
Container which has ability to automatic resolve dependency, injecting dependency, etc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class BindingResolutionException extends Exception {} | |
class SuperContainer implements ArrayAccess { | |
protected $resolved = array(); | |
protected $bindings = array(); | |
protected $instances = array(); | |
protected $reboundCallbacks = array(); | |
protected $resolvingCallbacks = array(); | |
protected $globalResolvingCallbacks = array(); | |
/******************************************************************************************************************/ | |
protected function bound($abstract) | |
{ | |
return isset($this[$abstract]) || isset($this->instances[$abstract]); | |
} | |
protected function getClosure($abstract, $concrete) | |
{ | |
return function($container) use ($abstract, $concrete) { | |
$method = ($abstract == $concrete) ? 'build' : 'make'; | |
return $container->$method($concrete); | |
}; | |
} | |
protected function share(Closure $closure) | |
{ | |
return function($container) use ($closure) { | |
static $object; | |
$object = $object ?: $closure($container); | |
return $object; | |
}; | |
} | |
protected function rebound($abstract) | |
{ | |
$instance = $this->make($abstract); | |
foreach ($this->getReboundCallbacks($abstract) as $callback) { | |
call_user_func($callback, $this, $instance); | |
} | |
} | |
protected function getReboundCallbacks($abstract) | |
{ | |
return (isset($this->reboundCallbacks[$abstract])) ? $this->reboundCallbacks[$abstract] : array(); | |
} | |
protected function getConcrete($abstract) | |
{ | |
return (isset($this->bindings[$abstract])) ? $this->bindings[$abstract]['concrete'] : $abstract; | |
} | |
protected function build($concrete, $parameters = array()) | |
{ | |
if ($concrete instanceof Closure) return $concrete($this, $parameters); | |
$reflector = new ReflectionClass($concrete); | |
if (! $reflector->isInstantiable()) { | |
$message = "Dependency [$concrete] is not instantiable."; | |
throw new BindingResolutionException($message); | |
} | |
$constructor = $reflector->getConstructor(); | |
if (is_null($constructor)) return new $concrete; | |
$dependencies = $constructor->getParameters(); | |
$parameters = $this->keyParametersByArgument($dependencies, $parameters); | |
$instances = $this->getDependencies($dependencies, $parameters); | |
return $reflector->newInstanceArgs($instances); | |
} | |
protected function getDependencies($parameters, array $primitives = array()) | |
{ | |
$dependencies = array(); | |
foreach ($parameters as $parameter) { | |
$dependency = $parameter->getClass(); | |
if (array_key_exists($parameter->name, $primitives)) { | |
$dependencies[] = $primitives[$parameter->name]; | |
} elseif (is_null($dependency)) { | |
$dependencies[] = $this->resolveNonClass($parameter); | |
} else { | |
$dependencies[] = $this->resolveClass($parameter); | |
} | |
} | |
return (array) $dependencies; | |
} | |
protected function resolveNonClass(ReflectionParameter $parameter) | |
{ | |
if ($parameter->isDefaultValueAvailable()) { | |
return $parameter->getDefaultValue(); | |
} else { | |
$message = "Unresolvable dependency resolving [$parameter]."; | |
throw new BindingResolutionException($message); | |
} | |
} | |
protected function resolveClass(ReflectionParameter $parameter) | |
{ | |
try { | |
return $this->make($parameter->getClass()->name); | |
} catch (BindingResolutionException $e) { | |
if ($parameter->isOptional()) { | |
return $parameter->getDefaultValue(); | |
} else { | |
throw $e; | |
} | |
} | |
} | |
protected function keyParametersByArgument(array $dependencies, array $parameters) | |
{ | |
foreach ($parameters as $key => $value) { | |
if (is_numeric($key)) { | |
unset($parameters[$key]); | |
$parameters[$dependencies[$key]->name] = $value; | |
} | |
} | |
return $parameters; | |
} | |
protected function fireResolvingCallbacks($abstract, $object) | |
{ | |
if (isset($this->resolvingCallbacks[$abstract])) { | |
$this->fireCallbackArray($object, $this->resolvingCallbacks[$abstract]); | |
} | |
$this->fireCallbackArray($object, $this->globalResolvingCallbacks); | |
} | |
protected function fireCallbackArray($object, array $callbacks) | |
{ | |
foreach ($callbacks as $callback) { | |
call_user_func($callback, $object, $this); | |
} | |
} | |
protected function isShared($abstract) | |
{ | |
if (isset($this->bindings[$abstract]['shared'])) { | |
$shared = $this->bindings[$abstract]['shared']; | |
} else { | |
$shared = false; | |
} | |
return isset($this->instances[$abstract]) || $shared === true; | |
} | |
protected function isBuildable($concrete, $abstract) | |
{ | |
return $concrete === $abstract || $concrete instanceof Closure; | |
} | |
/******************************************************************************************************************/ | |
public function bind($abstract, $concrete = null, $shared = false) | |
{ | |
unset($this->instances[$abstract]); | |
$concrete = $concrete ?: $abstract; | |
if (! $concrete instanceof Closure) { | |
$concrete = $this->getClosure($abstract, $concrete); | |
} | |
$bound = $this->bound($abstract); | |
$this->bindings[$abstract] = compact('concrete', 'shared'); | |
if ($bound) { | |
$this->rebound($abstract); | |
} | |
} | |
public function bindShared($abstract, Closure $closure) | |
{ | |
return $this->bind($abstract, $this->share($closure), true); | |
} | |
public function singleton($abstract, $concrete = null) | |
{ | |
return $this->bind($abstract, $concrete, true); | |
} | |
public function make($abstract, $parameters = array()) | |
{ | |
$this->resolved[$abstract] = true; | |
if (isset($this->instances[$abstract])) return $this->instances[$abstract]; | |
$concrete = $this->getConcrete($abstract); | |
if ($this->isBuildable($concrete, $abstract)) { | |
$object = $this->build($concrete, $parameters); | |
} else { | |
$object = $this->make($concrete, $parameters); | |
} | |
if ($this->isShared($abstract)) $this->instances[$abstract] = $object; | |
$this->fireResolvingCallbacks($abstract, $object); | |
return $object; | |
} | |
/******************************************************************************************************************/ | |
public function offsetExists($key) | |
{ | |
return isset($this->bindings[$key]); | |
} | |
public function offsetGet($key) | |
{ | |
return $this->make($key); | |
} | |
public function offsetSet($key, $value) | |
{ | |
$value = ($value instanceof Closure) ? $value : $value = function() use ($value) { return $value; }; | |
$this->bind($key, $value); | |
} | |
public function offsetUnset($key) | |
{ | |
unset($this->bindings[$key]); | |
unset($this->instances[$key]); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment