Skip to content

Commit 41ae2b4

Browse files
committed
Resolver::completeStatement() moved to Statement & Reference
1 parent fda1821 commit 41ae2b4

File tree

8 files changed

+270
-242
lines changed

8 files changed

+270
-242
lines changed

src/DI/ContainerBuilder.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -394,8 +394,8 @@ public static function literal(string $code, ?array $args = null): Nette\PhpGene
394394
public function formatPhp(string $statement, array $args): string
395395
{
396396
array_walk_recursive($args, function (&$val): void {
397-
if ($val instanceof Nette\DI\Definitions\Statement) {
398-
$val = (new Resolver($this))->completeStatement($val);
397+
if ($val instanceof Nette\DI\Definitions\Expression) {
398+
$val->complete(new Resolver($this));
399399

400400
} elseif ($val instanceof Definition) {
401401
$val = new Definitions\Reference($val->getName());

src/DI/Definitions/AccessorDefinition.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public function complete(Nette\DI\Resolver $resolver): void
103103
$this->setReference(Type::fromReflection($method)->getSingleName());
104104
}
105105

106-
$this->reference = $resolver->normalizeReference($this->reference);
106+
$this->reference->complete($resolver);
107107
}
108108

109109

src/DI/Definitions/Expression.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,8 @@ abstract class Expression
1717
abstract public function resolveType(Nette\DI\Resolver $resolver): ?string;
1818

1919

20+
abstract public function complete(Nette\DI\Resolver $resolver): void;
21+
22+
2023
abstract public function generateCode(Nette\DI\PhpGenerator $generator): string;
2124
}

src/DI/Definitions/LocatorDefinition.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,8 @@ public function complete(Nette\DI\Resolver $resolver): void
121121
}
122122
}
123123

124-
foreach ($this->references as $name => $ref) {
125-
$this->references[$name] = $resolver->normalizeReference($ref);
124+
foreach ($this->references as $ref) {
125+
$ref->complete($resolver);
126126
}
127127
}
128128

src/DI/Definitions/Reference.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,32 @@ public function resolveType(DI\Resolver $resolver): ?string
8484
}
8585

8686

87+
/**
88+
* Normalizes reference to 'self' or named reference (or leaves it typed if it is not possible during resolving) and checks existence of service.
89+
*/
90+
public function complete(DI\Resolver $resolver): void
91+
{
92+
if ($this->isSelf()) {
93+
return;
94+
95+
} elseif ($this->isType()) {
96+
try {
97+
$this->value = $resolver->getByType($this->value)->value;
98+
} catch (DI\NotAllowedDuringResolvingException) {
99+
}
100+
return;
101+
}
102+
103+
if (!$resolver->getContainerBuilder()->hasDefinition($this->value)) {
104+
throw new DI\ServiceCreationException(sprintf("Reference to missing service '%s'.", $this->value));
105+
}
106+
107+
if ($this->value === $resolver->getCurrentService()?->getName()) {
108+
$this->value = self::Self;
109+
}
110+
}
111+
112+
87113
public function generateCode(DI\PhpGenerator $generator): string
88114
{
89115
return match (true) {

src/DI/Definitions/ServiceDefinition.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,14 +161,14 @@ public function complete(Nette\DI\Resolver $resolver): void
161161
{
162162
$entity = $this->creator->getEntity();
163163
if ($entity instanceof Reference && !$this->creator->arguments && !$this->setup) {
164-
$ref = $resolver->normalizeReference($entity);
165-
$this->setCreator([new Reference(Nette\DI\ContainerBuilder::ThisContainer), 'getService'], [$ref->getValue()]);
164+
$entity->complete($resolver);
165+
$this->setCreator([new Reference(Nette\DI\ContainerBuilder::ThisContainer), 'getService'], [$entity->getValue()]);
166166
}
167167

168-
$this->creator = $resolver->completeStatement($this->creator);
168+
$this->creator->complete($resolver);
169169

170-
foreach ($this->setup as &$setup) {
171-
$setup = $resolver->withCurrentServiceAvailable()->completeStatement($setup);
170+
foreach ($this->setup as $setup) {
171+
$setup->complete($resolver->withCurrentServiceAvailable());
172172
}
173173
}
174174

src/DI/Definitions/Statement.php

Lines changed: 198 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Nette\DI\ServiceCreationException;
1616
use Nette\PhpGenerator as Php;
1717
use Nette\Utils\Callback;
18+
use Nette\Utils\Validators;
1819

1920

2021
/**
@@ -71,7 +72,7 @@ public function getEntity(): string|array|Definition|Reference|null
7172

7273
public function resolveType(Resolver $resolver): ?string
7374
{
74-
$entity = $resolver->normalizeEntity($this);
75+
$entity = $this->normalizeEntity($resolver);
7576

7677
if ($this->arguments === Resolver::getFirstClassCallable()) {
7778
return \Closure::class;
@@ -137,6 +138,202 @@ interface_exists($entity)
137138
}
138139

139140

141+
public function complete(Resolver $resolver): void
142+
{
143+
$entity = $this->normalizeEntity($resolver);
144+
$this->convertReferences($resolver);
145+
$arguments = $this->arguments;
146+
147+
switch (true) {
148+
case $this->arguments === Resolver::getFirstClassCallable():
149+
if (!is_array($entity) || !Php\Helpers::isIdentifier($entity[1])) {
150+
throw new ServiceCreationException(sprintf('Cannot create closure for %s(...)', $entity));
151+
}
152+
if ($entity[0] instanceof self) {
153+
$entity[0]->complete($resolver);
154+
}
155+
break;
156+
157+
case is_string($entity) && str_contains($entity, '?'): // PHP literal
158+
break;
159+
160+
case $entity === 'not':
161+
if (count($arguments) !== 1) {
162+
throw new ServiceCreationException(sprintf('Function %s() expects 1 parameter, %s given.', $entity, count($arguments)));
163+
}
164+
165+
$this->entity = ['', '!'];
166+
break;
167+
168+
case $entity === 'bool':
169+
case $entity === 'int':
170+
case $entity === 'float':
171+
case $entity === 'string':
172+
if (count($arguments) !== 1) {
173+
throw new ServiceCreationException(sprintf('Function %s() expects 1 parameter, %s given.', $entity, count($arguments)));
174+
}
175+
176+
$arguments = [$arguments[0], $entity];
177+
$this->entity = [DI\Helpers::class, 'convertType'];
178+
break;
179+
180+
case is_string($entity): // create class
181+
if (!class_exists($entity)) {
182+
throw new ServiceCreationException(sprintf("Class '%s' not found.", $entity));
183+
} elseif ((new \ReflectionClass($entity))->isAbstract()) {
184+
throw new ServiceCreationException(sprintf('Class %s is abstract.', $entity));
185+
} elseif (($rm = (new \ReflectionClass($entity))->getConstructor()) !== null && !$rm->isPublic()) {
186+
throw new ServiceCreationException(sprintf('Class %s has %s constructor.', $entity, $rm->isProtected() ? 'protected' : 'private'));
187+
} elseif ($constructor = (new \ReflectionClass($entity))->getConstructor()) {
188+
$arguments = $resolver->autowireServices($constructor, $arguments);
189+
$resolver->addDependency($constructor);
190+
} elseif ($arguments) {
191+
throw new ServiceCreationException(sprintf(
192+
'Unable to pass arguments, class %s has no constructor.',
193+
$entity,
194+
));
195+
}
196+
197+
break;
198+
199+
case $entity instanceof Reference:
200+
if ($arguments) {
201+
$e = $resolver->completeException(new ServiceCreationException(sprintf('Parameters were passed to reference @%s, although references cannot have any parameters.', $entity->getValue())), $resolver->getCurrentService());
202+
trigger_error($e->getMessage(), E_USER_DEPRECATED);
203+
}
204+
$this->entity = [new Reference(DI\ContainerBuilder::ThisContainer), DI\Container::getMethodName($entity->getValue())];
205+
break;
206+
207+
case is_array($entity):
208+
if (!preg_match('#^\$?(\\\?' . Php\Helpers::ReIdentifier . ')+(\[\])?$#D', $entity[1])) {
209+
throw new ServiceCreationException(sprintf(
210+
"Expected function, method or property name, '%s' given.",
211+
$entity[1],
212+
));
213+
}
214+
215+
switch (true) {
216+
case $entity[0] === '': // function call
217+
if (!function_exists($entity[1])) {
218+
throw new ServiceCreationException(sprintf("Function %s doesn't exist.", $entity[1]));
219+
}
220+
221+
$rf = new \ReflectionFunction($entity[1]);
222+
$arguments = $resolver->autowireServices($rf, $arguments);
223+
$resolver->addDependency($rf);
224+
break;
225+
226+
case $entity[0] instanceof self:
227+
$entity[0]->complete($resolver);
228+
// break omitted
229+
230+
case is_string($entity[0]): // static method call
231+
case $entity[0] instanceof Reference:
232+
if ($entity[1][0] === '$') { // property getter, setter or appender
233+
Validators::assert($arguments, 'list:0..1', "setup arguments for '" . Callback::toString($entity) . "'");
234+
if (!$arguments && str_ends_with($entity[1], '[]')) {
235+
throw new ServiceCreationException(sprintf('Missing argument for %s.', $entity[1]));
236+
}
237+
} elseif (
238+
$type = ($entity[0] instanceof Expression ? $entity[0] : new self($entity[0]))->resolveType($resolver)
239+
) {
240+
$rc = new \ReflectionClass($type);
241+
if ($rc->hasMethod($entity[1])) {
242+
$rm = $rc->getMethod($entity[1]);
243+
if (!$rm->isPublic()) {
244+
throw new ServiceCreationException(sprintf('%s::%s() is not callable.', $type, $entity[1]));
245+
}
246+
247+
$arguments = $resolver->autowireServices($rm, $arguments);
248+
$resolver->addDependency($rm);
249+
}
250+
}
251+
}
252+
}
253+
254+
try {
255+
$this->arguments = $this->completeArguments($resolver, $arguments);
256+
} catch (ServiceCreationException $e) {
257+
if (!str_contains($e->getMessage(), ' (used in')) {
258+
$e->setMessage($e->getMessage() . " (used in {$resolver->entityToString($entity)})");
259+
}
260+
261+
throw $e;
262+
}
263+
}
264+
265+
266+
public function completeArguments(Resolver $resolver, array $arguments): array
267+
{
268+
array_walk_recursive($arguments, function (&$val) use ($resolver): void {
269+
if ($val instanceof self) {
270+
if ($val->entity === 'typed' || $val->entity === 'tagged') {
271+
$services = [];
272+
$current = $resolver->getCurrentService()?->getName();
273+
foreach ($val->arguments as $argument) {
274+
foreach ($val->entity === 'tagged' ? $resolver->getContainerBuilder()->findByTag($argument) : $resolver->getContainerBuilder()->findAutowired($argument) as $name => $foo) {
275+
if ($name !== $current) {
276+
$services[] = new Reference($name);
277+
}
278+
}
279+
}
280+
281+
$val = $this->completeArguments($resolver, $services);
282+
} else {
283+
$val->complete($resolver);
284+
}
285+
} elseif ($val instanceof Definition || $val instanceof Reference) {
286+
$val = (new self($val))->normalizeEntity($resolver);
287+
}
288+
});
289+
return $arguments;
290+
}
291+
292+
293+
/** Returns literal, Class, Reference, [Class, member], [, globalFunc], [Reference, member], [Statement, member] */
294+
private function normalizeEntity(Resolver $resolver): string|array|Reference|null
295+
{
296+
if (is_array($this->entity)) {
297+
$item = &$this->entity[0];
298+
} else {
299+
$item = &$this->entity;
300+
}
301+
302+
if ($item instanceof Definition) {
303+
if ($resolver->getContainerBuilder()->getDefinition($item->getName()) !== $item) {
304+
throw new ServiceCreationException(sprintf("Service '%s' does not match the expected service.", $item->getName()));
305+
306+
}
307+
$item = new Reference($item->getName());
308+
}
309+
310+
if ($item instanceof Reference) {
311+
$item->complete($resolver);
312+
}
313+
314+
return $this->entity;
315+
}
316+
317+
318+
private function convertReferences(Resolver $resolver): void
319+
{
320+
array_walk_recursive($this->arguments, function (&$val) use ($resolver): void {
321+
if (is_string($val) && strlen($val) > 1 && $val[0] === '@' && $val[1] !== '@') {
322+
$pair = explode('::', substr($val, 1), 2);
323+
if (!isset($pair[1])) { // @service
324+
$val = new Reference($pair[0]);
325+
} elseif (preg_match('#^[A-Z][a-zA-Z0-9_]*$#D', $pair[1])) { // @service::CONSTANT
326+
$val = DI\ContainerBuilder::literal((new Reference($pair[0]))->resolveType($resolver) . '::' . $pair[1]);
327+
} else { // @service::property
328+
$val = new self([new Reference($pair[0]), '$' . $pair[1]]);
329+
}
330+
} elseif (is_string($val) && str_starts_with($val, '@@')) { // escaped text @@
331+
$val = substr($val, 1);
332+
}
333+
});
334+
}
335+
336+
140337
/**
141338
* Formats PHP code for class instantiating, function calling or property setting in PHP.
142339
*/

0 commit comments

Comments
 (0)