has($id)) { $entry = $this->entries[$id]; if (is_callable($entry)) { return $entry($this); } $id = $entry; } return $this->resolve($id); } public function has(string $id): bool { return isset($this->entries[$id]); } public function set(string $id, callable|string $concrete): void { $this->entries[$id] = $concrete; } public function resolve(string $id) { // 1. Inspect the class that we are trying to get from the container try { $reflectionClass = new \ReflectionClass($id); } catch (\ReflectionException $e) { throw new \Exception($e->getMessage(), $e->getCode(), $e); } if (!$reflectionClass->isInstantiable()) { throw new \Exception('Class "' . $id . '" is not instantiable'); } // 2. Inspect the constructor of the class $constructor = $reflectionClass->getConstructor(); if (!$constructor) { return new $id; } // 3. Inspect the constructor parameters (dependencies) $parameters = $constructor->getParameters(); if (!$parameters) { return new $id; } // 4. If the constructor parameter is a class then try to resolve that class using the container $dependencies = array_map( function (\ReflectionParameter $param) use ($id) { $name = $param->getName(); $type = $param->getType(); // Check for a default value if ($param->isDefaultValueAvailable()) { return $param->getDefaultValue(); } if (!$type) { throw new \Exception( 'Failed to resolve class "' . $id . '" because param "' . $name . '" is missing a type hint' ); } if ($type instanceof \ReflectionUnionType) { throw new \Exception( 'Failed to resolve class "' . $id . '" because of union type for param "' . $name . '"' ); } if ($type instanceof \ReflectionNamedType && !$type->isBuiltin()) { return $this->get($type->getName()); } throw new \Exception( 'Failed to resolve class "' . $id . '" because invalid param "' . $name . '"' ); }, $parameters ); return $reflectionClass->newInstanceArgs($dependencies); } public function getAllRegisteredClassNames(): array { return array_keys($this->entries); } }