Code Coverage |
||||||||||||||||
Lines |
Branches |
Paths |
Functions and Methods |
Classes and Traits |
||||||||||||
| Total | |
100.00% |
68 / 68 |
|
97.22% |
35 / 36 |
|
34.09% |
15 / 44 |
|
85.71% |
6 / 7 |
CRAP | |
0.00% |
0 / 1 |
| Creator | |
100.00% |
68 / 68 |
|
97.22% |
35 / 36 |
|
34.09% |
15 / 44 |
|
100.00% |
7 / 7 |
99.74 | |
100.00% |
1 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| create | |
100.00% |
38 / 38 |
|
95.83% |
23 / 24 |
|
20.00% |
7 / 35 |
|
100.00% |
1 / 1 |
50.47 | |||
| resolveConstructor | |
100.00% |
13 / 13 |
|
100.00% |
4 / 4 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| applyCallAttributes | |
100.00% |
13 / 13 |
|
100.00% |
4 / 4 |
|
66.67% |
2 / 3 |
|
100.00% |
1 / 1 |
2.15 | |||
| getReflectionClass | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| container | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| creator | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Celemas\Wire; |
| 6 | |
| 7 | use Celemas\Wire\Exception\WireException; |
| 8 | use Override; |
| 9 | use Psr\Container\ContainerInterface as Container; |
| 10 | use ReflectionClass; |
| 11 | use Throwable; |
| 12 | |
| 13 | /** @psalm-api */ |
| 14 | class Creator implements CreatorInterface |
| 15 | { |
| 16 | use ResolvesAbstractFunctions; |
| 17 | |
| 18 | /** @var array<class-string, ReflectionClass> */ |
| 19 | private static array $reflectionCache = []; |
| 20 | |
| 21 | public function __construct( |
| 22 | protected readonly Container|WireContainer|null $container = null, |
| 23 | ) {} |
| 24 | |
| 25 | /** @param class-string $class */ |
| 26 | #[Override] |
| 27 | public function create( |
| 28 | string $class, |
| 29 | array $predefinedArgs = [], |
| 30 | array $predefinedTypes = [], |
| 31 | ?callable $injectCallback = null, |
| 32 | string $constructor = '', |
| 33 | ): object { |
| 34 | $createdByWire = true; |
| 35 | |
| 36 | if ($constructor !== '') { |
| 37 | // Factory method: wrap reflection lookup, let invocation bubble |
| 38 | try { |
| 39 | $rmethod = self::getReflectionClass($class)->getMethod($constructor); |
| 40 | } catch (Throwable $e) { |
| 41 | throw new WireException( |
| 42 | 'Unresolvable: ' . $class . '::' . $constructor . ' - ' . $e->getMessage(), |
| 43 | previous: $e, |
| 44 | ); |
| 45 | } |
| 46 | |
| 47 | $args = $this->resolveArgs( |
| 48 | $rmethod, |
| 49 | predefinedArgs: $predefinedArgs, |
| 50 | predefinedTypes: $predefinedTypes, |
| 51 | injectCallback: $injectCallback, |
| 52 | ); |
| 53 | $instance = $rmethod->invoke(null, ...$args); |
| 54 | } elseif ($this->container && $this->container->has($class)) { |
| 55 | if (is_a($this->container, WireContainer::class)) { |
| 56 | /** @psalm-suppress MixedAssignment */ |
| 57 | $value = $this->container->definition($class); |
| 58 | |
| 59 | if (is_string($value) && class_exists($value)) { |
| 60 | $instance = $this->resolveConstructor( |
| 61 | $value, |
| 62 | $predefinedArgs, |
| 63 | $predefinedTypes, |
| 64 | $injectCallback, |
| 65 | ); |
| 66 | } else { |
| 67 | $createdByWire = false; |
| 68 | /** @psalm-suppress MixedAssignment */ |
| 69 | $instance = $this->container->get($class); |
| 70 | } |
| 71 | } else { |
| 72 | $createdByWire = false; |
| 73 | /** @psalm-suppress MixedAssignment */ |
| 74 | $instance = $this->container->get($class); |
| 75 | } |
| 76 | } else { |
| 77 | $instance = $this->resolveConstructor( |
| 78 | $class, |
| 79 | $predefinedArgs, |
| 80 | $predefinedTypes, |
| 81 | $injectCallback, |
| 82 | ); |
| 83 | } |
| 84 | |
| 85 | assert(is_object($instance), 'Created instance must be an object'); |
| 86 | |
| 87 | if (!$createdByWire) { |
| 88 | return $instance; |
| 89 | } |
| 90 | |
| 91 | return $this->applyCallAttributes($instance, $predefinedTypes, $injectCallback); |
| 92 | } |
| 93 | |
| 94 | /** @param class-string $class */ |
| 95 | protected function resolveConstructor( |
| 96 | string $class, |
| 97 | array $predefinedArgs, |
| 98 | array $predefinedTypes, |
| 99 | ?callable $injectCallback, |
| 100 | ): object { |
| 101 | try { |
| 102 | $rcls = self::getReflectionClass($class); |
| 103 | } catch (Throwable $e) { |
| 104 | throw new WireException( |
| 105 | 'Unresolvable: ' . $class . ' - ' . $e->getMessage(), |
| 106 | previous: $e, |
| 107 | ); |
| 108 | } |
| 109 | |
| 110 | $args = new ConstructorResolver($this)->resolve( |
| 111 | $rcls, |
| 112 | predefinedArgs: $predefinedArgs, |
| 113 | predefinedTypes: $predefinedTypes, |
| 114 | injectCallback: $injectCallback, |
| 115 | ); |
| 116 | |
| 117 | return $rcls->newInstance(...$args); |
| 118 | } |
| 119 | |
| 120 | protected function applyCallAttributes( |
| 121 | object $instance, |
| 122 | array $predefinedTypes = [], |
| 123 | ?callable $injectCallback = null, |
| 124 | ): object { |
| 125 | $callAttrs = self::getReflectionClass($instance::class)->getAttributes(Call::class); |
| 126 | |
| 127 | // See if the attribute itself has one or more Call attributes. If so, |
| 128 | // resolve/autowire the arguments of the method it states and call it. |
| 129 | foreach ($callAttrs as $callAttr) { |
| 130 | $callAttr = $callAttr->newInstance(); |
| 131 | $methodToResolve = $callAttr->method; |
| 132 | |
| 133 | /** @var callable $callable */ |
| 134 | $callable = [$instance, $methodToResolve]; |
| 135 | $args = new CallableResolver($this)->resolve( |
| 136 | $callable, |
| 137 | predefinedArgs: $callAttr->args, |
| 138 | predefinedTypes: $predefinedTypes, |
| 139 | injectCallback: $injectCallback, |
| 140 | ); |
| 141 | $callable(...$args); |
| 142 | } |
| 143 | |
| 144 | return $instance; |
| 145 | } |
| 146 | |
| 147 | /** @param class-string $class */ |
| 148 | private static function getReflectionClass(string $class): ReflectionClass |
| 149 | { |
| 150 | return self::$reflectionCache[$class] ??= new ReflectionClass($class); |
| 151 | } |
| 152 | |
| 153 | #[Override] |
| 154 | public function container(): ?Container |
| 155 | { |
| 156 | return $this->container; |
| 157 | } |
| 158 | |
| 159 | #[Override] |
| 160 | public function creator(): CreatorInterface |
| 161 | { |
| 162 | return $this; |
| 163 | } |
| 164 | } |
Below are the source code lines that represent each code branch as identified by Xdebug. Please note a branch is not
necessarily coterminous with a line, a line may contain multiple branches and therefore show up more than once.
Please also be aware that some branches may be implicit rather than explicit, e.g. an if statement
always has an else as part of its logical flow even if you didn't write one.
| 22 | protected readonly Container|WireContainer|null $container = null, |
| 23 | ) {} |
| 121 | object $instance, |
| 122 | array $predefinedTypes = [], |
| 123 | ?callable $injectCallback = null, |
| 124 | ): object { |
| 125 | $callAttrs = self::getReflectionClass($instance::class)->getAttributes(Call::class); |
| 126 | |
| 127 | // See if the attribute itself has one or more Call attributes. If so, |
| 128 | // resolve/autowire the arguments of the method it states and call it. |
| 129 | foreach ($callAttrs as $callAttr) { |
| 129 | foreach ($callAttrs as $callAttr) { |
| 129 | foreach ($callAttrs as $callAttr) { |
| 130 | $callAttr = $callAttr->newInstance(); |
| 129 | foreach ($callAttrs as $callAttr) { |
| 130 | $callAttr = $callAttr->newInstance(); |
| 131 | $methodToResolve = $callAttr->method; |
| 132 | |
| 133 | /** @var callable $callable */ |
| 134 | $callable = [$instance, $methodToResolve]; |
| 135 | $args = new CallableResolver($this)->resolve( |
| 136 | $callable, |
| 137 | predefinedArgs: $callAttr->args, |
| 138 | predefinedTypes: $predefinedTypes, |
| 139 | injectCallback: $injectCallback, |
| 140 | ); |
| 141 | $callable(...$args); |
| 142 | } |
| 143 | |
| 144 | return $instance; |
| 145 | } |
| 156 | return $this->container; |
| 157 | } |
| 28 | string $class, |
| 29 | array $predefinedArgs = [], |
| 30 | array $predefinedTypes = [], |
| 31 | ?callable $injectCallback = null, |
| 32 | string $constructor = '', |
| 33 | ): object { |
| 34 | $createdByWire = true; |
| 35 | |
| 36 | if ($constructor !== '') { |
| 38 | try { |
| 39 | $rmethod = self::getReflectionClass($class)->getMethod($constructor); |
| 40 | } catch (Throwable $e) { |
| 41 | throw new WireException( |
| 42 | 'Unresolvable: ' . $class . '::' . $constructor . ' - ' . $e->getMessage(), |
| 36 | if ($constructor !== '') { |
| 37 | // Factory method: wrap reflection lookup, let invocation bubble |
| 38 | try { |
| 39 | $rmethod = self::getReflectionClass($class)->getMethod($constructor); |
| 40 | } catch (Throwable $e) { |
| 41 | throw new WireException( |
| 42 | 'Unresolvable: ' . $class . '::' . $constructor . ' - ' . $e->getMessage(), |
| 43 | previous: $e, |
| 44 | ); |
| 45 | } |
| 46 | |
| 47 | $args = $this->resolveArgs( |
| 54 | } elseif ($this->container && $this->container->has($class)) { |
| 54 | } elseif ($this->container && $this->container->has($class)) { |
| 54 | } elseif ($this->container && $this->container->has($class)) { |
| 55 | if (is_a($this->container, WireContainer::class)) { |
| 57 | $value = $this->container->definition($class); |
| 58 | |
| 59 | if (is_string($value) && class_exists($value)) { |
| 59 | if (is_string($value) && class_exists($value)) { |
| 59 | if (is_string($value) && class_exists($value)) { |
| 59 | if (is_string($value) && class_exists($value)) { |
| 59 | if (is_string($value) && class_exists($value)) { |
| 59 | if (is_string($value) && class_exists($value)) { |
| 59 | if (is_string($value) && class_exists($value)) { |
| 60 | $instance = $this->resolveConstructor( |
| 55 | if (is_a($this->container, WireContainer::class)) { |
| 56 | /** @psalm-suppress MixedAssignment */ |
| 57 | $value = $this->container->definition($class); |
| 58 | |
| 59 | if (is_string($value) && class_exists($value)) { |
| 60 | $instance = $this->resolveConstructor( |
| 61 | $value, |
| 62 | $predefinedArgs, |
| 63 | $predefinedTypes, |
| 64 | $injectCallback, |
| 65 | ); |
| 66 | } else { |
| 67 | $createdByWire = false; |
| 55 | if (is_a($this->container, WireContainer::class)) { |
| 54 | } elseif ($this->container && $this->container->has($class)) { |
| 55 | if (is_a($this->container, WireContainer::class)) { |
| 56 | /** @psalm-suppress MixedAssignment */ |
| 57 | $value = $this->container->definition($class); |
| 58 | |
| 59 | if (is_string($value) && class_exists($value)) { |
| 60 | $instance = $this->resolveConstructor( |
| 61 | $value, |
| 62 | $predefinedArgs, |
| 63 | $predefinedTypes, |
| 64 | $injectCallback, |
| 65 | ); |
| 66 | } else { |
| 67 | $createdByWire = false; |
| 68 | /** @psalm-suppress MixedAssignment */ |
| 69 | $instance = $this->container->get($class); |
| 70 | } |
| 71 | } else { |
| 72 | $createdByWire = false; |
| 54 | } elseif ($this->container && $this->container->has($class)) { |
| 77 | $instance = $this->resolveConstructor( |
| 78 | $class, |
| 79 | $predefinedArgs, |
| 80 | $predefinedTypes, |
| 81 | $injectCallback, |
| 82 | ); |
| 83 | } |
| 84 | |
| 85 | assert(is_object($instance), 'Created instance must be an object'); |
| 85 | assert(is_object($instance), 'Created instance must be an object'); |
| 86 | |
| 87 | if (!$createdByWire) { |
| 88 | return $instance; |
| 91 | return $this->applyCallAttributes($instance, $predefinedTypes, $injectCallback); |
| 92 | } |
| 162 | return $this; |
| 163 | } |
| 148 | private static function getReflectionClass(string $class): ReflectionClass |
| 149 | { |
| 150 | return self::$reflectionCache[$class] ??= new ReflectionClass($class); |
| 151 | } |
| 96 | string $class, |
| 97 | array $predefinedArgs, |
| 98 | array $predefinedTypes, |
| 99 | ?callable $injectCallback, |
| 100 | ): object { |
| 101 | try { |
| 102 | $rcls = self::getReflectionClass($class); |
| 103 | } catch (Throwable $e) { |
| 104 | throw new WireException( |
| 105 | 'Unresolvable: ' . $class . ' - ' . $e->getMessage(), |
| 110 | $args = new ConstructorResolver($this)->resolve( |
| 111 | $rcls, |
| 112 | predefinedArgs: $predefinedArgs, |
| 113 | predefinedTypes: $predefinedTypes, |
| 114 | injectCallback: $injectCallback, |
| 115 | ); |
| 116 | |
| 117 | return $rcls->newInstance(...$args); |
| 118 | } |