• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nette / di / 6350956765

29 Sep 2023 11:27AM UTC coverage: 93.789% (-0.4%) from 94.168%
6350956765

push

github

dg
Option 'class' is allowed again

Partially reverts commit 046f89cc3.

1 of 1 new or added line in 1 file covered. (100.0%)

2250 of 2399 relevant lines covered (93.79%)

0.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.74
/src/DI/Definitions/FactoryDefinition.php
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

8
declare(strict_types=1);
9

10
namespace Nette\DI\Definitions;
11

12
use Nette;
13
use Nette\DI\Helpers;
14
use Nette\DI\ServiceCreationException;
15
use Nette\PhpGenerator as Php;
16
use Nette\Utils\Reflection;
17
use Nette\Utils\Type;
18

19

20
/**
21
 * Definition of standard service.
22
 */
23
final class FactoryDefinition extends Definition
24
{
25
        private const MethodCreate = 'create';
26

27
        /** @var array */
28
        public $parameters = [];
29

30
        /** @var Definition */
31
        private $resultDefinition;
32

33

34
        public function __construct()
35
        {
36
                $this->resultDefinition = new ServiceDefinition;
1✔
37
        }
1✔
38

39

40
        /** @return static */
41
        public function setImplement(string $interface)
1✔
42
        {
43
                if (!interface_exists($interface)) {
1✔
44
                        throw new Nette\InvalidArgumentException(sprintf(
1✔
45
                                "Service '%s': Interface '%s' not found.",
1✔
46
                                $this->getName(),
1✔
47
                                $interface
48
                        ));
49
                }
50

51
                $rc = new \ReflectionClass($interface);
1✔
52
                $method = $rc->getMethods()[0] ?? null;
1✔
53
                if (!$method || $method->isStatic() || $method->name !== self::MethodCreate || count($rc->getMethods()) > 1) {
1✔
54
                        throw new Nette\InvalidArgumentException(sprintf(
1✔
55
                                "Service '%s': Interface %s must have just one non-static method create().",
1✔
56
                                $this->getName(),
1✔
57
                                $interface
58
                        ));
59
                }
60

61
                try {
62
                        Helpers::ensureClassType(Type::fromReflection($method), "return type of $interface::create()");
1✔
63
                } catch (Nette\DI\ServiceCreationException $e) {
1✔
64
                        trigger_error($e->getMessage(), E_USER_DEPRECATED);
1✔
65
                }
66

67
                return parent::setType($interface);
1✔
68
        }
69

70

71
        public function getImplement(): ?string
72
        {
73
                return $this->getType();
1✔
74
        }
75

76

77
        final public function getResultType(): ?string
78
        {
79
                return $this->resultDefinition->getType();
1✔
80
        }
81

82

83
        /** @return static */
84
        public function setResultDefinition(Definition $definition)
1✔
85
        {
86
                $this->resultDefinition = $definition;
1✔
87
                return $this;
1✔
88
        }
89

90

91
        /** @return ServiceDefinition */
92
        public function getResultDefinition(): Definition
93
        {
94
                return $this->resultDefinition;
1✔
95
        }
96

97

98
        /** @deprecated */
99
        public function setParameters(array $params)
1✔
100
        {
101
                if ($params) {
1✔
102
                        $old = $new = [];
1✔
103
                        foreach ($params as $k => $v) {
1✔
104
                                $tmp = explode(' ', is_int($k) ? $v : $k);
1✔
105
                                $old[] = '%' . end($tmp) . '%';
1✔
106
                                $new[] = '$' . end($tmp);
1✔
107
                        }
108

109
                        trigger_error(sprintf(
1✔
110
                                "Service '%s': Option 'parameters' is deprecated and should be removed. The %s should be replaced with %s in configuration.",
1✔
111
                                $this->getName(),
1✔
112
                                implode(', ', $old),
1✔
113
                                implode(', ', $new)
1✔
114
                        ), E_USER_DEPRECATED);
1✔
115
                }
116

117
                $this->parameters = $params;
1✔
118
                return $this;
1✔
119
        }
120

121

122
        /** @deprecated */
123
        public function getParameters(): array
124
        {
125
                return $this->parameters;
1✔
126
        }
127

128

129
        public function resolveType(Nette\DI\Resolver $resolver): void
1✔
130
        {
131
                $interface = $this->getType();
1✔
132
                if (!$interface) {
1✔
133
                        throw new ServiceCreationException('Type is missing in definition of service.');
1✔
134
                }
135

136
                $method = new \ReflectionMethod($interface, self::MethodCreate);
1✔
137
                $type = Type::fromReflection($method) ?? Helpers::getReturnTypeAnnotation($method);
1✔
138

139
                $resultDef = $this->resultDefinition;
1✔
140
                try {
141
                        $resolver->resolveDefinition($resultDef);
1✔
142
                } catch (ServiceCreationException $e) {
1✔
143
                        if ($resultDef->getType()) {
1✔
144
                                throw $e;
×
145
                        }
146

147
                        $resultDef->setType(Helpers::ensureClassType($type, "return type of $interface::create()"));
1✔
148
                        $resolver->resolveDefinition($resultDef);
1✔
149
                }
150

151
                if ($type && !$type->allows($resultDef->getType())) {
1✔
152
                        throw new ServiceCreationException(sprintf(
1✔
153
                                'Factory for %s cannot create incompatible %s type.',
1✔
154
                                $type,
155
                                $resultDef->getType()
1✔
156
                        ));
157
                }
158
        }
1✔
159

160

161
        public function complete(Nette\DI\Resolver $resolver): void
1✔
162
        {
163
                $resultDef = $this->resultDefinition;
1✔
164

165
                if ($resultDef instanceof ServiceDefinition) {
1✔
166
                        if (!$this->parameters) {
1✔
167
                                $this->completeParameters($resolver);
1✔
168
                        }
169

170
                        $this->convertArguments($resultDef->getCreator()->arguments);
1✔
171
                        foreach ($resultDef->getSetup() as $setup) {
1✔
172
                                $this->convertArguments($setup->arguments);
1✔
173
                        }
174

175
                        if ($resultDef->getEntity() instanceof Reference && !$resultDef->getCreator()->arguments) {
1✔
176
                                $resultDef->setCreator([ // render as $container->createMethod()
1✔
177
                                        new Reference(Nette\DI\ContainerBuilder::ThisContainer),
1✔
178
                                        Nette\DI\Container::getMethodName($resultDef->getEntity()->getValue()),
1✔
179
                                ]);
180
                        }
181
                }
182

183
                $resolver->completeDefinition($resultDef);
1✔
184
        }
1✔
185

186

187
        private function completeParameters(Nette\DI\Resolver $resolver): void
1✔
188
        {
189
                $interface = $this->getType();
1✔
190
                $method = new \ReflectionMethod($interface, self::MethodCreate);
1✔
191

192
                $ctorParams = [];
1✔
193
                if (
194
                        ($class = $resolver->resolveEntityType($this->resultDefinition->getCreator()))
1✔
195
                        && ($ctor = (new \ReflectionClass($class))->getConstructor())
1✔
196
                ) {
197
                        foreach ($ctor->getParameters() as $param) {
1✔
198
                                $ctorParams[$param->name] = $param;
1✔
199
                        }
200
                }
201

202
                foreach ($method->getParameters() as $param) {
1✔
203
                        $methodType = Type::fromReflection($param);
1✔
204
                        if (isset($ctorParams[$param->name])) {
1✔
205
                                $ctorParam = $ctorParams[$param->name];
1✔
206
                                $ctorType = Type::fromReflection($ctorParam);
1✔
207
                                if ($ctorType && !$ctorType->allows((string) $methodType)) {
1✔
208
                                        throw new ServiceCreationException(sprintf(
1✔
209
                                                "Type of \$%s in %s::create() doesn't match type in %s constructor.",
1✔
210
                                                $param->name,
1✔
211
                                                $interface,
212
                                                $class
213
                                        ));
214
                                }
215

216
                                $this->resultDefinition->getCreator()->arguments[$ctorParam->getPosition()] = new Php\Literal('$' . $ctorParam->name);
1✔
217

218
                        } elseif (!$this->resultDefinition->getSetup()) {
1✔
219
                                // [param1, param2] => '$param1, $param2'
220
                                $stringifyParams = function (array $params): string {
1✔
221
                                        return implode(', ', array_map(
1✔
222
                                                function (string $param) { return '$' . $param; },
1✔
223
                                                $params
224
                                        ));
225
                                };
1✔
226
                                $ctorParamsKeys = array_keys($ctorParams);
1✔
227
                                $hint = Nette\Utils\Helpers::getSuggestion($ctorParamsKeys, $param->name);
1✔
228
                                throw new ServiceCreationException(sprintf(
1✔
229
                                        'Cannot implement %s::create(): factory method parameters (%s) are not matching %s::__construct() parameters (%s).',
1✔
230
                                        $interface,
231
                                        $stringifyParams(array_map(function (\ReflectionParameter $param) { return $param->name; }, $method->getParameters())),
1✔
232
                                        $class,
233
                                        $stringifyParams($ctorParamsKeys)
1✔
234
                                ) . ($hint ? " Did you mean to use '\${$hint}' in factory method?" : ''));
1✔
235
                        }
236

237
                        $paramDef = $methodType . ' ' . $param->name;
1✔
238
                        if ($param->isDefaultValueAvailable()) {
1✔
239
                                $this->parameters[$paramDef] = Reflection::getParameterDefaultValue($param);
1✔
240
                        } else {
241
                                $this->parameters[] = $paramDef;
1✔
242
                        }
243
                }
244
        }
1✔
245

246

247
        public function convertArguments(array &$args): void
1✔
248
        {
249
                foreach ($args as &$v) {
1✔
250
                        if (is_string($v) && $v && $v[0] === '$') {
1✔
251
                                $v = new Php\Literal($v);
1✔
252
                        }
253
                }
254
        }
1✔
255

256

257
        public function generateMethod(Php\Method $method, Nette\DI\PhpGenerator $generator): void
1✔
258
        {
259
                $class = (new Php\ClassType)
1✔
260
                        ->addImplement($this->getType());
1✔
261

262
                $class->addProperty('container')
1✔
263
                        ->setPrivate();
1✔
264

265
                $class->addMethod('__construct')
1✔
266
                        ->addBody('$this->container = $container;')
1✔
267
                        ->addParameter('container')
1✔
268
                        ->setType($generator->getClassName());
1✔
269

270
                $methodCreate = $class->addMethod(self::MethodCreate);
1✔
271
                $this->resultDefinition->generateMethod($methodCreate, $generator);
1✔
272
                $body = $methodCreate->getBody();
1✔
273
                $body = str_replace('$this', '$this->container', $body);
1✔
274
                $body = str_replace('$this->container->container', '$this->container', $body);
1✔
275

276
                $rm = new \ReflectionMethod($this->getType(), self::MethodCreate);
1✔
277
                $methodCreate
278
                        ->setParameters($generator->convertParameters($this->parameters))
1✔
279
                        ->setReturnType((string) (Type::fromReflection($rm) ?? $this->getResultType()))
1✔
280
                        ->setBody($body);
1✔
281

282
                $method->setBody('return new class ($this) ' . $class . ';');
1✔
283
        }
1✔
284

285

286
        public function __clone()
287
        {
288
                parent::__clone();
×
289
                $this->resultDefinition = unserialize(serialize($this->resultDefinition));
×
290
        }
291
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc