| 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 |
use Packetery\Nette\DI\Helpers; |
| 12 |
use Packetery\Nette\DI\ServiceCreationException; |
| 13 |
use Packetery\Nette\PhpGenerator as Php; |
| 14 |
use Packetery\Nette\Utils\Reflection; |
| 15 |
use Packetery\Nette\Utils\Type; |
| 16 |
/** |
| 17 |
* Definition of standard service. |
| 18 |
*/ |
| 19 |
final class FactoryDefinition extends Definition |
| 20 |
{ |
| 21 |
private const MethodCreate = 'create'; |
| 22 |
/** @var array */ |
| 23 |
public $parameters = []; |
| 24 |
/** @var Definition */ |
| 25 |
private $resultDefinition; |
| 26 |
public function __construct() |
| 27 |
{ |
| 28 |
$this->resultDefinition = new ServiceDefinition(); |
| 29 |
} |
| 30 |
/** @return static */ |
| 31 |
public function setImplement(string $interface) |
| 32 |
{ |
| 33 |
if (!\interface_exists($interface)) { |
| 34 |
throw new \Packetery\Nette\InvalidArgumentException(\sprintf("Service '%s': Interface '%s' not found.", $this->getName(), $interface)); |
| 35 |
} |
| 36 |
$rc = new \ReflectionClass($interface); |
| 37 |
$method = $rc->getMethods()[0] ?? null; |
| 38 |
if (!$method || $method->isStatic() || $method->name !== self::MethodCreate || \count($rc->getMethods()) > 1) { |
| 39 |
throw new \Packetery\Nette\InvalidArgumentException(\sprintf("Service '%s': Interface %s must have just one non-static method create().", $this->getName(), $interface)); |
| 40 |
} |
| 41 |
try { |
| 42 |
Helpers::ensureClassType(Type::fromReflection($method), "return type of {$interface}::create()"); |
| 43 |
} catch (\Packetery\Nette\DI\ServiceCreationException $e) { |
| 44 |
\trigger_error($e->getMessage(), \E_USER_DEPRECATED); |
| 45 |
} |
| 46 |
return parent::setType($interface); |
| 47 |
} |
| 48 |
public function getImplement() : ?string |
| 49 |
{ |
| 50 |
return $this->getType(); |
| 51 |
} |
| 52 |
public final function getResultType() : ?string |
| 53 |
{ |
| 54 |
return $this->resultDefinition->getType(); |
| 55 |
} |
| 56 |
/** @return static */ |
| 57 |
public function setResultDefinition(Definition $definition) |
| 58 |
{ |
| 59 |
$this->resultDefinition = $definition; |
| 60 |
return $this; |
| 61 |
} |
| 62 |
/** @return ServiceDefinition */ |
| 63 |
public function getResultDefinition() : Definition |
| 64 |
{ |
| 65 |
return $this->resultDefinition; |
| 66 |
} |
| 67 |
/** @deprecated */ |
| 68 |
public function setParameters(array $params) |
| 69 |
{ |
| 70 |
if ($params) { |
| 71 |
$old = $new = []; |
| 72 |
foreach ($params as $k => $v) { |
| 73 |
$tmp = \explode(' ', \is_int($k) ? $v : $k); |
| 74 |
$old[] = '%' . \end($tmp) . '%'; |
| 75 |
$new[] = '$' . \end($tmp); |
| 76 |
} |
| 77 |
\trigger_error(\sprintf("Service '%s': Option 'parameters' is deprecated and should be removed. The %s should be replaced with %s in configuration.", $this->getName(), \implode(', ', $old), \implode(', ', $new)), \E_USER_DEPRECATED); |
| 78 |
} |
| 79 |
$this->parameters = $params; |
| 80 |
return $this; |
| 81 |
} |
| 82 |
/** @deprecated */ |
| 83 |
public function getParameters() : array |
| 84 |
{ |
| 85 |
return $this->parameters; |
| 86 |
} |
| 87 |
public function resolveType(\Packetery\Nette\DI\Resolver $resolver) : void |
| 88 |
{ |
| 89 |
$interface = $this->getType(); |
| 90 |
if (!$interface) { |
| 91 |
throw new ServiceCreationException('Type is missing in definition of service.'); |
| 92 |
} |
| 93 |
$method = new \ReflectionMethod($interface, self::MethodCreate); |
| 94 |
$type = Type::fromReflection($method) ?? Helpers::getReturnTypeAnnotation($method); |
| 95 |
$resultDef = $this->resultDefinition; |
| 96 |
try { |
| 97 |
$resolver->resolveDefinition($resultDef); |
| 98 |
} catch (ServiceCreationException $e) { |
| 99 |
if ($resultDef->getType()) { |
| 100 |
throw $e; |
| 101 |
} |
| 102 |
$resultDef->setType(Helpers::ensureClassType($type, "return type of {$interface}::create()")); |
| 103 |
$resolver->resolveDefinition($resultDef); |
| 104 |
} |
| 105 |
if ($type && !$type->allows($resultDef->getType())) { |
| 106 |
throw new ServiceCreationException(\sprintf('Factory for %s cannot create incompatible %s type.', $type, $resultDef->getType())); |
| 107 |
} |
| 108 |
} |
| 109 |
public function complete(\Packetery\Nette\DI\Resolver $resolver) : void |
| 110 |
{ |
| 111 |
$resultDef = $this->resultDefinition; |
| 112 |
if ($resultDef instanceof ServiceDefinition) { |
| 113 |
if (!$this->parameters) { |
| 114 |
$this->completeParameters($resolver); |
| 115 |
} |
| 116 |
$this->convertArguments($resultDef->getCreator()->arguments); |
| 117 |
foreach ($resultDef->getSetup() as $setup) { |
| 118 |
$this->convertArguments($setup->arguments); |
| 119 |
} |
| 120 |
if ($resultDef->getEntity() instanceof Reference && !$resultDef->getCreator()->arguments) { |
| 121 |
$resultDef->setCreator([ |
| 122 |
// render as $container->createMethod() |
| 123 |
new Reference(\Packetery\Nette\DI\ContainerBuilder::ThisContainer), |
| 124 |
\Packetery\Nette\DI\Container::getMethodName($resultDef->getEntity()->getValue()), |
| 125 |
]); |
| 126 |
} |
| 127 |
} |
| 128 |
$resolver->completeDefinition($resultDef); |
| 129 |
} |
| 130 |
private function completeParameters(\Packetery\Nette\DI\Resolver $resolver) : void |
| 131 |
{ |
| 132 |
$interface = $this->getType(); |
| 133 |
$method = new \ReflectionMethod($interface, self::MethodCreate); |
| 134 |
$ctorParams = []; |
| 135 |
if (($class = $resolver->resolveEntityType($this->resultDefinition->getCreator())) && ($ctor = (new \ReflectionClass($class))->getConstructor())) { |
| 136 |
foreach ($ctor->getParameters() as $param) { |
| 137 |
$ctorParams[$param->name] = $param; |
| 138 |
} |
| 139 |
} |
| 140 |
foreach ($method->getParameters() as $param) { |
| 141 |
$methodType = Type::fromReflection($param); |
| 142 |
if (isset($ctorParams[$param->name])) { |
| 143 |
$ctorParam = $ctorParams[$param->name]; |
| 144 |
$ctorType = Type::fromReflection($ctorParam); |
| 145 |
if ($ctorType && !$ctorType->allows((string) $methodType)) { |
| 146 |
throw new ServiceCreationException(\sprintf("Type of \$%s in %s::create() doesn't match type in %s constructor.", $param->name, $interface, $class)); |
| 147 |
} |
| 148 |
$this->resultDefinition->getCreator()->arguments[$ctorParam->getPosition()] = new Php\Literal('$' . $ctorParam->name); |
| 149 |
} elseif (!$this->resultDefinition->getSetup()) { |
| 150 |
// [param1, param2] => '$param1, $param2' |
| 151 |
$stringifyParams = function (array $params) : string { |
| 152 |
return \implode(', ', \array_map(function (string $param) { |
| 153 |
return '$' . $param; |
| 154 |
}, $params)); |
| 155 |
}; |
| 156 |
$ctorParamsKeys = \array_keys($ctorParams); |
| 157 |
$hint = \Packetery\Nette\Utils\Helpers::getSuggestion($ctorParamsKeys, $param->name); |
| 158 |
throw new ServiceCreationException(\sprintf('Cannot implement %s::create(): factory method parameters (%s) are not matching %s::__construct() parameters (%s).', $interface, $stringifyParams(\array_map(function (\ReflectionParameter $param) { |
| 159 |
return $param->name; |
| 160 |
}, $method->getParameters())), $class, $stringifyParams($ctorParamsKeys)) . ($hint ? " Did you mean to use '\${$hint}' in factory method?" : '')); |
| 161 |
} |
| 162 |
$paramDef = $methodType . ' ' . $param->name; |
| 163 |
if ($param->isDefaultValueAvailable()) { |
| 164 |
$this->parameters[$paramDef] = Reflection::getParameterDefaultValue($param); |
| 165 |
} else { |
| 166 |
$this->parameters[] = $paramDef; |
| 167 |
} |
| 168 |
} |
| 169 |
} |
| 170 |
public function convertArguments(array &$args) : void |
| 171 |
{ |
| 172 |
foreach ($args as &$v) { |
| 173 |
if (\is_string($v) && $v && $v[0] === '$') { |
| 174 |
$v = new Php\Literal($v); |
| 175 |
} |
| 176 |
} |
| 177 |
} |
| 178 |
public function generateMethod(Php\Method $method, \Packetery\Nette\DI\PhpGenerator $generator) : void |
| 179 |
{ |
| 180 |
$class = (new Php\ClassType())->addImplement($this->getType()); |
| 181 |
$class->addProperty('container')->setPrivate(); |
| 182 |
$class->addMethod('__construct')->addBody('$this->container = $container;')->addParameter('container')->setType($generator->getClassName()); |
| 183 |
$methodCreate = $class->addMethod(self::MethodCreate); |
| 184 |
$this->resultDefinition->generateMethod($methodCreate, $generator); |
| 185 |
$body = $methodCreate->getBody(); |
| 186 |
$body = \str_replace('$this', '$this->container', $body); |
| 187 |
$body = \str_replace('$this->container->container', '$this->container', $body); |
| 188 |
$rm = new \ReflectionMethod($this->getType(), self::MethodCreate); |
| 189 |
$methodCreate->setParameters($generator->convertParameters($this->parameters))->setReturnType((string) (Type::fromReflection($rm) ?? $this->getResultType()))->setBody($body); |
| 190 |
$method->setBody('return new class ($this) ' . $class . ';'); |
| 191 |
} |
| 192 |
public function __clone() |
| 193 |
{ |
| 194 |
parent::__clone(); |
| 195 |
$this->resultDefinition = \unserialize(\serialize($this->resultDefinition)); |
| 196 |
} |
| 197 |
} |
| 198 |
|