From 4c2a2b995d67f4ae22d63db650d9b8ac4ef22ddf Mon Sep 17 00:00:00 2001 From: brendt Date: Tue, 7 May 2024 20:07:57 +0200 Subject: [PATCH] Improved dependency injections --- src/Container/Dependency.php | 15 +++++++++++ .../Exceptions/CannotAutowireException.php | 24 ++++++++++------- .../CircularDependencyException.php | 26 +++++++++---------- src/Container/GenericContainer.php | 8 +++--- .../CannotAutowireExceptionTest.php | 4 +-- .../CircularDependencyExceptionTest.php | 8 +++--- 6 files changed, 50 insertions(+), 35 deletions(-) diff --git a/src/Container/Dependency.php b/src/Container/Dependency.php index dc8a197..22c7b70 100644 --- a/src/Container/Dependency.php +++ b/src/Container/Dependency.php @@ -37,6 +37,21 @@ public function equals(self $other): bool return $this->getName() === $other->getName(); } + public function getTypeName(): string + { + $dependency = $this->dependency; + + return match($dependency::class) { + ReflectionClass::class => $dependency->getShortName(), + ReflectionMethod::class => $dependency->getDeclaringClass()->getShortName(), + ReflectionParameter::class => $this->resolveName($dependency->getType()), + ReflectionNamedType::class => $dependency->getName(), + ReflectionIntersectionType::class => $this->intersectionTypeToString($dependency), + ReflectionUnionType::class => $this->unionTypeToString($dependency), + default => 'unknown', + }; + } + private function resolveName(ReflectionType|Reflector|string|Closure $dependency): string { if (is_string($dependency)) { diff --git a/src/Container/Exceptions/CannotAutowireException.php b/src/Container/Exceptions/CannotAutowireException.php index a24290e..6b98b3a 100644 --- a/src/Container/Exceptions/CannotAutowireException.php +++ b/src/Container/Exceptions/CannotAutowireException.php @@ -5,18 +5,19 @@ namespace Tempest\Container\Exceptions; use Exception; +use Tempest\Container\Dependency; use Tempest\Container\DependencyChain; final class CannotAutowireException extends Exception { - public function __construct(DependencyChain $chain) + public function __construct(DependencyChain $chain, Dependency $brokenDependency) { $stack = $chain->all(); $firstDependency = $chain->first(); $lastDependency = $chain->last(); - $message = PHP_EOL . PHP_EOL . "Cannot autowire {$firstDependency->getName()} because {$lastDependency->getName()} cannot be resolved" . PHP_EOL; + $message = PHP_EOL . PHP_EOL . "Cannot autowire {$firstDependency->getName()} because {$brokenDependency->getName()} cannot be resolved" . PHP_EOL; $i = 0; @@ -32,15 +33,18 @@ public function __construct(DependencyChain $chain) $i++; } - $currentDependency = $lastDependency; - $currentDependencyName = $currentDependency->getShortName(); - $firstPart = explode($currentDependencyName, $lastDependency->getShortName())[0] ?? null; - $fillerSpaces = str_repeat(' ', strlen($firstPart) + 3); - $fillerArrows = str_repeat('▒', strlen($currentDependencyName)); - $message .= PHP_EOL . "\t {$fillerSpaces}{$fillerArrows}"; - - $message .= PHP_EOL . PHP_EOL; + $selectionLine = preg_replace_callback( + pattern: '/(?(.*))(?'. $brokenDependency->getTypeName() .'\s\$\w+)(.*)/', + callback: function ($matches) { + return str_repeat(' ', strlen($matches['prefix']) + 4) + . str_repeat('▒', strlen($matches['selection'])); + }, + subject: $chain->last()->getShortName(), + ); + $message .= PHP_EOL; + $message .= "\t{$selectionLine}"; + $message .= PHP_EOL; $message .= "Originally called in {$chain->getOrigin()}"; $message .= PHP_EOL; diff --git a/src/Container/Exceptions/CircularDependencyException.php b/src/Container/Exceptions/CircularDependencyException.php index 7eba7b0..4d39308 100644 --- a/src/Container/Exceptions/CircularDependencyException.php +++ b/src/Container/Exceptions/CircularDependencyException.php @@ -33,21 +33,19 @@ public function __construct(DependencyChain $chain, Dependency $circularDependen $message .= PHP_EOL . "\t{$prefix} " . $currentDependency->getShortName(); } - $circularDependencyName = $circularDependency->getShortName(); - $lastDependencyName = $chain->last()->getShortName(); - - $firstPart = explode($circularDependencyName, $lastDependencyName)[0] ?? null; - - if ($lastDependencyName === $firstPart) { - $fillerLines = str_repeat('─', 3); - } else { - $fillerLines = str_repeat('─', strlen($firstPart) + 3); - } - - $fillerArrows = str_repeat('▒', strlen($circularDependencyName)); - $message .= PHP_EOL . "\t└{$fillerLines}{$fillerArrows}"; - $message .= PHP_EOL . PHP_EOL; + $selectionLine = preg_replace_callback( + pattern: '/(?(.*))(?'. $circularDependency->getTypeName() .'\s\$\w+)(.*)/', + callback: function ($matches) { + return "└" + . str_repeat('─', strlen($matches['prefix']) + 3) + . str_repeat('▒', strlen($matches['selection'])); + }, + subject: $chain->last()->getShortName(), + ); + $message .= PHP_EOL; + $message .= "\t{$selectionLine}"; + $message .= PHP_EOL; $message .= "Originally called in {$chain->getOrigin()}"; $message .= PHP_EOL; diff --git a/src/Container/GenericContainer.php b/src/Container/GenericContainer.php index 2c05818..1335a08 100644 --- a/src/Container/GenericContainer.php +++ b/src/Container/GenericContainer.php @@ -244,8 +244,6 @@ private function autowireDependencies(ReflectionMethod $method, array $parameter private function autowireDependency(ReflectionParameter $parameter, mixed $providedValue = null): mixed { - // $this->resolveChain()->add($parameter); - $parameterType = $parameter->getType(); // If the parameter is a built-in type, immediately skip reflection @@ -281,7 +279,7 @@ private function autowireDependency(ReflectionParameter $parameter, mixed $provi // At this point, there is nothing else we can do; we don't know // how to autowire this dependency. - throw $lastThrowable ?? new CannotAutowireException($this->chain); + throw $lastThrowable ?? new CannotAutowireException($this->chain, new Dependency($parameter)); } private function autowireObjectDependency(ReflectionNamedType $type, mixed $providedValue): mixed @@ -300,7 +298,7 @@ private function autowireObjectDependency(ReflectionNamedType $type, mixed $prov // At this point, there is nothing else we can do; we don't know // how to autowire this dependency. - throw new CannotAutowireException($this->chain); + throw new CannotAutowireException($this->chain, new Dependency($type)); } private function autowireBuiltinDependency(ReflectionParameter $parameter, mixed $providedValue): mixed @@ -335,7 +333,7 @@ private function autowireBuiltinDependency(ReflectionParameter $parameter, mixed // At this point, there is nothing else we can do; we don't know // how to autowire this dependency. - throw new CannotAutowireException($this->chain); + throw new CannotAutowireException($this->chain, new Dependency($parameter)); } private function clone(): self diff --git a/tests/Unit/Container/Exceptions/CannotAutowireExceptionTest.php b/tests/Unit/Container/Exceptions/CannotAutowireExceptionTest.php index 3472449..8d65d38 100644 --- a/tests/Unit/Container/Exceptions/CannotAutowireExceptionTest.php +++ b/tests/Unit/Container/Exceptions/CannotAutowireExceptionTest.php @@ -24,13 +24,13 @@ public function test_autowire_without_exception() $container->get(AutowireA::class); } catch (CannotAutowireException $exception) { - $this->assertStringContainsString('Cannot autowire Tests\Tempest\Unit\Container\Fixtures\AutowireA::__construct because Tests\Tempest\Unit\Container\Fixtures\AutowireC::__construct cannot be resolved', $exception->getMessage()); + $this->assertStringContainsString('Cannot autowire Tests\Tempest\Unit\Container\Fixtures\AutowireA::__construct because string cannot be resolved', $exception->getMessage()); $expected = <<<'TXT' ┌── AutowireA::__construct(AutowireB $b) ├── AutowireB::__construct(AutowireC $c) └── AutowireC::__construct(ContainerObjectA $other, string $unknown) - ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ + ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ TXT; $this->assertStringContainsString($expected, $exception->getMessage()); diff --git a/tests/Unit/Container/Exceptions/CircularDependencyExceptionTest.php b/tests/Unit/Container/Exceptions/CircularDependencyExceptionTest.php index 5636924..b19ef8a 100644 --- a/tests/Unit/Container/Exceptions/CircularDependencyExceptionTest.php +++ b/tests/Unit/Container/Exceptions/CircularDependencyExceptionTest.php @@ -16,7 +16,7 @@ */ class CircularDependencyExceptionTest extends TestCase { - public function test_circular_dependency_test() + public function test_circular_dependency_test(): void { $this->expectException(CircularDependencyException::class); @@ -31,7 +31,7 @@ public function test_circular_dependency_test() ┌─► CircularA::__construct(ContainerObjectA $other, CircularB $b) │ CircularB::__construct(CircularC $c) │ CircularC::__construct(ContainerObjectA $other, CircularA $a) - └───▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ + └───────────────────────────────────────────────────▒▒▒▒▒▒▒▒▒▒▒▒ TXT; $this->assertStringContainsString($expected, $exception->getMessage()); @@ -42,7 +42,7 @@ public function test_circular_dependency_test() } } - public function test_circular_dependency_as_a_child_test() + public function test_circular_dependency_as_a_child_test(): void { $this->expectException(CircularDependencyException::class); @@ -58,7 +58,7 @@ public function test_circular_dependency_as_a_child_test() ┌─► CircularA::__construct(ContainerObjectA $other, CircularB $b) │ CircularB::__construct(CircularC $c) │ CircularC::__construct(ContainerObjectA $other, CircularA $a) - └───▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ + └───────────────────────────────────────────────────▒▒▒▒▒▒▒▒▒▒▒▒ TXT; $this->assertStringContainsString($expected, $exception->getMessage());