| 1 |
<?php |
| 2 |
|
| 3 |
/** |
| 4 |
* This file is part of the Nette Framework (https://nette.org) |
| 5 |
* Copyright (c) 2004 David Grudl (https://davidgrudl.com) |
| 6 |
*/ |
| 7 |
declare (strict_types=1); |
| 8 |
namespace Packetery\Nette\DI\Definitions; |
| 9 |
|
| 10 |
use Packetery\Nette; |
| 11 |
/** |
| 12 |
* Multi accessor/factory definition. |
| 13 |
*/ |
| 14 |
final class LocatorDefinition extends Definition |
| 15 |
{ |
| 16 |
/** @var Reference[] */ |
| 17 |
private $references = []; |
| 18 |
/** @var string|null */ |
| 19 |
private $tagged; |
| 20 |
/** @return static */ |
| 21 |
public function setImplement(string $interface) |
| 22 |
{ |
| 23 |
if (!\interface_exists($interface)) { |
| 24 |
throw new \Packetery\Nette\InvalidArgumentException(\sprintf("Service '%s': Interface '%s' not found.", $this->getName(), $interface)); |
| 25 |
} |
| 26 |
$methods = (new \ReflectionClass($interface))->getMethods(); |
| 27 |
if (!$methods) { |
| 28 |
throw new \Packetery\Nette\InvalidArgumentException(\sprintf("Service '%s': Interface %s must have at least one method.", $this->getName(), $interface)); |
| 29 |
} |
| 30 |
foreach ($methods as $method) { |
| 31 |
if ($method->isStatic() || !(\preg_match('#^(get|create)$#', $method->name) && $method->getNumberOfParameters() === 1 || \preg_match('#^(get|create)[A-Z]#', $method->name) && $method->getNumberOfParameters() === 0)) { |
| 32 |
throw new \Packetery\Nette\InvalidArgumentException(\sprintf("Service '%s': Method %s::%s() does not meet the requirements: is create(\$name), get(\$name), create*() or get*() and is non-static.", $this->getName(), $interface, $method->name)); |
| 33 |
} |
| 34 |
if ($method->getNumberOfParameters() === 0) { |
| 35 |
try { |
| 36 |
\Packetery\Nette\DI\Helpers::ensureClassType(\Packetery\Nette\Utils\Type::fromReflection($method), "return type of {$interface}::{$method->name}()", \true); |
| 37 |
} catch (\Packetery\Nette\DI\ServiceCreationException $e) { |
| 38 |
\trigger_error($e->getMessage(), \E_USER_DEPRECATED); |
| 39 |
} |
| 40 |
} |
| 41 |
} |
| 42 |
return parent::setType($interface); |
| 43 |
} |
| 44 |
public function getImplement() : ?string |
| 45 |
{ |
| 46 |
return $this->getType(); |
| 47 |
} |
| 48 |
/** @return static */ |
| 49 |
public function setReferences(array $references) |
| 50 |
{ |
| 51 |
$this->references = []; |
| 52 |
foreach ($references as $name => $ref) { |
| 53 |
$this->references[$name] = \substr($ref, 0, 1) === '@' ? new Reference(\substr($ref, 1)) : Reference::fromType($ref); |
| 54 |
} |
| 55 |
return $this; |
| 56 |
} |
| 57 |
/** @return Reference[] */ |
| 58 |
public function getReferences() : array |
| 59 |
{ |
| 60 |
return $this->references; |
| 61 |
} |
| 62 |
/** @return static */ |
| 63 |
public function setTagged(?string $tagged) |
| 64 |
{ |
| 65 |
$this->tagged = $tagged; |
| 66 |
return $this; |
| 67 |
} |
| 68 |
public function getTagged() : ?string |
| 69 |
{ |
| 70 |
return $this->tagged; |
| 71 |
} |
| 72 |
public function resolveType(\Packetery\Nette\DI\Resolver $resolver) : void |
| 73 |
{ |
| 74 |
} |
| 75 |
public function complete(\Packetery\Nette\DI\Resolver $resolver) : void |
| 76 |
{ |
| 77 |
if ($this->tagged !== null) { |
| 78 |
$this->references = []; |
| 79 |
foreach ($resolver->getContainerBuilder()->findByTag($this->tagged) as $name => $tag) { |
| 80 |
if (isset($this->references[$tag])) { |
| 81 |
\trigger_error(\sprintf("Service '%s': duplicated tag '%s' with value '%s'.", $this->getName(), $this->tagged, $tag)); |
| 82 |
} |
| 83 |
$this->references[$tag] = new Reference($name); |
| 84 |
} |
| 85 |
} |
| 86 |
foreach ($this->references as $name => $ref) { |
| 87 |
$this->references[$name] = $resolver->normalizeReference($ref); |
| 88 |
} |
| 89 |
} |
| 90 |
public function generateMethod(\Packetery\Nette\PhpGenerator\Method $method, \Packetery\Nette\DI\PhpGenerator $generator) : void |
| 91 |
{ |
| 92 |
$class = (new \Packetery\Nette\PhpGenerator\ClassType())->addImplement($this->getType()); |
| 93 |
$class->addProperty('container')->setPrivate(); |
| 94 |
$class->addMethod('__construct')->addBody('$this->container = $container;')->addParameter('container')->setType($generator->getClassName()); |
| 95 |
foreach ((new \ReflectionClass($this->getType()))->getMethods() as $rm) { |
| 96 |
\preg_match('#^(get|create)(.*)#', $rm->name, $m); |
| 97 |
$name = \lcfirst($m[2]); |
| 98 |
$nullable = $rm->getReturnType()->allowsNull(); |
| 99 |
$methodInner = $class->addMethod($rm->name)->setReturnType((string) \Packetery\Nette\Utils\Type::fromReflection($rm)); |
| 100 |
if (!$name) { |
| 101 |
$class->addProperty('mapping', \array_map(function ($item) { |
| 102 |
return $item->getValue(); |
| 103 |
}, $this->references))->setPrivate(); |
| 104 |
$methodInner->setBody('if (!isset($this->mapping[$name])) { |
| 105 |
' . ($nullable ? 'return null;' : 'throw new \\Packetery\\Nette\\DI\\MissingServiceException("Service \'$name\' is not defined.");') . ' |
| 106 |
} |
| 107 |
return $this->container->' . $m[1] . 'Service($this->mapping[$name]);')->addParameter('name'); |
| 108 |
} elseif (isset($this->references[$name])) { |
| 109 |
$ref = $this->references[$name]->getValue(); |
| 110 |
if ($m[1] === 'get') { |
| 111 |
$methodInner->setBody('return $this->container->getService(?);', [$ref]); |
| 112 |
} else { |
| 113 |
$methodInner->setBody('return $this->container->?();', [\Packetery\Nette\DI\Container::getMethodName($ref)]); |
| 114 |
} |
| 115 |
} else { |
| 116 |
$methodInner->setBody($nullable ? 'return null;' : 'throw new \\Packetery\\Nette\\DI\\MissingServiceException("Service is not defined.");'); |
| 117 |
} |
| 118 |
} |
| 119 |
$method->setBody('return new class ($this) ' . $class . ';'); |
| 120 |
} |
| 121 |
} |
| 122 |
|