Skip to content

Commit 542379d

Browse files
committed
Rename ParameterResolver interface to Resolver
Swap the names so the contract carries the minimal name and the implementation carries the qualifier describing what it does. The interface is the generic `resolve()` promise; the concrete class is specifically about resolving parameters from a PSR-11 container. ParameterResolver (interface) -> Resolver Resolver (class) -> ContainerResolver This frees the bare `Resolver` name for consumers depending on the abstraction (`Resolver $resolver`) and gives future strategies parallel, self-describing names. ContainerResolver's name now carries strictly more information than the interface's, matching the usual contract-vs- implementation convention. BC break (clean, no aliases): ships as a major version bump. - ParameterResolver is removed; type-hint Resolver instead. - The old Resolver class becomes ContainerResolver; `new Resolver(...)` callers must switch to `new ContainerResolver(...)`. README and tests updated; ResolverTest renamed to ContainerResolverTest.
1 parent e8b5025 commit 542379d

5 files changed

Lines changed: 226 additions & 226 deletions

File tree

README.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ For each parameter the resolver tries, in order:
2424
A trailing **variadic** parameter receives a matching named argument (if any) followed by every remaining positional argument.
2525

2626
```php
27-
use Respect\Parameter\Resolver;
27+
use Respect\Parameter\ContainerResolver;
2828

2929
function notify(Mailer $mailer, Logger $logger, string $to, string $subject = 'Hi') {
3030
// ...
3131
}
3232

33-
$resolver = new Resolver($container);
33+
$resolver = new ContainerResolver($container);
3434
$args = $resolver->resolve(new ReflectionFunction('notify'), ['bob@example.com']);
3535
// [Mailer, Logger, 'bob@example.com', 'Hi'] — ordered, ready to splat
3636
```
@@ -54,15 +54,15 @@ $args = $resolver->resolve($constructor, ['username' => 'admin']);
5454

5555
### Bind to the interface
5656

57-
Type-hint `ParameterResolver` (the `resolve()` contract) rather than the concrete `Resolver` to stay
57+
Type-hint `Resolver` (the `resolve()` contract) rather than the concrete `ContainerResolver` to stay
5858
decoupled from the implementation:
5959

6060
```php
61-
use Respect\Parameter\ParameterResolver;
61+
use Respect\Parameter\Resolver;
6262

6363
final class Factory
6464
{
65-
public function __construct(private ParameterResolver $resolver)
65+
public function __construct(private Resolver $resolver)
6666
{
6767
}
6868
}
@@ -73,19 +73,19 @@ final class Factory
7373
Convert any callable form into a `ReflectionFunctionAbstract`:
7474

7575
```php
76-
use Respect\Parameter\Resolver;
76+
use Respect\Parameter\ContainerResolver;
7777

78-
Resolver::reflectCallable(fn() => ...); // Closure
79-
Resolver::reflectCallable([$obj, 'method']); // Array callable
80-
Resolver::reflectCallable(new Invocable()); // __invoke object
81-
Resolver::reflectCallable('strlen'); // Function name
82-
Resolver::reflectCallable('DateTime::createFromFormat'); // Static method
78+
ContainerResolver::reflectCallable(fn() => ...); // Closure
79+
ContainerResolver::reflectCallable([$obj, 'method']); // Array callable
80+
ContainerResolver::reflectCallable(new Invocable()); // __invoke object
81+
ContainerResolver::reflectCallable('strlen'); // Function name
82+
ContainerResolver::reflectCallable('DateTime::createFromFormat'); // Static method
8383
```
8484

8585
### Check accepted types
8686

8787
```php
88-
Resolver::acceptsType($reflection, LoggerInterface::class); // true/false
88+
ContainerResolver::acceptsType($reflection, LoggerInterface::class); // true/false
8989
```
9090

9191
## API
@@ -96,7 +96,7 @@ Resolver::acceptsType($reflection, LoggerInterface::class); // true/false
9696
| `reflectCallable($callable)` | static | Any callable to `ReflectionFunctionAbstract` |
9797
| `acceptsType($reflection, $type)` | static | Check if any parameter accepts a type |
9898

99-
`Resolver` implements `ParameterResolver`.
99+
`ContainerResolver` implements `Resolver`.
100100

101101
## License
102102

src/ContainerResolver.php

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
<?php
2+
3+
/*
4+
* SPDX-License-Identifier: ISC
5+
* SPDX-FileCopyrightText: (c) Respect Project Contributors
6+
* SPDX-FileContributor: Alexandre Gomes Gaigalas <alganet@gmail.com>
7+
*/
8+
9+
declare(strict_types=1);
10+
11+
namespace Respect\Parameter;
12+
13+
use Closure;
14+
use Psr\Container\ContainerInterface;
15+
use ReflectionFunction;
16+
use ReflectionFunctionAbstract;
17+
use ReflectionMethod;
18+
use ReflectionNamedType;
19+
use ReflectionParameter;
20+
21+
use function array_key_exists;
22+
use function array_values;
23+
use function assert;
24+
use function count;
25+
use function is_a;
26+
use function is_array;
27+
use function is_int;
28+
use function is_object;
29+
use function is_string;
30+
use function str_contains;
31+
32+
/**
33+
* Resolves the arguments to call a function or constructor with, autowiring any parameter that is
34+
* not supplied from a PSR-11 container by type.
35+
*
36+
* The result is always an ordered list ready to splat (`...$args` / `newInstanceArgs`), with
37+
* variadic parameters expanded.
38+
*/
39+
final readonly class ContainerResolver implements Resolver
40+
{
41+
public function __construct(private ContainerInterface $container)
42+
{
43+
}
44+
45+
/**
46+
* Resolve the arguments for a function/constructor.
47+
*
48+
* Provided arguments may be positional (int-keyed) or named (string-keyed by parameter name).
49+
* For each parameter, in order: an explicit named argument wins; then a positional argument
50+
* already matching the parameter type; then the container by type; then the next positional
51+
* argument; then the parameter default; otherwise null. A trailing variadic parameter receives
52+
* a matching named argument (if any) followed by every remaining positional argument.
53+
*
54+
* @param array<int|string, mixed> $arguments
55+
*
56+
* @return list<mixed>
57+
*/
58+
public function resolve(ReflectionFunctionAbstract $reflection, array $arguments): array
59+
{
60+
$parameters = $reflection->getParameters();
61+
if ($parameters === []) {
62+
return array_values($arguments);
63+
}
64+
65+
$positional = [];
66+
$named = [];
67+
foreach ($arguments as $key => $value) {
68+
if (is_int($key)) {
69+
$positional[] = $value;
70+
} else {
71+
$named[$key] = $value;
72+
}
73+
}
74+
75+
$resolved = [];
76+
$index = 0;
77+
$count = count($positional);
78+
79+
foreach ($parameters as $param) {
80+
$name = $param->getName();
81+
82+
if ($param->isVariadic()) {
83+
if (array_key_exists($name, $named)) {
84+
$resolved[] = $named[$name];
85+
}
86+
87+
while ($index < $count) {
88+
$resolved[] = $positional[$index++];
89+
}
90+
91+
break;
92+
}
93+
94+
if (array_key_exists($name, $named)) {
95+
$resolved[] = $named[$name];
96+
97+
continue;
98+
}
99+
100+
$type = self::typeName($param);
101+
102+
if ($type !== null && isset($positional[$index]) && $positional[$index] instanceof $type) {
103+
$resolved[] = $positional[$index++];
104+
105+
continue;
106+
}
107+
108+
if ($type !== null && $this->container->has($type)) {
109+
$resolved[] = $this->container->get($type);
110+
111+
continue;
112+
}
113+
114+
if ($index < $count) {
115+
$resolved[] = $positional[$index++];
116+
} elseif ($param->isDefaultValueAvailable()) {
117+
$resolved[] = $param->getDefaultValue();
118+
} else {
119+
$resolved[] = null;
120+
}
121+
}
122+
123+
return $resolved;
124+
}
125+
126+
/**
127+
* Resolve arguments, with named arguments taking precedence over the container.
128+
*
129+
* @deprecated Use {@see resolve()} instead; it now handles named arguments directly.
130+
*
131+
* @param array<int|string, mixed> $arguments
132+
*
133+
* @return list<mixed>
134+
*/
135+
public function resolveNamed(ReflectionFunctionAbstract $reflection, array $arguments): array
136+
{
137+
return $this->resolve($reflection, $arguments);
138+
}
139+
140+
/** Reflect any callable into its ReflectionFunctionAbstract. */
141+
public static function reflectCallable(callable $callable): ReflectionFunctionAbstract
142+
{
143+
if ($callable instanceof Closure) {
144+
return new ReflectionFunction($callable);
145+
}
146+
147+
if (is_array($callable)) {
148+
/** @var array{object|class-string, string} $callable */ // phpcs:ignore SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration.MissingVariable
149+
150+
return new ReflectionMethod(...$callable);
151+
}
152+
153+
if (is_object($callable)) {
154+
return new ReflectionMethod($callable, '__invoke');
155+
}
156+
157+
if (is_string($callable) && str_contains($callable, '::')) {
158+
return ReflectionMethod::createFromMethodName($callable);
159+
}
160+
161+
assert(is_string($callable));
162+
163+
return new ReflectionFunction($callable);
164+
}
165+
166+
/** @param class-string $type */
167+
public static function acceptsType(ReflectionFunctionAbstract $reflection, string $type): bool
168+
{
169+
foreach ($reflection->getParameters() as $param) {
170+
$typeName = self::typeName($param);
171+
172+
if ($typeName !== null && is_a($typeName, $type, true)) {
173+
return true;
174+
}
175+
}
176+
177+
return false;
178+
}
179+
180+
/** @return class-string|null */
181+
private static function typeName(ReflectionParameter $param): string|null
182+
{
183+
$type = $param->getType();
184+
185+
/** @phpstan-ignore return.type */
186+
return $type instanceof ReflectionNamedType && !$type->isBuiltin() ? $type->getName() : null;
187+
/* Ignore Reason: !isBuiltin() guarantees class-string */
188+
}
189+
}

src/ParameterResolver.php

Lines changed: 0 additions & 25 deletions
This file was deleted.

0 commit comments

Comments
 (0)