Skip to content

Instantly share code, notes, and snippets.

@krisanalfa
Created May 2, 2014 11:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save krisanalfa/e86e007df24f0a4074f0 to your computer and use it in GitHub Desktop.
Save krisanalfa/e86e007df24f0a4074f0 to your computer and use it in GitHub Desktop.
Container which has ability to automatic resolve dependency, injecting dependency, etc
<?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