diff --git a/src/Di/DiContainer.php b/src/Di/DiContainer.php index a93fa4da0..51fd4746b 100644 --- a/src/Di/DiContainer.php +++ b/src/Di/DiContainer.php @@ -34,10 +34,7 @@ */ class DiContainer { - /** - * @var array - */ - private array $dependencies = []; + private DiRegistry $registry; /** * @var array @@ -49,6 +46,11 @@ class DiContainer */ private array $resolving = []; + public function __construct(?DiRegistry $registry = null) + { + $this->registry = $registry ?? new DiRegistry(); + } + /** * Register dependencies * @param array $dependencies @@ -56,11 +58,7 @@ class DiContainer */ public function registerDependencies(array $dependencies): void { - foreach ($dependencies as $abstract => $concrete) { - if (!$this->isRegistered($abstract)) { - $this->register($concrete, $abstract); - } - } + $this->registry->registerDependencies($dependencies); } /** @@ -69,21 +67,7 @@ public function registerDependencies(array $dependencies): void */ public function register(string $concrete, ?string $abstract = null): void { - $key = $abstract ?? $concrete; - - if (isset($this->dependencies[$key])) { - throw DiException::dependencyAlreadyRegistered($key); - } - - if (!class_exists($concrete)) { - throw DiException::dependencyNotInstantiable($concrete); - } - - if ($abstract !== null && !class_exists($abstract) && !interface_exists($abstract)) { - throw DiException::invalidAbstractDependency($abstract); - } - - $this->dependencies[$key] = $concrete; + $this->registry->register($concrete, $abstract); } /** @@ -91,7 +75,7 @@ public function register(string $concrete, ?string $abstract = null): void */ public function isRegistered(string $abstract): bool { - return isset($this->dependencies[$abstract]); + return $this->registry->isRegistered($abstract); } /** @@ -123,12 +107,12 @@ public function set(string $abstract, object $instance, bool $override = true): throw DiException::dependencyAlreadyRegistered($abstract); } - if (!$override && isset($this->dependencies[$abstract])) { + if (!$override && $this->registry->isRegistered($abstract)) { throw DiException::dependencyAlreadyRegistered($abstract); } - if (!isset($this->dependencies[$abstract])) { - $this->dependencies[$abstract] = get_class($instance); + if (!$this->registry->isRegistered($abstract)) { + $this->registry->register(get_class($instance), $abstract); } $this->container[$abstract] = $instance; @@ -190,7 +174,7 @@ public function autowire(callable $entry, array $args = []): array public function reset(): void { - $this->dependencies = []; + $this->registry->reset(); $this->resetContainer(); } @@ -212,7 +196,7 @@ private function resolve(string $abstract, array $args = [], bool $singleton = t $this->resolving[$abstract] = true; try { - $concrete = $this->dependencies[$abstract]; + $concrete = $this->registry->getConcrete($abstract); if ($singleton) { if (!isset($this->container[$abstract])) { @@ -279,7 +263,7 @@ private function resolveParameter(ReflectionParameter $param, array &$args = []) $type = $param->getType()->getName(); } - if ($type !== null && isset($this->dependencies[$type])) { + if ($type !== null && $this->registry->isRegistered($type)) { /** @var class-string $type */ return $this->get($type); } diff --git a/src/Di/DiRegistry.php b/src/Di/DiRegistry.php new file mode 100644 index 000000000..7c7822e49 --- /dev/null +++ b/src/Di/DiRegistry.php @@ -0,0 +1,102 @@ + + * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org) + * @link http://quantum.softberg.org/ + * @since 3.0.0 + */ + +namespace Quantum\Di; + +use Quantum\Di\Exceptions\DiException; +use ReflectionClass; + +/** + * DiRegistry Class + * + * Stores dependency bindings for the container. + * + * @package Quantum/Di + */ +class DiRegistry +{ + /** + * @var array + */ + private array $dependencies = []; + + /** + * Register dependencies + * @param array $dependencies + * @throws DiException + */ + public function registerDependencies(array $dependencies): void + { + foreach ($dependencies as $abstract => $concrete) { + if (!$this->isRegistered($abstract)) { + $this->register($concrete, $abstract); + } + } + } + + /** + * Registers new dependency + * @throws DiException + */ + public function register(string $concrete, ?string $abstract = null): void + { + $key = $abstract ?? $concrete; + + if (isset($this->dependencies[$key])) { + throw DiException::dependencyAlreadyRegistered($key); + } + + if (!class_exists($concrete)) { + throw DiException::dependencyNotInstantiable($concrete); + } + + if (!(new ReflectionClass($concrete))->isInstantiable()) { + throw DiException::dependencyNotInstantiable($concrete); + } + + if ($abstract !== null && !class_exists($abstract) && !interface_exists($abstract)) { + throw DiException::invalidAbstractDependency($abstract); + } + + $this->dependencies[$key] = $concrete; + } + + /** + * Checks if a dependency registered + */ + public function isRegistered(string $abstract): bool + { + return isset($this->dependencies[$abstract]); + } + + /** + * @throws DiException + * @return class-string + */ + public function getConcrete(string $abstract): string + { + if (!$this->isRegistered($abstract)) { + throw DiException::dependencyNotRegistered($abstract); + } + + return $this->dependencies[$abstract]; + } + + public function reset(): void + { + $this->dependencies = []; + } +} diff --git a/tests/Unit/Di/DiRegistryTest.php b/tests/Unit/Di/DiRegistryTest.php new file mode 100644 index 000000000..633e6db84 --- /dev/null +++ b/tests/Unit/Di/DiRegistryTest.php @@ -0,0 +1,91 @@ +registry = new DiRegistry(); + } + + public function testRegisterDependency(): void + { + $this->registry->register(Setup::class); + + $this->assertTrue($this->registry->isRegistered(Setup::class)); + $this->assertSame(Setup::class, $this->registry->getConcrete(Setup::class)); + } + + public function testRegisterDependencies(): void + { + $this->registry->registerDependencies([ + \ArrayAccess::class => \ArrayObject::class, + Setup::class => Setup::class, + ]); + + $this->assertSame(\ArrayObject::class, $this->registry->getConcrete(\ArrayAccess::class)); + $this->assertSame(Setup::class, $this->registry->getConcrete(Setup::class)); + } + + public function testAttemptingToRegisterAlreadyRegisteredDependency(): void + { + $this->expectException(DiException::class); + $this->expectExceptionMessage('The dependency `Quantum\Loader\Setup` is already registered.'); + + $this->registry->register(Setup::class); + $this->registry->register(Setup::class); + } + + public function testAttemptingToRegisterNonExistentClass(): void + { + $this->expectException(DiException::class); + $this->expectExceptionMessage('The dependency `NonExistentClass` is not instantiable.'); + + $this->registry->register('NonExistentClass'); + } + + public function testAttemptingToRegisterAbstractClass(): void + { + $this->expectException(DiException::class); + $this->expectExceptionMessage('The dependency `Quantum\\App\\Exceptions\\BaseException` is not instantiable.'); + + $this->registry->register(\Quantum\App\Exceptions\BaseException::class); + } + + public function testAttemptingToRegisterNonExistentAbstract(): void + { + $this->expectException(DiException::class); + $this->expectExceptionMessage('The dependency `NonExistentInterface` is not valid abstract class.'); + + $this->registry->register(Request::class, 'NonExistentInterface'); + } + + public function testGetConcreteThrowsForUnregisteredDependency(): void + { + $this->expectException(DiException::class); + $this->expectExceptionMessage('The dependency `Quantum\Loader\Setup` is not registered.'); + + $this->registry->getConcrete(Setup::class); + } + + public function testResetClearsDependencies(): void + { + $this->registry->register(Setup::class); + + $this->assertTrue($this->registry->isRegistered(Setup::class)); + + $this->registry->reset(); + + $this->assertFalse($this->registry->isRegistered(Setup::class)); + } +}