*/ private $bindings = []; /** @var array */ private $instances = []; /** * Bind an abstract name (interface or key) to a concrete implementation. * $concrete can be: * - a callable: function(Container $c) { return new Foo(); } * - a string class name: 'App\\Foo' * * @param string $abstract * @param callable|string $concrete * @return void */ public function bind(string $abstract, $concrete): void { $this->bindings[$abstract] = $concrete; } /** * Bind as singleton. The first resolved instance is cached and returned afterwards. * * @param string $abstract * @param callable|string $concrete * @return void */ public function singleton(string $abstract, $concrete): void { $this->bindings[$abstract] = $concrete; // mark as singleton with null placeholder $this->instances[$abstract] = null; } /** * Resolve an abstract to an instance. * * @param string $abstract * @return mixed * @throws \Exception */ public function make(string $abstract) { // return existing singleton instance if already created if (array_key_exists($abstract, $this->instances) && $this->instances[$abstract] !== null) { return $this->instances[$abstract]; } if (!isset($this->bindings[$abstract])) { // if no binding, try to instantiate the abstract directly if it's a class if (class_exists($abstract)) { $object = $this->build($abstract); } else { throw new \Exception("No binding found for [{$abstract}]"); } } else { $concrete = $this->bindings[$abstract]; if (is_callable($concrete)) { // factory receives the container $object = $concrete($this); } elseif (is_string($concrete) && class_exists($concrete)) { $object = $this->build($concrete); } else { throw new \Exception("Invalid binding for [{$abstract}]"); } } // if abstract was registered as singleton, cache the instance if (array_key_exists($abstract, $this->instances)) { $this->instances[$abstract] = $object; } return $object; } /** * Build an instance of the given class resolving constructor dependencies. * * @param string $class * @return object * @throws \Exception */ private function build(string $class) { $reflector = new \ReflectionClass($class); if (!$reflector->isInstantiable()) { throw new \Exception("Class {$class} is not instantiable"); } $constructor = $reflector->getConstructor(); if (is_null($constructor)) { return new $class(); } $params = $constructor->getParameters(); $dependencies = []; foreach ($params as $param) { $type = $param->getType(); // If parameter has a class/interface type, resolve it from container if ($type && !$type->isBuiltin()) { $depClass = $type->getName(); $dependencies[] = $this->make($depClass); continue; } // If default value is available, use it if ($param->isDefaultValueAvailable()) { $dependencies[] = $param->getDefaultValue(); continue; } // Cannot resolve the dependency $name = $param->getName(); throw new \Exception("Unresolvable dependency [\${$name}] in class {$class}"); } return $reflector->newInstanceArgs($dependencies); } }