$bindings */ private static array $bindings = []; /** * @var array $instances */ private static array $instances = []; /** * Binds an interface to a class to instantiate it instead. * * @param class-string $interfaceName * @param class-string $className * * @return void * @throws Exception If the interface or class does not exist. */ public static function bind(string $interfaceName, string $className): void { if (interface_exists($interfaceName) && class_exists($className)) { static::$bindings[$interfaceName] = $className; return; } throw new Exception('Error at binding non existant Interface or ClassName.'); } /** * Binds multiple interfaces to multiple classes in bulk. * * @param array $bindings * * @return void */ public static function bulkBind(array $bindings): void { foreach ($bindings as $interfaceName => $className) { static::bind($interfaceName, $className); } } /** * Resolves and injects dependencies for a callable and returns its result. * * @param callable $action * * @return mixed * @throws Exception If an unhandled callable type is provided. */ public static function resolve(callable $action): mixed { if ($action instanceof Closure) { // If it's an anonymous function $reflectionCallback = new ReflectionFunction($action); } else { // Array or string with the method if (is_string($action)) { $action = preg_split('/::/', $action); } // Check if it's a method or just a function if (count($action) == 2) { $reflectionCallback = new ReflectionMethod($action[0], $action[1]); } else { $reflectionCallback = new ReflectionFunction($action[0]); } } // Get the parameters return call_user_func_array( $action, static::resolveParameterValues($reflectionCallback->getParameters()) ); } /** * Resolves and injects dependencies for a class and returns its result. * * @param class-string $className * * @return mixed * @throws Exception If a binding is missing for an interface or the class does not exist. */ public static function &resolveInstance(string $className): mixed { if (interface_exists($className)) { if (isset(static::$bindings[$className])) { $className = static::$bindings[$className]; } else { throw new Exception("Missing binding for interface: {$className}"); } } if (!class_exists($className)) { throw new Exception("Cannot resolve: {$className}"); } if (!isset(static::$instances[$className])) { // If the instance has not been resolved before, resolve it $reflectionClass = new ReflectionClass($className); $constructor = $reflectionClass->getConstructor(); static::$instances[$className] = new $className( ...static::resolveParameterValues($constructor?->getParameters() ?? []) ); } return static::$instances[$className]; } /** * Resolves parameter values by injecting dependencies. * * @param array $parameters * * @return array * @throws Exception If a primitive parameter does not have a default value. */ public static function resolveParameterValues(array $parameters): array { $values = []; foreach ($parameters as $parameter) { if ($parameter->isOptional()) { // Always use the default value first $values[] = $parameter->getDefaultValue(); continue; } $paramType = $parameter->getType(); if ($paramType === null) { // If no type is declared, and it's not optional, we cannot resolve it. throw new Exception('Untyped parameters expect at least a default value or must be optional.'); } if ($paramType->isBuiltin()) { throw new Exception("Primitive parameter '{$parameter->getName()}' expects at least a default value."); } $values[] = static::resolveInstance($paramType->getName()); } return $values; } }