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

nette / di / 5707259747

pending completion
5707259747

push

github

dg
Accessor, Locator, Factory: generates promoted properties

9 of 9 new or added lines in 3 files covered. (100.0%)

2096 of 2216 relevant lines covered (94.58%)

0.95 hits per line

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

96.15
/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
                return parent::setType($interface);
1✔
57
        }
58

59

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

65

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

71

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

78

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

85

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

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

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

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

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

116

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

121
                if ($resultDef instanceof ServiceDefinition) {
1✔
122
                        $this->completeParameters($resolver);
1✔
123
                        $this->convertArguments($resultDef->getCreator()->arguments);
1✔
124
                        foreach ($resultDef->getSetup() as $setup) {
1✔
125
                                $this->convertArguments($setup->arguments);
1✔
126
                        }
127

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

136
                $resolver->completeDefinition($resultDef);
1✔
137
        }
1✔
138

139

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

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

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

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

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

189

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

199

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

205
                $class->addMethod('__construct')
1✔
206
                        ->addPromotedParameter('container')
1✔
207
                                ->setPrivate()
1✔
208
                                ->setType($generator->getClassName());
1✔
209

210
                $methodCreate = $class->addMethod(self::MethodCreate);
1✔
211
                $this->resultDefinition->generateMethod($methodCreate, $generator);
1✔
212
                $body = $methodCreate->getBody();
1✔
213
                $body = str_replace('$this', '$this->container', $body);
1✔
214
                $body = str_replace('$this->container->container', '$this->container', $body);
1✔
215

216
                $rm = new \ReflectionMethod($this->getType(), self::MethodCreate);
1✔
217
                $methodCreate
218
                        ->setParameters(array_map([new Php\Factory, 'fromParameterReflection'], $rm->getParameters()))
1✔
219
                        ->setReturnType((string) Type::fromReflection($rm))
1✔
220
                        ->setBody($body);
1✔
221

222
                $method->setBody('return new class ($this) ' . $class . ';');
1✔
223
        }
1✔
224

225

226
        public function __clone()
227
        {
228
                parent::__clone();
×
229
                $this->resultDefinition = unserialize(serialize($this->resultDefinition));
×
230
        }
231
}
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