Skip to content

Commit c571821

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 c571821

File tree

4 files changed

+124
-1
lines changed

4 files changed

+124
-1
lines changed

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/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/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)