src/Di/Container.php
<?php declare(strict_types=1); namespace Peak\Di; use Peak\Di\Binding\Factory;use Peak\Di\Binding\Prototype;use Peak\Di\Binding\Singleton;use Peak\Di\Exception\ClassDefinitionNotFoundException;use Psr\Container\ContainerInterface;use Psr\Container\NotFoundExceptionInterface; use function array_search;use function call_user_func_array;use function class_implements;use function get_class;use function is_array; class Container implements ContainerInterface{ /** * Container object instances collection * @var array */ protected $instances = []; /** * Classes namespace alias * @var array */ protected $aliases = []; /** * Container object interfaces collection * @var array */ protected $interfaces = []; /** * Class instance creator * @var \Peak\Di\ClassInstantiator */ protected $instantiator; /** * Container object instances collection * @var \Peak\Di\ClassResolver */ protected $resolver; /** * Class definitions resolver * @var BindingResolver */ protected $binding_resolver; /** * Allow container to resolve automatically for your object needed * @var bool */ protected $auto_wiring = true; /** * Container configuration definitions * @var array */ protected $definitions = []; /** * Constructor */ public function __construct() { $this->instantiator = new ClassInstantiator(); $this->resolver = new ClassResolver(); $this->binding_resolver = new BindingResolver(); } /** * Instantiate a class * * The generated instance is not stored, but may use stored * instance(s) as dependency when needed * * @param string $class * @param array $args * @param null $explicit * @return mixed|object * @throws ClassDefinitionNotFoundException * @throws Exception\AmbiguousResolutionException * @throws Exception\InterfaceNotFoundException * @throws \ReflectionException */ public function create(string $class, $args = [], $explicit = null) { // check first for definition even if auto wiring is on $def = $this->getDefinition($class); if ($def === null && !$this->auto_wiring) { throw new ClassDefinitionNotFoundException($class); } elseif ($def !== null) { return $this->binding_resolver->resolve( $this->getDefinition($class), $this, $args, $explicit ); } // process class dependencies with reflection $args = $this->resolver->resolve($class, $this, $args, $explicit); // instantiate class with resolved dependencies and args if apply return $this->instantiator->instantiate($class, $args); } /** * Similar to instantiate(), it call a method on specified object * * @param array $callback * @param array $args * @param null $explicit * @return mixed * @throws ClassDefinitionNotFoundException * @throws Exception\AmbiguousResolutionException * @throws Exception\InterfaceNotFoundException * @throws \ReflectionException */ public function call(array $callback, array $args = [], $explicit = null) { // process class dependencies $args = $this->resolver->resolve($callback, $this, $args, $explicit); return call_user_func_array($callback, $args); } /** * Same as create() but also store the created object before returning it * * @param string $class * @param array $args * @param null $explicit * @return mixed|object * @throws ClassDefinitionNotFoundException * @throws Exception\AmbiguousResolutionException * @throws Exception\InterfaceNotFoundException * @throws \ReflectionException */ public function createAndStore(string $class, array $args = [], $explicit = null) { $object = $this->create($class, $args, $explicit); $this->set($object); return $object; } /** * Resolve a stored definition * * @param string $definition * @param array $args * @return mixed * @throws ClassDefinitionNotFoundException */ public function resolve(string $definition, array $args = []) { $def = $this->getDefinition($definition); if ($def === null) { throw new ClassDefinitionNotFoundException($definition); } return $this->binding_resolver->resolve($def, $this, $args); } /** * Has object instance * * @param string $id * @return bool */ public function has($id) { return isset($this->instances[$id]); } /** * Get an instance if exists, otherwise try to create it return null * * @param string $id * @return mixed|object * @throws ClassDefinitionNotFoundException * @throws Exception\AmbiguousResolutionException * @throws Exception\InterfaceNotFoundException * @throws \ReflectionException */ public function get($id) { if ($this->has($id)) { return $this->instances[$id]; } elseif ($this->hasAlias($id) && $this->has($this->aliases[$id])) { return $this->instances[$this->aliases[$id]]; } return $this->create($id); } /** * Set an object instance. Chainable * * @param object $object * @param string|null $alias * @return Container */ public function set(object $object, string $alias = null) { $class = get_class($object); $this->instances[$class] = $object; if (isset($alias)) { $this->addAlias($alias, $class); } $interfaces = class_implements($object); if (is_array($interfaces)) { foreach ($interfaces as $i) { $this->addInterface($i, $class); } } return $this; } /** * Delete an instance if exists. * * @param string $id * @return Container */Function `delete` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring. public function delete($id) { if ($this->has($id)) { //remove instance unset($this->instances[$id]); //remove interface reference if exists foreach ($this->interfaces as $int => $classes) { $key = array_search($id, $classes); if ($key !== false) { unset($classes[$key]); $this->interfaces[$int] = $classes; } } //remove instance from singleton binding if ($this->hasDefinition($id)) { $definition = $this->getDefinition($id); if ($definition instanceof Singleton) { $definition->deleteStoredInstance(); } } } return $this; } /** * Add a class alias * * @param string $name * @param string $className * @return $this */ public function addAlias(string $name, string $className) { $this->aliases[$name] = $className; return $this; } /** * Has an alias * * @param string $name * @return boolean */ public function hasAlias(string $name) { return isset($this->aliases[$name]); } /** * Add container itself * * @return $this */ public function addItself() { return $this->set($this); } /** * Get all stored instances * * @return array */ public function getInstances() { return $this->instances; } /** * Catalogue also class interface when using add * * @param string $name * @param string $class */ protected function addInterface(string $name, string $class) { if (!$this->hasInterface($name)) { $this->interfaces[$name] = $class; return; } $interfaces = $this->getInterface($name); if (!is_array($interfaces)) { $interfaces = [$interfaces]; } if (!in_array($class, $interfaces)) { $interfaces[] = $class; $this->interfaces[$name] = $interfaces; } } /** * Has an interface * * @param string $name * @return bool */ public function hasInterface(string $name) { return isset($this->interfaces[$name]); } /** * Get an interface * * @param string $name * @return mixed|null */ public function getInterface(string $name) { if ($this->hasInterface($name)) { return $this->interfaces[$name]; } return null; } /** * Get all stored interfaces * * @return array */ public function getInterfaces() { return $this->interfaces; } /** * Set class definition * * @param string $name * @param mixed $definition * @return $this */ public function setDefinition(string $name, $definition) { $this->definitions[$name] = $definition; return $this; } /** * Set definitions. Use definitions when autowiring is off * * @param array $definitions * @return $this */ public function setDefinitions(array $definitions) { $this->definitions = $definitions; return $this; } /** * Has a definition * * @param string $name * @return bool */ public function hasDefinition(string $name) { return isset($this->definitions[$name]); } /** * Get a definition * * @param string $name * @return mixed */ public function getDefinition(string $name) { if ($this->hasDefinition($name)) { return $this->definitions[$name]; } return null; } /** * @return array */ public function getDefinitions(): array { return $this->definitions; } /** * Add a singleton definition * * @param string $name * @param mixed $definition * @return $this */ public function bindSingleton(string $name, $definition) { $this->definitions[$name] = new Singleton($name, $definition); return $this; } /** * Add a groups of singletons definitions * * @param array<string, mixed> $singletons * @return $this */ public function bindSingletons(array $singletons) { foreach ($singletons as $name => $definition) { $this->bindSingleton($name, $definition); } return $this; } /** * Add a prototype definition * * @param string $name * @param mixed $definition * @return $this */ public function bindPrototype(string $name, $definition) { $this->definitions[$name] = new Prototype($name, $definition); return $this; } /** * Add a groups of prototypes definitions * * @param array<string, mixed> $prototypes * @return $this */ public function bindPrototypes(array $prototypes) { foreach ($prototypes as $name => $definition) { $this->bindPrototype($name, $definition); } return $this; } /** * Add a factory definition * * @param string $name * @param mixed $definition * @return $this */ public function bindFactory(string $name, $definition) { $this->definitions[$name] = new Factory($name, $definition); return $this; } /** * Add a groups of factories definitions * * @param array<string, mixed> $factories * @return $this */ public function bindFactories(array $factories) { foreach ($factories as $name => $definition) { $this->bindFactory($name, $definition); } return $this; } /** * Enable Auto Wiring * * @return $this */ public function enableAutoWiring() { $this->auto_wiring = true; return $this; } /** * Disable Auto Wiring * * @return $this */ public function disableAutoWiring() { $this->auto_wiring = false; return $this; }}