manually. * * @param string $class The class to make. Dependencies are injected. * @param bool $register Made classes are registered in the container on * true. Useful for optimization on multi-used classes. * @param bool $registerDependencies Made dependency classes are registered * in the container on true. Useful for optimization on multi-used classes. * * @throws \Exception If the target is not instantiable or a dependency cannot be resolved. * @throws \ReflectionException If reflection fails. */ public function make(string $class, bool $register = true, bool $registerDependencies = true): object { $reflector = new \ReflectionClass($class); if ($reflector->isInstantiable() === false) { throw new \Exception("Target [{$class}] is not instantiable."); } $constructor = $reflector->getConstructor(); if ($constructor === null) { return new $class(); } $arguments = []; $parameters = $constructor->getParameters(); foreach ($parameters as $parameter) { $type = $parameter->getType(); // No type hinted: allow default value, otherwise we cannot resolve. if ($type === null) { if ($parameter->isDefaultValueAvailable()) { $arguments[] = $parameter->getDefaultValue(); continue; } throw new \Exception(sprintf( 'Cannot resolve untyped parameter $%s for [%s] without a default value.', $parameter->getName(), $class )); } // For PHP 7.4 only ReflectionNamedType exists (no unions). if ($type instanceof \ReflectionNamedType === false) { throw new \Exception(sprintf( 'Unsupported parameter type for $%s in [%s].', $parameter->getName(), $class )); } // If nullable and no default, we still must supply something; if ($type->isBuiltin()) { if ($parameter->isDefaultValueAvailable()) { $arguments[] = $parameter->getDefaultValue(); continue; } throw new \Exception(sprintf( 'Cannot autowire builtin parameter $%s (%s) for [%s]. Provide a default or register a factory.', $parameter->getName(), $type->getName(), $class )); } $dependencyClass = $type->getName(); if (interface_exists($dependencyClass) && isset($this->contextualBindings[$class][$dependencyClass])) { $dependencyClass = $this->contextualBindings[$class][$dependencyClass]; } // Inject the current container, never a new one. if ($dependencyClass === self::class) { throw new \Exception(sprintf( 'Cannot resolve App container dependency for $%s in [%s] to prevent circular dependencies.', $parameter->getName(), $class )); } // Using get() will also resolve dependencies of dependencies $dependency = $this->get($dependencyClass); if ($registerDependencies === true) { $this->instances[$dependencyClass] = $dependency; } $arguments[] = $dependency; } $made = new $class(...$arguments); if ($register) { $this->instances[$class] = $made; } return $made; } }