Skip to content

Commit

Permalink
Resolver::completeStatement() moved to Statement & Reference
Browse files Browse the repository at this point in the history
  • Loading branch information
dg committed Jan 10, 2025
1 parent 82caadf commit 673b9a8
Show file tree
Hide file tree
Showing 8 changed files with 270 additions and 242 deletions.
4 changes: 2 additions & 2 deletions src/DI/ContainerBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -394,8 +394,8 @@ public static function literal(string $code, ?array $args = null): Nette\PhpGene
public function formatPhp(string $statement, array $args): string
{
array_walk_recursive($args, function (&$val): void {
if ($val instanceof Nette\DI\Definitions\Statement) {
$val = (new Resolver($this))->completeStatement($val);
if ($val instanceof Nette\DI\Definitions\Expression) {
$val->complete(new Resolver($this));

} elseif ($val instanceof Definition) {
$val = new Definitions\Reference($val->getName());
Expand Down
2 changes: 1 addition & 1 deletion src/DI/Definitions/AccessorDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public function complete(Nette\DI\Resolver $resolver): void
$this->setReference(Type::fromReflection($method)->getSingleName());
}

$this->reference = $resolver->normalizeReference($this->reference);
$this->reference->complete($resolver);
}


Expand Down
3 changes: 3 additions & 0 deletions src/DI/Definitions/Expression.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,8 @@ abstract class Expression
abstract public function resolveType(Nette\DI\Resolver $resolver): ?string;


abstract public function complete(Nette\DI\Resolver $resolver): void;


abstract public function generateCode(Nette\DI\PhpGenerator $generator): string;
}
4 changes: 2 additions & 2 deletions src/DI/Definitions/LocatorDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,8 @@ public function complete(Nette\DI\Resolver $resolver): void
}
}

foreach ($this->references as $name => $ref) {
$this->references[$name] = $resolver->normalizeReference($ref);
foreach ($this->references as $ref) {
$ref->complete($resolver);
}
}

Expand Down
26 changes: 26 additions & 0 deletions src/DI/Definitions/Reference.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,32 @@ public function resolveType(DI\Resolver $resolver): ?string
}


/**
* Normalizes reference to 'self' or named reference (or leaves it typed if it is not possible during resolving) and checks existence of service.
*/
public function complete(DI\Resolver $resolver): void
{
if ($this->isSelf()) {
return;

} elseif ($this->isType()) {
try {
$this->value = $resolver->getByType($this->value)->value;
} catch (DI\NotAllowedDuringResolvingException) {
}
return;
}

if (!$resolver->getContainerBuilder()->hasDefinition($this->value)) {
throw new DI\ServiceCreationException(sprintf("Reference to missing service '%s'.", $this->value));
}

if ($this->value === $resolver->getCurrentService()?->getName()) {
$this->value = self::Self;
}
}


public function generateCode(DI\PhpGenerator $generator): string
{
return match (true) {
Expand Down
10 changes: 5 additions & 5 deletions src/DI/Definitions/ServiceDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -161,14 +161,14 @@ public function complete(Nette\DI\Resolver $resolver): void
{
$entity = $this->creator->getEntity();
if ($entity instanceof Reference && !$this->creator->arguments && !$this->setup) {
$ref = $resolver->normalizeReference($entity);
$this->setCreator([new Reference(Nette\DI\ContainerBuilder::ThisContainer), 'getService'], [$ref->getValue()]);
$entity->complete($resolver);
$this->setCreator([new Reference(Nette\DI\ContainerBuilder::ThisContainer), 'getService'], [$entity->getValue()]);
}

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

foreach ($this->setup as &$setup) {
$setup = $resolver->withCurrentServiceAvailable()->completeStatement($setup);
foreach ($this->setup as $setup) {
$setup->complete($resolver->withCurrentServiceAvailable());
}
}

Expand Down
199 changes: 198 additions & 1 deletion src/DI/Definitions/Statement.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Nette\DI\ServiceCreationException;
use Nette\PhpGenerator as Php;
use Nette\Utils\Callback;
use Nette\Utils\Validators;


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

public function resolveType(Resolver $resolver): ?string
{
$entity = $resolver->normalizeEntity($this);
$entity = $this->normalizeEntity($resolver);

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


public function complete(Resolver $resolver): void
{
$entity = $this->normalizeEntity($resolver);
$this->convertReferences($resolver);
$arguments = $this->arguments;

switch (true) {
case $this->arguments === Resolver::getFirstClassCallable():
if (!is_array($entity) || !Php\Helpers::isIdentifier($entity[1])) {
throw new ServiceCreationException(sprintf('Cannot create closure for %s(...)', $entity));
}
if ($entity[0] instanceof self) {
$entity[0]->complete($resolver);
}
break;

case is_string($entity) && str_contains($entity, '?'): // PHP literal
break;

case $entity === 'not':
if (count($arguments) !== 1) {
throw new ServiceCreationException(sprintf('Function %s() expects 1 parameter, %s given.', $entity, count($arguments)));
}

$this->entity = ['', '!'];
break;

case $entity === 'bool':
case $entity === 'int':
case $entity === 'float':
case $entity === 'string':
if (count($arguments) !== 1) {
throw new ServiceCreationException(sprintf('Function %s() expects 1 parameter, %s given.', $entity, count($arguments)));
}

$arguments = [$arguments[0], $entity];
$this->entity = [DI\Helpers::class, 'convertType'];
break;

case is_string($entity): // create class
if (!class_exists($entity)) {
throw new ServiceCreationException(sprintf("Class '%s' not found.", $entity));
} elseif ((new \ReflectionClass($entity))->isAbstract()) {
throw new ServiceCreationException(sprintf('Class %s is abstract.', $entity));
} elseif (($rm = (new \ReflectionClass($entity))->getConstructor()) !== null && !$rm->isPublic()) {
throw new ServiceCreationException(sprintf('Class %s has %s constructor.', $entity, $rm->isProtected() ? 'protected' : 'private'));
} elseif ($constructor = (new \ReflectionClass($entity))->getConstructor()) {
$arguments = $resolver->autowireServices($constructor, $arguments);
$resolver->addDependency($constructor);
} elseif ($arguments) {
throw new ServiceCreationException(sprintf(
'Unable to pass arguments, class %s has no constructor.',
$entity,
));
}

break;

case $entity instanceof Reference:
if ($arguments) {
$e = $resolver->completeException(new ServiceCreationException(sprintf('Parameters were passed to reference @%s, although references cannot have any parameters.', $entity->getValue())), $resolver->getCurrentService());
trigger_error($e->getMessage(), E_USER_DEPRECATED);
}
$this->entity = [new Reference(DI\ContainerBuilder::ThisContainer), DI\Container::getMethodName($entity->getValue())];
break;

case is_array($entity):
if (!preg_match('#^\$?(\\\\?' . Php\Helpers::ReIdentifier . ')+(\[\])?$#D', $entity[1])) {
throw new ServiceCreationException(sprintf(
"Expected function, method or property name, '%s' given.",
$entity[1],
));
}

switch (true) {
case $entity[0] === '': // function call
if (!function_exists($entity[1])) {
throw new ServiceCreationException(sprintf("Function %s doesn't exist.", $entity[1]));
}

$rf = new \ReflectionFunction($entity[1]);
$arguments = $resolver->autowireServices($rf, $arguments);
$resolver->addDependency($rf);
break;

case $entity[0] instanceof self:
$entity[0]->complete($resolver);
// break omitted

case is_string($entity[0]): // static method call
case $entity[0] instanceof Reference:
if ($entity[1][0] === '$') { // property getter, setter or appender
Validators::assert($arguments, 'list:0..1', "setup arguments for '" . Callback::toString($entity) . "'");
if (!$arguments && str_ends_with($entity[1], '[]')) {
throw new ServiceCreationException(sprintf('Missing argument for %s.', $entity[1]));
}
} elseif (
$type = ($entity[0] instanceof Expression ? $entity[0] : new self($entity[0]))->resolveType($resolver)
) {
$rc = new \ReflectionClass($type);
if ($rc->hasMethod($entity[1])) {
$rm = $rc->getMethod($entity[1]);
if (!$rm->isPublic()) {
throw new ServiceCreationException(sprintf('%s::%s() is not callable.', $type, $entity[1]));
}

$arguments = $resolver->autowireServices($rm, $arguments);
$resolver->addDependency($rm);
}
}
}
}

try {
$this->arguments = $this->completeArguments($resolver, $arguments);
} catch (ServiceCreationException $e) {
if (!str_contains($e->getMessage(), ' (used in')) {
$e->setMessage($e->getMessage() . " (used in {$resolver->entityToString($entity)})");
}

throw $e;
}
}


public function completeArguments(Resolver $resolver, array $arguments): array
{
array_walk_recursive($arguments, function (&$val) use ($resolver): void {
if ($val instanceof self) {
if ($val->entity === 'typed' || $val->entity === 'tagged') {
$services = [];
$current = $resolver->getCurrentService()?->getName();
foreach ($val->arguments as $argument) {
foreach ($val->entity === 'tagged' ? $resolver->getContainerBuilder()->findByTag($argument) : $resolver->getContainerBuilder()->findAutowired($argument) as $name => $foo) {
if ($name !== $current) {
$services[] = new Reference($name);
}
}
}

$val = $this->completeArguments($resolver, $services);
} else {
$val->complete($resolver);
}
} elseif ($val instanceof Definition || $val instanceof Reference) {
$val = (new self($val))->normalizeEntity($resolver);
}
});
return $arguments;
}


/** Returns literal, Class, Reference, [Class, member], [, globalFunc], [Reference, member], [Statement, member] */
private function normalizeEntity(Resolver $resolver): string|array|Reference|null
{
if (is_array($this->entity)) {
$item = &$this->entity[0];
} else {
$item = &$this->entity;
}

if ($item instanceof Definition) {
if ($resolver->getContainerBuilder()->getDefinition($item->getName()) !== $item) {
throw new ServiceCreationException(sprintf("Service '%s' does not match the expected service.", $item->getName()));

}
$item = new Reference($item->getName());
}

if ($item instanceof Reference) {
$item->complete($resolver);
}

return $this->entity;
}


private function convertReferences(Resolver $resolver): void
{
array_walk_recursive($this->arguments, function (&$val) use ($resolver): void {
if (is_string($val) && strlen($val) > 1 && $val[0] === '@' && $val[1] !== '@') {
$pair = explode('::', substr($val, 1), 2);
if (!isset($pair[1])) { // @service
$val = new Reference($pair[0]);
} elseif (preg_match('#^[A-Z][a-zA-Z0-9_]*$#D', $pair[1])) { // @service::CONSTANT
$val = DI\ContainerBuilder::literal((new Reference($pair[0]))->resolveType($resolver) . '::' . $pair[1]);
} else { // @service::property
$val = new self([new Reference($pair[0]), '$' . $pair[1]]);
}
} elseif (is_string($val) && str_starts_with($val, '@@')) { // escaped text @@
$val = substr($val, 1);
}
});
}


/**
* Formats PHP code for class instantiating, function calling or property setting in PHP.
*/
Expand Down
Loading

0 comments on commit 673b9a8

Please sign in to comment.