21 private array $services = [];
22 private array $instantiated = [];
25 private array $callStack = [];
27 private function __construct()
30 private function __clone()
35 if (!isset(self::$instance))
37 self::$instance =
new self();
40 return self::$instance;
62 if (!isset($configuration[
'className']) && !isset($configuration[
'constructor']))
64 throw $this->buildBadRegistrationExceptions($id);
67 $furtherClassMetadata = $configuration[
'className'] ?? $configuration[
'constructor'];
69 $this->services[$id] = [$furtherClassMetadata, $configuration[
'constructorParams'] ?? []];
79 $configuration = Configuration::getInstance($moduleName);
80 $services = $configuration[
'services'] ?? [];
100 $configuration = Configuration::getInstance();
101 $services = $configuration[
'services'] ?? [];
113 public function has(
string $id): bool
115 return isset($this->services[$id]) || isset($this->instantiated[$id]);
126 public function get(
string $id): mixed
128 if (isset($this->instantiated[$id]))
130 return $this->instantiated[$id];
133 if ($this->isInterfaceKey($id) || $this->isAbstractClassKey($id))
135 $object = $this->resolveInterfaceOrAbstractClass($id);
137 elseif ($this->isClassKey($id))
139 $object = $this->resolveClass($id);
143 $object = $this->createItemByServiceName($id);
146 $this->instantiated[$id] = $object;
154 implements NotFoundExceptionInterface {}
158 private function buildBadRegistrationExceptions(
string $id): SystemException|ContainerExceptionInterface
161 "Could not register service {{$id}}." .
162 "There is no {className} to find class or {constructor} to build instance."
165 return new class(
$message) extends SystemException implements ContainerExceptionInterface {};
168 private function resolveInterfaceOrAbstractClass(
string $id): object
170 [$classOrClosure, $args] = $this->services[$id];
171 if ($classOrClosure instanceof Closure)
173 return $classOrClosure(...(is_array($args) ? $args : []));
176 return $this->createItemByClassName($classOrClosure);
179 private function resolveClass(
string $id): object
181 if (!class_exists($id))
183 throw $this->buildNotFoundException(
"Could not find service by code {$id}.");
185 return $this->createItemByClassName($id);
188 private function createItemByClassName(
string $className): object
192 return $this->createObjectWithFullConstruct($className);
194 catch (ReflectionException $exception)
196 throw new ServiceNotFoundException(
197 $exception->getMessage()
205 private function createItemByServiceName(
string $serviceName): object
207 [$class, $args] = $this->services[$serviceName];
209 if ($class instanceof Closure)
214 if ($args instanceof Closure)
219 return new $class(...array_values($args));
222 private function checkCircularDependency(
string $className): void
224 if ($this->isCallStacked($className))
226 $path = implode(
' -> ', $this->callStack) .
" -> $className";
228 throw new CircularDependencyException(
229 'Cyclic dependency detected for service: ' .
$path
233 $this->addCallStack($className);
236 private function getConstructorParams(ReflectionClass $class):
array
238 $constructor = $class->getConstructor();
239 if ($constructor !==
null && !$constructor->isPublic())
241 throw new ServiceNotFoundException(
242 $class->getName() .
' constructor must be is public'
246 return $constructor?->getParameters() ?? [];
249 private function resolveConstructorDependencies(
array $params,
string $className):
array
256 $paramsForClass = [];
259 $type = $param->getType();
260 if (!
$type instanceof ReflectionNamedType)
262 throw new ServiceNotFoundException(
263 $className .
' All parameters in the constructor must have real class type'
267 $classNameInParams =
$type->getName();
268 if (!class_exists($classNameInParams) && !interface_exists($classNameInParams))
270 throw new ServiceNotFoundException(
271 "For {$className} error in params: {$classNameInParams} must be an existing class, interface or abstract class"
275 $paramsForClass[] = $this->
get($classNameInParams);
278 return $paramsForClass;
281 private function createInstance(ReflectionClass $class,
array $paramsForClass,
string $className): object
283 $object = $class->newInstanceArgs($paramsForClass);
286 throw new ServiceNotFoundException(
287 'Failed to create component ' . $className
294 private function createObjectWithFullConstruct(
string $className): object
298 $this->checkCircularDependency($className);
300 $class =
new ReflectionClass($className);
301 $params = $this->getConstructorParams($class);
304 return new $className();
307 $paramsForClass = $this->resolveConstructorDependencies(
$params, $className);
309 return $this->createInstance($class, $paramsForClass, $className);
313 $this->popCallStack();
317 private function addCallStack(
string $className): void
319 $this->callStack[] = $className;
322 private function popCallStack(): void
324 array_pop($this->callStack);
327 private function isCallStacked(
string $className): bool
329 return in_array($className, $this->callStack,
true);
332 private function isInterfaceKey(
string $id): bool
334 return interface_exists($id) && isset($this->services[$id]);
337 private function isAbstractClassKey(
string $id): bool
339 return isset($this->services[$id]) && class_exists($id) && (
new ReflectionClass($id))->isAbstract();
342 private function isClassKey(
string $id): bool
344 return !isset($this->services[$id]);
addInstance(string $code, mixed $service)
registerByModuleSettings(string $moduleName)
addInstanceLazy(string $id, $configuration)
if( $daysToExpire >=0 &&$daysToExpire< 60 elseif)( $daysToExpire< 0)