Test Failed
Push — main ( cdf2b9...aa3274 )
by Thomas
13:21
created

Registry::resolveEntry()   B

Complexity

Conditions 11
Paths 15

Size

Total Lines 68
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 32
CRAP Score 11

Importance

Changes 0
Metric Value
eloc 35
c 0
b 0
f 0
dl 0
loc 68
ccs 32
cts 32
cp 1
rs 7.3166
cc 11
nc 15
nop 1
crap 11

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace FiveOrbs\Registry;
6
7
use Closure;
8
use FiveOrbs\Registry\Entry;
9
use FiveOrbs\Registry\Exception\NotFoundException;
10
use FiveOrbs\Wire\CallableResolver;
11
use FiveOrbs\Wire\Creator;
12
use FiveOrbs\Wire\Exception\WireException;
13
use Psr\Container\ContainerInterface as Container;
14
15
/**
16
 * @psalm-api
17
 *
18
 * @psalm-type EntryArray = array<never, never>|array<string, Entry>
19
 */
20
class Registry implements Container
21
{
22
	protected Creator $creator;
23
	protected readonly ?Container $wrappedContainer;
24
25
	/** @psalm-var EntryArray */
26
	protected array $entries = [];
27
28
	/** @psalm-var array<never, never>|array<non-empty-string, self> */
29
	protected array $tags = [];
30 36
31
	public function __construct(
32
		public readonly bool $autowire = true,
33
		?Container $container = null,
34
		protected readonly string $tag = '',
35 36
		protected readonly ?Registry $parent = null,
36 36
	) {
37 36
		if ($container) {
38
			$this->wrappedContainer = $container;
39
			$this->add(Container::class, $container);
40 12
			$this->add($container::class, $container);
41
		} else {
42 12
			$this->wrappedContainer = null;
43
			$this->add(Container::class, $this);
44
		}
45
		$this->add(Registry::class, $this);
46 1
		$this->creator = new Creator($this);
47
	}
48 1
49
	public function has(string $id): bool
50 1
	{
51 1
		return isset($this->entries[$id]) || $this->parent?->has($id) || $this->wrappedContainer?->has($id);
52
	}
53
54 1
	/** @psalm-return list<string> */
55 1
	public function entries(bool $includeRegistry = false): array
56 1
	{
57
		$keys = array_keys($this->entries);
58
59 5
		if ($includeRegistry) {
60
			return $keys;
61 5
		}
62
63
		return array_values(array_filter($keys, function ($item) {
64 25
			return $item !== Container::class && !is_subclass_of($item, Container::class);
65
		}));
66 25
	}
67
68
	public function entry(string $id): Entry
69 25
	{
70 20
		return $this->entries[$id];
71
	}
72
73
	public function get(string $id): mixed
74
	{
75 5
		$entry = $this->entries[$id] ?? null;
76 1
77
		try {
78
			if ($entry) {
79
				return $this->resolveEntry($entry);
80 5
			}
81 5
82
			if ($this->wrappedContainer?->has($id)) {
83 4
				return $this->wrappedContainer->get($id);
84 2
			}
85
86
			// We are in a tag. See if the $id can be resolved by the parent
87 3
			// be registered on the root.
88
			if ($this->parent) {
89
				return $this->parent->get($id);
90
			}
91
92
			// Autowiring: $id does not exists as an entry in the registry
93 36
			if ($this->autowire && class_exists($id)) {
94
				return $this->creator->create($id);
95
			}
96
		} catch (WireException $e) {
97 36
			throw new NotFoundException('Unresolvable id: ' . $id . ' - Details: ' . $e->getMessage());
98 36
		}
99
100 36
		throw new NotFoundException('Unresolvable id: ' . $id);
101
	}
102
103 1
	/**
104
	 * @psalm-param non-empty-string $id
105
	 */
106 1
	public function add(
107
		string $id,
108 1
		mixed $value = null,
109
	): Entry {
110
		$entry = new Entry($id, $value ?? $id);
111
		$this->entries[$id] = $entry;
112 3
113
		return $entry;
114 3
	}
115 3
116
	public function addEntry(
117
		Entry $entry,
118 3
	): Entry {
119
		$this->entries[$entry->id] = $entry;
120
121 4
		return $entry;
122
	}
123 4
124
	/** @psalm-param non-empty-string $tag */
125 4
	public function tag(string $tag): Registry
126
	{
127 2
		if (!isset($this->tags[$tag])) {
128
			$this->tags[$tag] = new self(tag: $tag, parent: $this);
129 2
		}
130 2
131 1
		return $this->tags[$tag];
132
	}
133
134 2
	public function new(string $id, mixed ...$args): object
135
	{
136 2
		$entry = $this->entries[$id] ?? null;
137
138
		if ($entry) {
139
			/** @var mixed */
140
			$value = $entry->definition();
141 2
142
			if (is_string($value)) {
143 1
				if (interface_exists($value)) {
144
					return $this->new($value, ...$args);
145
				}
146 1
147
				if (class_exists($value)) {
148
					/** @psalm-suppress MixedMethodCall */
149 17
					return new $value(...$args);
150
				}
151 17
			}
152 1
		}
153
154
		if (class_exists($id)) {
155 1
			/** @psalm-suppress MixedMethodCall */
156 1
			return new $id(...$args);
157 1
		}
158
159
		throw new NotFoundException('Cannot instantiate ' . $id);
160 17
	}
161 16
162
	protected function callAndReify(Entry $entry, mixed $value): mixed
163
	{
164 17
		foreach ($entry->getCalls() as $call) {
165
			$methodToResolve = $call->method;
166
167 20
			/** @psalm-var callable */
168
			$callable = [$value, $methodToResolve];
169 20
			$args = (new CallableResolver($this->creator))->resolve($callable, $call->args);
170 1
			$callable(...$args);
171
		}
172
173
		if ($entry->shouldReify()) {
174 20
			$entry->set($value);
175
		}
176 20
177 15
		return $value;
178 14
	}
179 14
180
	protected function resolveEntry(Entry $entry): mixed
181 14
	{
182
		if ($entry->shouldReturnAsIs()) {
183 5
			return $entry->definition();
184
		}
185 1
186
		/** @var mixed - the current value, instantiated or definition */
187 1
		$value = $entry->get();
188 1
189 1
		if (is_string($value)) {
190 1
			if (class_exists($value)) {
191
				$constructor = $entry->getConstructor();
192
				$args = $entry->getArgs();
193 4
194 4
				if (isset($args)) {
195 4
					// Don't autowire if $args are given
196 4
					if ($args instanceof Closure) {
197 4
						/** @psalm-var array<string, mixed> */
198 4
						$args = $args(...(new CallableResolver($this->creator))->resolve($args));
199 4
200 4
						return $this->callAndReify(
201
							$entry,
202
							$this->creator->create($value, $args),
203 9
						);
204 9
					}
205 9
206 9
					return $this->callAndReify(
207
						$entry,
208
						$this->creator->create(
209 2
							$value,
210 1
							predefinedArgs: $args,
211
							constructor: $constructor,
212
						),
213
					);
214 8
				}
215 4
216
				return $this->callAndReify(
217 4
					$entry,
218 2
					$this->creator->create($value, constructor: $constructor),
219 2
				);
220
			}
221 1
222
			if ($this->has($value)) {
223
				return $this->get($value);
224
			}
225 4
		}
226
227 4
		if ($value instanceof Closure) {
228
			$args = $entry->getArgs();
229
230 5
			if (is_null($args)) {
231 4
				$args = (new CallableResolver($this->creator))->resolve($value);
232
			} elseif ($args instanceof Closure) {
233
				/** @var array<string, mixed> */
234 1
				$args = $args();
235
			}
236
237
			/** @var mixed */
238
			$result = $value(...$args);
239
240
			return $this->callAndReify($entry, $result);
241
		}
242
243
		if (is_object($value)) {
244
			return $value;
245
		}
246
247
		throw new NotFoundException('Unresolvable id: ' . (string) $value);
248
	}
249
}
250