Skip to content

Commit cc5e21b

Browse files
committed
Introduce Instantiator types (Autowire and Factory)
Extract Factory (always-new instances) and Autowire (type-hint-based DI resolution) from Instantiator, replacing the mode string with polymorphism. Refactor Instantiator to lazily initialize reflection and constructor params, accept initial params in the constructor, and remove the embedded class-name parsing that now lives in Container's createInstantiator(). Container gains createInstantiator() with `new`/`autowire` modifier support, Autowire integration via offsetSet(), class_exists() check in has(), ReflectionException wrapping in getItem(), Closure-aware lazyLoad(), and set()/loadArray() handling for Instantiator/Closure values. Add AutowireTest, FactoryTest, NotFoundExceptionTest, and extend ContainerTest and InstantiatorTest to cover magic methods, deferred config, loadString/loadFile error paths, and edge cases (102 tests).
1 parent 2a8e237 commit cc5e21b

File tree

10 files changed

+867
-74
lines changed

10 files changed

+867
-74
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
},
5252
"extra": {
5353
"branch-alias": {
54-
"dev-master": "2.1-dev"
54+
"dev-master": "3.0-dev"
5555
}
5656
}
5757
}

src/Autowire.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Respect\Config;
6+
7+
use Psr\Container\ContainerInterface;
8+
use ReflectionNamedType;
9+
10+
use function array_key_exists;
11+
use function end;
12+
use function key;
13+
14+
class Autowire extends Instantiator
15+
{
16+
protected ContainerInterface|null $container = null;
17+
18+
public function setContainer(ContainerInterface $container): void
19+
{
20+
$this->container = $container;
21+
}
22+
23+
/** @inheritDoc */
24+
protected function cleanupParams(array $params): array
25+
{
26+
$constructor = $this->reflection()->getConstructor();
27+
if ($constructor && $this->container) {
28+
foreach ($constructor->getParameters() as $param) {
29+
$name = $param->getName();
30+
if (array_key_exists($name, $this->params)) {
31+
$params[$name] = $this->lazyLoad($params[$name] ?? null);
32+
continue;
33+
}
34+
35+
$type = $param->getType();
36+
if (
37+
!($type instanceof ReflectionNamedType) || $type->isBuiltin()
38+
|| !$this->container->has($type->getName())
39+
) {
40+
continue;
41+
}
42+
43+
$params[$name] = $this->container->get($type->getName());
44+
}
45+
46+
while (end($params) === null && ($key = key($params)) !== null) {
47+
unset($params[$key]);
48+
}
49+
50+
return $params;
51+
}
52+
53+
return parent::cleanupParams($params);
54+
}
55+
}

src/Container.php

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@
99
use InvalidArgumentException;
1010
use Psr\Container\ContainerInterface;
1111
use ReflectionClass;
12+
use ReflectionException;
1213
use ReflectionFunction;
1314
use ReflectionNamedType;
1415

1516
use function array_filter;
1617
use function array_map;
1718
use function assert;
19+
use function call_user_func;
20+
use function class_exists;
1821
use function constant;
1922
use function count;
2023
use function current;
@@ -47,7 +50,16 @@ public function has(string $id): bool
4750
$this->configure();
4851
}
4952

50-
return parent::offsetExists($id);
53+
if (!parent::offsetExists($id)) {
54+
return false;
55+
}
56+
57+
$entry = $this[$id];
58+
if ($entry instanceof Instantiator) {
59+
return class_exists($entry->getClassName());
60+
}
61+
62+
return true;
5163
}
5264

5365
public function getItem(string $name, bool $raw = false): mixed
@@ -64,7 +76,11 @@ public function getItem(string $name, bool $raw = false): mixed
6476
return $this[$name];
6577
}
6678

67-
return $this->lazyLoad($name);
79+
try {
80+
return $this->lazyLoad($name);
81+
} catch (ReflectionException $e) {
82+
throw new NotFoundException('Item ' . $name . ' not found: ' . $e->getMessage(), 0, $e);
83+
}
6884
}
6985

7086
public function get(string $id): mixed
@@ -97,13 +113,33 @@ public function loadArray(array $configurator): void
97113
{
98114
foreach ($this->state() + $configurator as $key => $value) {
99115
if ($value instanceof Closure) {
116+
$this->offsetSet((string) $key, $value);
117+
continue;
118+
}
119+
120+
if ($value instanceof Instantiator) {
121+
$this->offsetSet((string) $key, $value);
100122
continue;
101123
}
102124

103125
$this->parseItem($key, $value);
104126
}
105127
}
106128

129+
public function offsetSet(mixed $key, mixed $value): void
130+
{
131+
if ($value instanceof Autowire) {
132+
$value->setContainer($this);
133+
}
134+
135+
parent::offsetSet($key, $value);
136+
}
137+
138+
public function set(string $name, mixed $value): void
139+
{
140+
$this[$name] = $value;
141+
}
142+
107143
protected function configure(): void
108144
{
109145
$configurator = $this->configurator;
@@ -206,7 +242,7 @@ protected function parseInstantiator(string $key, mixed $value): void
206242
}
207243

208244
/** @var class-string $keyClass */
209-
$instantiator = new Instantiator($keyClass);
245+
$instantiator = $this->createInstantiator($keyClass);
210246

211247
if (is_array($value)) {
212248
foreach ($value as $property => $pValue) {
@@ -219,6 +255,23 @@ protected function parseInstantiator(string $key, mixed $value): void
219255
$this->offsetSet($keyName, $instantiator);
220256
}
221257

258+
/** @param class-string $keyClass */
259+
protected function createInstantiator(string $keyClass): Instantiator
260+
{
261+
if (!str_contains($keyClass, ' ')) {
262+
return new Instantiator($keyClass);
263+
}
264+
265+
[$modifier, $className] = explode(' ', $keyClass, 2);
266+
267+
/** @var class-string $className */
268+
return match ($modifier) {
269+
'new' => new Factory($className),
270+
'autowire' => new Autowire($className),
271+
default => new Instantiator($keyClass),
272+
};
273+
}
274+
222275
protected function parseValue(mixed $value): mixed
223276
{
224277
if ($value instanceof Instantiator) {
@@ -319,11 +372,15 @@ protected function parseArgumentList(string $value): array
319372
protected function lazyLoad(string $name): mixed
320373
{
321374
$callback = $this[$name];
322-
if ($callback instanceof Instantiator && $callback->getMode() !== Instantiator::MODE_FACTORY) {
375+
if ($callback instanceof Instantiator && !$callback instanceof Factory) {
323376
return $this[$name] = $callback();
324377
}
325378

326-
return $callback();
379+
if ($callback instanceof Closure) {
380+
return $this[$name] = $callback($this);
381+
}
382+
383+
return call_user_func($callback);
327384
}
328385

329386
public function __isset(string $name): bool

src/Factory.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Respect\Config;
6+
7+
class Factory extends Instantiator
8+
{
9+
public function getInstance(bool $forceNew = false): mixed
10+
{
11+
$this->instance = null;
12+
13+
return parent::getInstance(true);
14+
}
15+
}

src/Instantiator.php

Lines changed: 35 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -10,28 +10,22 @@
1010
use function call_user_func_array;
1111
use function count;
1212
use function end;
13-
use function explode;
1413
use function func_get_args;
1514
use function is_array;
1615
use function is_callable;
1716
use function is_object;
1817
use function key;
19-
use function str_contains;
2018
use function stripos;
21-
use function strtolower;
2219

2320
class Instantiator
2421
{
25-
public const false MODE_DEPENDENCY = false;
26-
public const string MODE_FACTORY = 'new';
27-
2822
protected mixed $instance = null;
2923

30-
/** @var ReflectionClass<object> */
31-
protected ReflectionClass $reflection;
24+
/** @var ReflectionClass<object>|null */
25+
protected ReflectionClass|null $reflection = null;
3226

33-
/** @var array<string, mixed> */
34-
protected array $constructor = [];
27+
/** @var array<string, mixed>|null */
28+
protected array|null $constructor = null;
3529

3630
/** @var array<string, mixed> */
3731
protected array $params = [];
@@ -45,25 +39,15 @@ class Instantiator
4539
/** @var array<string, mixed> */
4640
protected array $propertySetters = [];
4741

48-
protected string|false $mode = self::MODE_DEPENDENCY;
49-
50-
/** @param class-string $className */
51-
public function __construct(protected string $className)
42+
/**
43+
* @param class-string $className
44+
* @param array<string, mixed> $params Initial parameters (constructor, method, or property)
45+
*/
46+
public function __construct(protected string $className, array $params = [])
5247
{
53-
if (str_contains(strtolower($className), ' ')) {
54-
[$mode, $className] = explode(' ', $className, 2);
55-
$this->mode = $mode;
56-
/** @var class-string $className */
57-
$this->className = $className;
48+
foreach ($params as $name => $value) {
49+
$this->setParam($name, $value);
5850
}
59-
60-
$this->reflection = new ReflectionClass($className);
61-
$this->constructor = $this->findConstructorParams($this->reflection);
62-
}
63-
64-
public function getMode(): string|false
65-
{
66-
return $this->mode;
6751
}
6852

6953
public function getClassName(): string
@@ -73,10 +57,6 @@ public function getClassName(): string
7357

7458
public function getInstance(bool $forceNew = false): mixed
7559
{
76-
if ($this->mode === self::MODE_FACTORY) {
77-
$this->instance = null;
78-
}
79-
8060
if ($this->instance && !$forceNew) {
8161
return $this->instance;
8262
}
@@ -98,15 +78,12 @@ static function (mixed $result) use ($className, &$instance, $staticMethods): vo
9878
);
9979
}
10080

101-
$constructor = $this->reflection->getConstructor();
102-
$hasConstructor = $constructor ? $constructor->isPublic() : false;
10381
if (empty($instance)) {
104-
if (empty($this->constructor) || !$hasConstructor) {
105-
$instance = new $className();
82+
$constructorParams = $this->cleanupParams($this->constructor ?? []);
83+
if (empty($constructorParams)) {
84+
$instance = $this->reflection()->newInstance();
10685
} else {
107-
$instance = $this->reflection->newInstanceArgs(
108-
$this->cleanupParams($this->constructor),
109-
);
86+
$instance = $this->reflection()->newInstanceArgs($constructorParams);
11087
}
11188
}
11289

@@ -156,19 +133,20 @@ public function getParams(): array
156133
return $this->params;
157134
}
158135

136+
/** @return ReflectionClass<object> */
137+
protected function reflection(): ReflectionClass
138+
{
139+
return $this->reflection ??= new ReflectionClass($this->className);
140+
}
141+
159142
/**
160143
* @param array<mixed> $params
161144
*
162145
* @return array<mixed>
163146
*/
164147
protected function cleanupParams(array $params): array
165148
{
166-
while (end($params) === null) {
167-
$key = key($params);
168-
if ($key === null) {
169-
break;
170-
}
171-
149+
while (end($params) === null && ($key = key($params)) !== null) {
172150
unset($params[$key]);
173151
}
174152

@@ -184,27 +162,6 @@ protected function lazyLoad(mixed $value): mixed
184162
return $value instanceof self ? $value->getInstance() : $value;
185163
}
186164

187-
/**
188-
* @param ReflectionClass<object> $class
189-
*
190-
* @return array<string, mixed>
191-
*/
192-
protected function findConstructorParams(ReflectionClass $class): array
193-
{
194-
$params = [];
195-
$constructor = $class->getConstructor();
196-
197-
if (!$constructor) {
198-
return [];
199-
}
200-
201-
foreach ($constructor->getParameters() as $param) {
202-
$params[$param->getName()] = $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null;
203-
}
204-
205-
return $params;
206-
}
207-
208165
protected function processValue(mixed $value): mixed
209166
{
210167
if (is_array($value)) {
@@ -218,6 +175,16 @@ protected function processValue(mixed $value): mixed
218175

219176
protected function matchConstructorParam(string $name): bool
220177
{
178+
if ($this->constructor === null) {
179+
$this->constructor = [];
180+
$ctor = $this->reflection()->getConstructor();
181+
if ($ctor) {
182+
foreach ($ctor->getParameters() as $param) {
183+
$this->constructor[$param->getName()] = null;
184+
}
185+
}
186+
}
187+
221188
return array_key_exists($name, $this->constructor);
222189
}
223190

@@ -229,13 +196,13 @@ protected function matchFullConstructor(string $name): bool
229196

230197
protected function matchMethod(string $name): bool
231198
{
232-
return $this->reflection->hasMethod($name);
199+
return $this->reflection()->hasMethod($name);
233200
}
234201

235202
protected function matchStaticMethod(string $name): bool
236203
{
237-
return $this->reflection->hasMethod($name)
238-
&& $this->reflection->getMethod($name)->isStatic();
204+
return $this->reflection()->hasMethod($name)
205+
&& $this->reflection()->getMethod($name)->isStatic();
239206
}
240207

241208
/** @param array{string, mixed} $methodCalls */

0 commit comments

Comments
 (0)