Skip to content

Commit e744aa2

Browse files
committed
Add Ref for named container references in Autowire
Ref is a value object that maps a constructor parameter to a container key, enabling Autowire for cases where type-based resolution is insufficient (same interface with multiple implementations, or builtin types like array/string).
1 parent b56c276 commit e744aa2

6 files changed

Lines changed: 139 additions & 1 deletion

File tree

src/Autowire.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,13 @@ protected function cleanupParams(array $params): array
2828
foreach ($constructor->getParameters() as $param) {
2929
$name = $param->getName();
3030
if (array_key_exists($name, $this->params)) {
31-
$params[$name] = $this->lazyLoad($params[$name] ?? null);
31+
$value = $params[$name] ?? null;
32+
if ($value instanceof Ref) {
33+
$params[$name] = $this->container->get($value->id);
34+
} else {
35+
$params[$name] = $this->lazyLoad($value);
36+
}
37+
3238
continue;
3339
}
3440

src/Instantiator.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Respect\Config;
44

5+
use InvalidArgumentException;
56
use ReflectionClass;
67

78
use function array_key_exists;
@@ -159,6 +160,10 @@ protected function cleanupParams(array $params): array
159160

160161
protected function lazyLoad(mixed $value): mixed
161162
{
163+
if ($value instanceof Ref) {
164+
throw new InvalidArgumentException('Ref can only be used with Autowire, not ' . static::class);
165+
}
166+
162167
return $value instanceof self ? $value->getInstance() : $value;
163168
}
164169

src/Ref.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Respect\Config;
6+
7+
class Ref
8+
{
9+
public function __construct(public readonly string $id)
10+
{
11+
}
12+
}

tests/AutowireTest.php

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,77 @@ public function testAutowireWithoutContainerLazyLoadsParams(): void
226226
$this->assertEquals('lazy_loaded', $instance->dep->value);
227227
}
228228

229+
public function testRefResolvesClassDependencyByStringKey(): void
230+
{
231+
$container = new Container();
232+
$dep = new AutowireDependency('via_ref');
233+
$container['my.custom.dep'] = $dep;
234+
235+
$autowire = new Autowire(AutowireTypedConsumer::class);
236+
$autowire->setContainer($container);
237+
$autowire->setParam('dep', new Ref('my.custom.dep'));
238+
239+
$instance = $autowire->getInstance();
240+
$this->assertSame($dep, $instance->dep);
241+
}
242+
243+
public function testRefResolvesNonClassDependency(): void
244+
{
245+
$container = new Container();
246+
$container['app.name'] = 'MyApp';
247+
248+
$autowire = new Autowire(AutowireWithBuiltin::class);
249+
$autowire->setContainer($container);
250+
$autowire->setParam('name', new Ref('app.name'));
251+
252+
$instance = $autowire->getInstance();
253+
$this->assertEquals('MyApp', $instance->name);
254+
}
255+
256+
public function testRefCoexistsWithTypeBasedAutowiring(): void
257+
{
258+
$container = new Container();
259+
$container['DateTime'] = new DateTime('2024-01-15');
260+
$container['custom.dep'] = new AutowireDependency('from_ref');
261+
262+
$autowire = new Autowire(AutowireMultiParam::class);
263+
$autowire->setContainer($container);
264+
// Only bind 'dep' via Ref; 'date' should be autowired by type
265+
$autowire->setParam('dep', new Ref('custom.dep'));
266+
267+
$instance = $autowire->getInstance();
268+
$this->assertInstanceOf(DateTime::class, $instance->date);
269+
$this->assertEquals('from_ref', $instance->dep->value);
270+
}
271+
272+
public function testRefTakesPrecedenceOverTypeBasedAutowiring(): void
273+
{
274+
$container = new Container();
275+
$container['DateTime'] = new DateTime('2024-01-15');
276+
$container[AutowireDependency::class] = new AutowireDependency('from_type');
277+
$container['override.dep'] = new AutowireDependency('from_ref');
278+
279+
$autowire = new Autowire(AutowireTypedConsumer::class);
280+
$autowire->setContainer($container);
281+
$autowire->setParam('dep', new Ref('override.dep'));
282+
283+
$instance = $autowire->getInstance();
284+
$this->assertEquals('from_ref', $instance->dep->value);
285+
}
286+
287+
public function testRefResolvesArrayDependency(): void
288+
{
289+
$container = new Container();
290+
$container['app.paths'] = ['/path/one', '/path/two'];
291+
292+
$autowire = new Autowire(AutowireWithArray::class);
293+
$autowire->setContainer($container);
294+
$autowire->setParam('paths', new Ref('app.paths'));
295+
296+
$instance = $autowire->getInstance();
297+
$this->assertEquals(['/path/one', '/path/two'], $instance->paths);
298+
}
299+
229300
/** @return array<string, mixed> */
230301
private static function parseIni(string $ini): array
231302
{
@@ -284,3 +355,11 @@ public function __construct(public DateTime|null $a = null, public DateTime|null
284355
{
285356
}
286357
}
358+
359+
class AutowireWithArray
360+
{
361+
/** @param array<string> $paths */
362+
public function __construct(public array $paths)
363+
{
364+
}
365+
}

tests/InstantiatorTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Respect\Config;
66

77
use DateTimeZone;
8+
use InvalidArgumentException;
89
use PHPUnit\Framework\Attributes\CoversClass;
910
use PHPUnit\Framework\TestCase;
1011
use stdClass;
@@ -198,6 +199,15 @@ public function testMethodCallWithNullArg(): void
198199
$this->assertTrue($s->ok);
199200
}
200201

202+
public function testRefThrowsInInstantiator(): void
203+
{
204+
$this->expectException(InvalidArgumentException::class);
205+
$this->expectExceptionMessage('Ref can only be used with Autowire');
206+
$i = new Instantiator(stdClass::class);
207+
$i->setParam('foo', new Ref('some.key'));
208+
$i->getInstance();
209+
}
210+
201211
public function testStaticMethodReturningNonObject(): void
202212
{
203213
$i = new Instantiator(__NAMESPACE__ . '\\StaticNonObjectReturn');

tests/RefTest.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Respect\Config;
6+
7+
use PHPUnit\Framework\Attributes\CoversClass;
8+
use PHPUnit\Framework\TestCase;
9+
use ReflectionProperty;
10+
11+
#[CoversClass(Ref::class)]
12+
final class RefTest extends TestCase
13+
{
14+
public function testConstructorSetsId(): void
15+
{
16+
$ref = new Ref('some.container.key');
17+
$this->assertEquals('some.container.key', $ref->id);
18+
}
19+
20+
public function testIdIsReadonly(): void
21+
{
22+
$ref = new Ref('my.key');
23+
$reflection = new ReflectionProperty($ref, 'id');
24+
$this->assertTrue($reflection->isReadOnly());
25+
}
26+
}

0 commit comments

Comments
 (0)