Skip to content

Commit 83e1515

Browse files
committed
Unified type validation using Helper::ensureClassType()
1 parent bad278b commit 83e1515

16 files changed

+74
-157
lines changed

src/DI/Container.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,12 @@ public function addService(string $name, $service)
7575
throw new Nette\InvalidArgumentException(sprintf("Service '%s' must be a object, %s given.", $name, gettype($service)));
7676
}
7777

78-
$type = $service instanceof \Closure
79-
? (string) Nette\Utils\Reflection::getReturnType(new \ReflectionFunction($service))
80-
: get_class($service);
78+
if ($service instanceof \Closure) {
79+
$rt = Nette\Utils\Type::fromReflection(new \ReflectionFunction($service));
80+
$type = $rt ? Helpers::ensureClassType($rt, 'return type of factory') : '';
81+
} else {
82+
$type = get_class($service);
83+
}
8184

8285
if (!isset($this->methods[self::getMethodName($name)])) {
8386
$this->types[$name] = $type;

src/DI/Definitions/AccessorDefinition.php

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
namespace Nette\DI\Definitions;
1111

1212
use Nette;
13-
use Nette\DI\ServiceCreationException;
13+
use Nette\DI\Helpers;
1414
use Nette\Utils\Type;
1515

1616

@@ -99,18 +99,8 @@ public function complete(Nette\DI\Resolver $resolver): void
9999
if (!$this->reference) {
100100
$interface = $this->getType();
101101
$method = new \ReflectionMethod($interface, self::METHOD_GET);
102-
$returnType = Nette\DI\Helpers::getReturnType($method);
103-
104-
if (!$returnType) {
105-
throw new ServiceCreationException(sprintf('Method %s::get() has no return type or annotation @return.', $interface));
106-
} elseif (!class_exists($returnType) && !interface_exists($returnType)) {
107-
throw new ServiceCreationException(sprintf(
108-
"Class '%s' not found.\nCheck the return type or annotation @return of the %s::get() method.",
109-
$returnType,
110-
$interface
111-
));
112-
}
113-
$this->setReference($returnType);
102+
$type = Type::fromReflection($method) ?? Helpers::getReturnTypeAnnotation($method);
103+
$this->setReference(Helpers::ensureClassType($type, "return type of $interface::get()"));
114104
}
115105

116106
$this->reference = $resolver->normalizeReference($this->reference);

src/DI/Definitions/FactoryDefinition.php

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
namespace Nette\DI\Definitions;
1111

1212
use Nette;
13+
use Nette\DI\Helpers;
1314
use Nette\DI\ServiceCreationException;
1415
use Nette\Utils\Reflection;
1516
use Nette\Utils\Type;
@@ -189,17 +190,8 @@ public function resolveType(Nette\DI\Resolver $resolver): void
189190
throw new ServiceCreationException('Type is missing in definition of service.');
190191
}
191192
$method = new \ReflectionMethod($interface, self::METHOD_CREATE);
192-
$returnType = Nette\DI\Helpers::getReturnType($method);
193-
if (!$returnType) {
194-
throw new ServiceCreationException(sprintf('Method %s::create() has no return type or annotation @return.', $interface));
195-
} elseif (!class_exists($returnType) && !interface_exists($returnType)) {
196-
throw new ServiceCreationException(sprintf(
197-
"Class '%s' not found.\nCheck the return type or annotation @return of the %s::create() method.",
198-
$returnType,
199-
$interface
200-
));
201-
}
202-
$resultDef->setType($returnType);
193+
$type = Type::fromReflection($method) ?? Helpers::getReturnTypeAnnotation($method);
194+
$resultDef->setType(Helpers::ensureClassType($type, "return type of $interface::create()"));
203195
}
204196

205197
$resolver->resolveDefinition($resultDef);

src/DI/Extensions/InjectExtension.php

Lines changed: 5 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ private function updateDefinition(Definitions\ServiceDefinition $def): void
6262
unset($setups[$key]);
6363
}
6464
}
65-
self::checkType($class, $property, $type, $builder);
6665
array_unshift($setups, $inject);
6766
}
6867

@@ -114,17 +113,12 @@ public static function getInjectProperties(string $class): array
114113
$rp = new \ReflectionProperty($class, $name);
115114
$hasAttr = PHP_VERSION_ID >= 80000 && $rp->getAttributes(DI\Attributes\Inject::class);
116115
if ($hasAttr || DI\Helpers::parseAnnotation($rp, 'inject') !== null) {
117-
if ($type = Reflection::getPropertyType($rp)) {
118-
} elseif (!$hasAttr && ($type = DI\Helpers::parseAnnotation($rp, 'var'))) {
119-
if (strpos($type, '|') !== false) {
120-
throw new Nette\InvalidStateException(sprintf(
121-
'The %s is not expected to have a union type.',
122-
Reflection::toString($rp)
123-
));
124-
}
125-
$type = Reflection::expandClassName($type, Reflection::getPropertyDeclaringClass($rp));
116+
$type = Nette\Utils\Type::fromReflection($rp);
117+
if (!$type && !$hasAttr && ($annotation = DI\Helpers::parseAnnotation($rp, 'var'))) {
118+
$annotation = Reflection::expandClassName($annotation, Reflection::getPropertyDeclaringClass($rp));
119+
$type = Nette\Utils\Type::fromString($annotation);
126120
}
127-
$res[$name] = $type;
121+
$res[$name] = DI\Helpers::ensureClassType($type, 'type of property ' . Reflection::toString($rp));
128122
}
129123
}
130124
ksort($res);
@@ -147,35 +141,7 @@ public static function callInjects(DI\Container $container, $service): void
147141
}
148142

149143
foreach (self::getInjectProperties(get_class($service)) as $property => $type) {
150-
self::checkType($service, $property, $type, $container);
151144
$service->$property = $container->getByType($type);
152145
}
153146
}
154-
155-
156-
/**
157-
* @param object|string $class
158-
* @param DI\Container|DI\ContainerBuilder|null $container
159-
*/
160-
private static function checkType($class, string $name, ?string $type, $container): void
161-
{
162-
$propName = Reflection::toString(new \ReflectionProperty($class, $name));
163-
if (!$type) {
164-
throw new Nette\InvalidStateException(sprintf('Property %s has no type.', $propName));
165-
166-
} elseif (!class_exists($type) && !interface_exists($type)) {
167-
throw new Nette\InvalidStateException(sprintf(
168-
"Class '%s' required by %s not found. Check the property type and 'use' statements.",
169-
$type,
170-
$propName
171-
));
172-
173-
} elseif ($container && !$container->getByType($type, false)) {
174-
throw new Nette\DI\MissingServiceException(sprintf(
175-
'Service of type %s required by %s not found. Did you add it to configuration file?',
176-
$type,
177-
$propName
178-
));
179-
}
180-
}
181147
}

src/DI/Helpers.php

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Nette\DI\Definitions\Reference;
1414
use Nette\DI\Definitions\Statement;
1515
use Nette\Utils\Reflection;
16+
use Nette\Utils\Type;
1617

1718

1819
/**
@@ -202,22 +203,31 @@ public static function parseAnnotation(\Reflector $ref, string $name): ?string
202203
}
203204

204205

205-
public static function getReturnType(\ReflectionFunctionAbstract $func): ?string
206+
public static function getReturnTypeAnnotation(\ReflectionFunctionAbstract $func): ?Type
206207
{
207-
if ($type = Reflection::getReturnType($func)) {
208-
return $type;
209-
} elseif ($type = preg_replace('#[|\s].*#', '', (string) self::parseAnnotation($func, 'return'))) {
210-
if ($type === 'object' || $type === 'mixed') {
211-
return null;
212-
} elseif ($func instanceof \ReflectionMethod) {
213-
return $type === 'static' || $type === '$this'
214-
? $func->getDeclaringClass()->name
215-
: Reflection::expandClassName($type, $func->getDeclaringClass());
216-
} else {
217-
return $type;
218-
}
208+
$type = preg_replace('#[|\s].*#', '', (string) self::parseAnnotation($func, 'return'));
209+
if (!$type || $type === 'object' || $type === 'mixed') {
210+
return null;
211+
} elseif ($func instanceof \ReflectionMethod) {
212+
$type = $type === '$this' ? 'static' : $type;
213+
$type = Reflection::expandClassName($type, $func->getDeclaringClass());
219214
}
220-
return null;
215+
return Type::fromString($type);
216+
}
217+
218+
219+
public static function ensureClassType(?Type $type, string $hint): string
220+
{
221+
if (!$type) {
222+
throw new ServiceCreationException(sprintf('%s is not declared.', ucfirst($hint)));
223+
} elseif (!$type->isClass()) {
224+
throw new ServiceCreationException(sprintf("%s is not expected to be union/intersection/built-in, '%s' given.", ucfirst($hint), $type));
225+
}
226+
$class = $type->getSingleName();
227+
if (!class_exists($class) && !interface_exists($class)) {
228+
throw new ServiceCreationException(sprintf("Class '%s' not found.\nCheck the %s.", $class, $hint));
229+
}
230+
return $class;
221231
}
222232

223233

src/DI/Resolver.php

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Nette\DI\Definitions\Reference;
1515
use Nette\DI\Definitions\Statement;
1616
use Nette\PhpGenerator\Helpers as PhpHelpers;
17+
use Nette\Utils\Callback;
1718
use Nette\Utils\Reflection;
1819
use Nette\Utils\Strings;
1920
use Nette\Utils\Validators;
@@ -110,7 +111,7 @@ public function resolveEntityType(Statement $statement): ?string
110111

111112
try {
112113
/** @var \ReflectionMethod|\ReflectionFunction $reflection */
113-
$reflection = Nette\Utils\Callback::toReflection($entity[0] === '' ? $entity[1] : $entity);
114+
$reflection = Callback::toReflection($entity[0] === '' ? $entity[1] : $entity);
114115
$refClass = $reflection instanceof \ReflectionMethod
115116
? $reflection->getDeclaringClass()
116117
: null;
@@ -121,15 +122,15 @@ public function resolveEntityType(Statement $statement): ?string
121122
if (isset($e) || ($refClass && (!$reflection->isPublic()
122123
|| ($refClass->isTrait() && !$reflection->isStatic())
123124
))) {
124-
throw new ServiceCreationException(sprintf('Method %s() is not callable.', Nette\Utils\Callback::toString($entity)), 0, $e ?? null);
125+
throw new ServiceCreationException(sprintf('Method %s() is not callable.', Callback::toString($entity)), 0, $e ?? null);
125126
}
126127
$this->addDependency($reflection);
127128

128-
$type = Helpers::getReturnType($reflection);
129-
if ($type && !class_exists($type) && !interface_exists($type)) {
130-
throw new ServiceCreationException(sprintf("Class or interface '%s' not found. Check the return type of %s() method.", $type, Nette\Utils\Callback::toString($entity)));
129+
$type = Nette\Utils\Type::fromReflection($reflection) ?? Helpers::getReturnTypeAnnotation($reflection);
130+
if ($type) {
131+
return Helpers::ensureClassType($type, sprintf('return type of %s()', Callback::toString($entity)));
131132
}
132-
return $type;
133+
return null;
133134

134135
} elseif ($entity instanceof Reference) { // alias or factory
135136
return $this->resolveReferenceType($entity);
@@ -264,7 +265,7 @@ public function completeStatement(Statement $statement, bool $currentServiceAllo
264265
case is_string($entity[0]): // static method call
265266
case $entity[0] instanceof Reference:
266267
if ($entity[1][0] === '$') { // property getter, setter or appender
267-
Validators::assert($arguments, 'list:0..1', "setup arguments for '" . Nette\Utils\Callback::toString($entity) . "'");
268+
Validators::assert($arguments, 'list:0..1', "setup arguments for '" . Callback::toString($entity) . "'");
268269
if (!$arguments && substr($entity[1], -2) === '[]') {
269270
throw new ServiceCreationException(sprintf('Missing argument for %s.', $entity[1]));
270271
}

tests/DI/Container.dynamic.php80.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ $container = new Container;
1919
Assert::exception(function () use ($container) {
2020
@$container->addService('six', function (): \stdClass|\Closure {}); // @ triggers service should be defined as "imported"
2121
$container->getService('six');
22-
}, Nette\InvalidStateException::class, 'The {closure}%a?% is not expected to have a union%a?% type.');
22+
}, Nette\InvalidStateException::class, "Return type of factory is not expected to be union/intersection/built-in, 'stdClass|Closure' given.");

tests/DI/ContainerBuilder.factory.error.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ Assert::exception(function () {
7676
$builder->addFactoryDefinition('one')
7777
->setImplement('Bad4');
7878
$builder->complete();
79-
}, Nette\InvalidStateException::class, "Service 'one' (type of Bad4): Method create() has no return type or annotation @return.");
79+
}, Nette\InvalidStateException::class, "Service 'one' (type of Bad4): Return type of create() is not declared.");
8080

8181

8282
interface Bad5

tests/DI/ContainerBuilder.factory.resolveBuiltinTypes.phpt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ namespace
8282
$builder->addDefinition('a')
8383
->setFactory('@factory::createArray');
8484
$container = createContainer($builder);
85-
}, Nette\DI\ServiceCreationException::class, "Service 'a': Class or interface 'array' not found. Check the return type of A\\Factory::createArray() method.");
85+
}, Nette\DI\ServiceCreationException::class, "Service 'a': Return type of A\\Factory::createArray() is not expected to be union/intersection/built-in, 'array' given.");
8686

8787
Assert::exception(function () {
8888
$builder = new DI\ContainerBuilder;
@@ -91,7 +91,7 @@ namespace
9191
$builder->addDefinition('c')
9292
->setFactory('@factory::createCallable');
9393
$container = createContainer($builder);
94-
}, Nette\DI\ServiceCreationException::class, "Service 'c': Class or interface 'callable' not found. Check the return type of A\\Factory::createCallable() method.");
94+
}, Nette\DI\ServiceCreationException::class, "Service 'c': Return type of A\\Factory::createCallable() is not expected to be union/intersection/built-in, 'callable' given.");
9595

9696
Assert::exception(function () {
9797
$builder = new DI\ContainerBuilder;
@@ -100,7 +100,7 @@ namespace
100100
$builder->addDefinition('s')
101101
->setFactory('@factory::createString');
102102
$container = createContainer($builder);
103-
}, Nette\DI\ServiceCreationException::class, "Service 's': Class or interface 'string' not found. Check the return type of A\\Factory::createString() method.");
103+
}, Nette\DI\ServiceCreationException::class, "Service 's': Return type of A\\Factory::createString() is not expected to be union/intersection/built-in, 'string' given.");
104104

105105
Assert::exception(function () {
106106
$builder = new DI\ContainerBuilder;
@@ -109,7 +109,7 @@ namespace
109109
$builder->addDefinition('i')
110110
->setFactory('@factory::createInt');
111111
$container = createContainer($builder);
112-
}, Nette\DI\ServiceCreationException::class, "Service 'i': Class or interface 'int' not found. Check the return type of A\\Factory::createInt() method.");
112+
}, Nette\DI\ServiceCreationException::class, "Service 'i': Return type of A\\Factory::createInt() is not expected to be union/intersection/built-in, 'int' given.");
113113

114114
Assert::exception(function () {
115115
$builder = new DI\ContainerBuilder;
@@ -118,7 +118,7 @@ namespace
118118
$builder->addDefinition('b')
119119
->setFactory('@factory::createBool');
120120
$container = createContainer($builder);
121-
}, Nette\DI\ServiceCreationException::class, "Service 'b': Class or interface 'bool' not found. Check the return type of A\\Factory::createBool() method.");
121+
}, Nette\DI\ServiceCreationException::class, "Service 'b': Return type of A\\Factory::createBool() is not expected to be union/intersection/built-in, 'bool' given.");
122122

123123
Assert::exception(function () {
124124
$builder = new DI\ContainerBuilder;
@@ -127,7 +127,7 @@ namespace
127127
$builder->addDefinition('f')
128128
->setFactory('@factory::createFloat');
129129
$container = createContainer($builder);
130-
}, Nette\DI\ServiceCreationException::class, "Service 'f': Class or interface 'float' not found. Check the return type of A\\Factory::createFloat() method.");
130+
}, Nette\DI\ServiceCreationException::class, "Service 'f': Return type of A\\Factory::createFloat() is not expected to be union/intersection/built-in, 'float' given.");
131131

132132
Assert::exception(function () {
133133
$builder = new DI\ContainerBuilder;

tests/DI/Definitions.AccessorDefinition.resolve.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Assert::exception(function () {
3737
$resolver = new Nette\DI\Resolver(new Nette\DI\ContainerBuilder);
3838
$resolver->resolveDefinition($def);
3939
$resolver->completeDefinition($def);
40-
}, Nette\DI\ServiceCreationException::class, 'Service of type Good1: Method get() has no return type or annotation @return.');
40+
}, Nette\DI\ServiceCreationException::class, 'Service of type Good1: Return type of get() is not declared.');
4141

4242

4343
Assert::noError(function () {

0 commit comments

Comments
 (0)