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

nette / di / 4292649159

pending completion
4292649159

push

github

David Grudl
more self explanatory message for factory and service mismatch (closes #199) (#284)

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

2086 of 2207 relevant lines covered (94.52%)

0.95 hits per line

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

96.23
/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\Type;
17

18

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

26
        private Definition $resultDefinition;
27

28

29
        public function __construct()
30
        {
31
                $this->resultDefinition = new ServiceDefinition;
1✔
32
        }
1✔
33

34

35
        public function setImplement(string $interface): static
1✔
36
        {
37
                if (!interface_exists($interface)) {
1✔
38
                        throw new Nette\InvalidArgumentException(sprintf(
1✔
39
                                "[%s]\nInterface '%s' not found.",
1✔
40
                                $this->getDescriptor(),
1✔
41
                                $interface,
42
                        ));
43
                }
44

45
                $rc = new \ReflectionClass($interface);
1✔
46
                $method = $rc->getMethods()[0] ?? null;
1✔
47
                if (!$method || $method->isStatic() || $method->name !== self::MethodCreate || count($rc->getMethods()) > 1) {
1✔
48
                        throw new Nette\InvalidArgumentException(sprintf(
1✔
49
                                "[%s]\nInterface %s must have just one non-static method create().",
1✔
50
                                $this->getDescriptor(),
1✔
51
                                $interface,
52
                        ));
53
                }
54

55
                Helpers::ensureClassType(Type::fromReflection($method), "return type of $interface::create()", $this->getDescriptor());
1✔
56

57
                return parent::setType($interface);
1✔
58
        }
59

60

61
        public function getImplement(): ?string
62
        {
63
                return $this->getType();
1✔
64
        }
65

66

67
        public function getResultType(): ?string
68
        {
69
                return $this->resultDefinition->getType();
1✔
70
        }
71

72

73
        public function setResultDefinition(Definition $definition): static
1✔
74
        {
75
                $this->resultDefinition = $definition;
1✔
76
                return $this;
1✔
77
        }
78

79

80
        /** @return ServiceDefinition */
81
        public function getResultDefinition(): Definition
82
        {
83
                return $this->resultDefinition;
1✔
84
        }
85

86

87
        public function resolveType(Nette\DI\Resolver $resolver): void
1✔
88
        {
89
                if (!$this->getType()) {
1✔
90
                        throw new ServiceCreationException('Type is missing in definition of service.');
1✔
91
                }
92

93
                $type = Type::fromReflection(new \ReflectionMethod($this->getType(), self::MethodCreate));
1✔
94

95
                $resultDef = $this->resultDefinition;
1✔
96
                try {
97
                        $resolver->resolveDefinition($resultDef);
1✔
98
                } catch (ServiceCreationException $e) {
1✔
99
                        if ($resultDef->getType()) {
1✔
100
                                throw $e;
×
101
                        }
102

103
                        $resultDef->setType($type->getSingleName());
1✔
104
                        $resolver->resolveDefinition($resultDef);
1✔
105
                }
106

107
                if ($type && !$type->allows($resultDef->getType())) {
1✔
108
                        throw new ServiceCreationException(sprintf(
1✔
109
                                "[%s]\nFactory for %s cannot create incompatible %s type.",
1✔
110
                                $this->getDescriptor(),
1✔
111
                                $type,
112
                                $resultDef->getType(),
1✔
113
                        ));
114
                }
115
        }
1✔
116

117

118
        public function complete(Nette\DI\Resolver $resolver): void
1✔
119
        {
120
                $resultDef = $this->resultDefinition;
1✔
121

122
                if ($resultDef instanceof ServiceDefinition) {
1✔
123
                        $this->completeParameters($resolver);
1✔
124

125
                        $this->convertArguments($resultDef->getCreator()->arguments);
1✔
126
                        foreach ($resultDef->getSetup() as $setup) {
1✔
127
                                $this->convertArguments($setup->arguments);
1✔
128
                        }
129

130
                        if ($resultDef->getEntity() instanceof Reference && !$resultDef->getCreator()->arguments) {
1✔
131
                                $resultDef->setCreator([ // render as $container->createMethod()
1✔
132
                                        new Reference(Nette\DI\ContainerBuilder::ThisContainer),
1✔
133
                                        Nette\DI\Container::getMethodName($resultDef->getEntity()->getValue()),
1✔
134
                                ]);
135
                        }
136
                }
137

138
                $resolver->completeDefinition($resultDef);
1✔
139
        }
1✔
140

141

142
        private function completeParameters(Nette\DI\Resolver $resolver): void
1✔
143
        {
144
                $interface = $this->getType();
1✔
145
                $method = new \ReflectionMethod($interface, self::MethodCreate);
1✔
146

147
                $ctorParams = [];
1✔
148
                if (
149
                        ($class = $resolver->resolveEntityType($this->resultDefinition->getCreator()))
1✔
150
                        && ($ctor = (new \ReflectionClass($class))->getConstructor())
1✔
151
                ) {
152
                        foreach ($ctor->getParameters() as $param) {
1✔
153
                                $ctorParams[$param->name] = $param;
1✔
154
                        }
155
                }
156

157
                foreach ($method->getParameters() as $param) {
1✔
158
                        if (isset($ctorParams[$param->name])) {
1✔
159
                                $ctorParam = $ctorParams[$param->name];
1✔
160
                                $ctorType = Type::fromReflection($ctorParam);
1✔
161
                                if ($ctorType && !$ctorType->allows((string) Type::fromReflection($param))) {
1✔
162
                                        throw new ServiceCreationException(sprintf(
1✔
163
                                                "Type of \$%s in %s::create() doesn't match type in %s constructor.",
1✔
164
                                                $param->name,
1✔
165
                                                $interface,
166
                                                $class,
167
                                        ));
168
                                }
169

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

172
                        } elseif (!$this->resultDefinition->getSetup()) {
1✔
173
                                // [param1, param2] => '$param1, $param2'
174
                                $stringifyParams = static fn(array $params): string => implode(', ', array_map(
1✔
175
                                        static fn(string $param): string => sprintf('$%s', $param),
1✔
176
                                        $params,
177
                                ));
178
                                $ctorParamsKeys = array_keys($ctorParams);
1✔
179
                                $hint = Nette\Utils\Helpers::getSuggestion($ctorParamsKeys, $param->name);
1✔
180
                                throw new ServiceCreationException(sprintf(
1✔
181
                                        'Cannot implement %s::create(): factory method parameters (%s) are not matching %s::__construct() parameters (%s).',
1✔
182
                                        $interface,
183
                                        $stringifyParams(array_map(static fn(\ReflectionParameter $param): string => $param->name, $method->getParameters())),
1✔
184
                                        $class,
185
                                        $stringifyParams($ctorParamsKeys),
1✔
186
                                ) . ($hint ? " Did you mean to use '\${$hint}' in factory method?" : ''));
1✔
187
                        }
188
                }
189
        }
1✔
190

191

192
        public function convertArguments(array &$args): void
1✔
193
        {
194
                foreach ($args as &$v) {
1✔
195
                        if (is_string($v) && $v && $v[0] === '$') {
1✔
196
                                $v = new Php\Literal($v);
×
197
                        }
198
                }
199
        }
1✔
200

201

202
        public function generateMethod(Php\Method $method, Nette\DI\PhpGenerator $generator): void
1✔
203
        {
204
                $class = (new Php\ClassType)
1✔
205
                        ->addImplement($this->getType());
1✔
206

207
                $class->addProperty('container')
1✔
208
                        ->setPrivate();
1✔
209

210
                $class->addMethod('__construct')
1✔
211
                        ->addBody('$this->container = $container;')
1✔
212
                        ->addParameter('container')
1✔
213
                        ->setType($generator->getClassName());
1✔
214

215
                $methodCreate = $class->addMethod(self::MethodCreate);
1✔
216
                $this->resultDefinition->generateMethod($methodCreate, $generator);
1✔
217
                $body = $methodCreate->getBody();
1✔
218
                $body = str_replace('$this', '$this->container', $body);
1✔
219
                $body = str_replace('$this->container->container', '$this->container', $body);
1✔
220

221
                $rm = new \ReflectionMethod($this->getType(), self::MethodCreate);
1✔
222
                $methodCreate
223
                        ->setParameters(array_map([new Php\Factory, 'fromParameterReflection'], $rm->getParameters()))
1✔
224
                        ->setReturnType((string) Type::fromReflection($rm))
1✔
225
                        ->setBody($body);
1✔
226

227
                $method->setBody('return new class ($this) ' . $class . ';');
1✔
228
        }
1✔
229

230

231
        public function __clone()
232
        {
233
                parent::__clone();
×
234
                $this->resultDefinition = unserialize(serialize($this->resultDefinition));
×
235
        }
236
}
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