PluginProbe
Packeta / trunk
Packeta vtrunk
2.3.2 2.3.1 trunk 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.3.0 1.3.1 1.3.2 1.4 1.4.1 1.4.2 1.4.3 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 All 56 releases
packeta / deps / nette / di / src / DI / Definitions / FactoryDefinition.php

FactoryDefinition.php in Packeta trunk, at deps/nette/di/src/DI/Definitions/FactoryDefinition.php

198 lines 8.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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