Skip to content

Commit 21a840d

Browse files
committed
Reflection::getReturnType(), getParameterType() and getPropertyType() throws exception on union types
1 parent 22231d3 commit 21a840d

File tree

4 files changed

+291
-14
lines changed

4 files changed

+291
-14
lines changed

src/Utils/Reflection.php

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,49 +37,65 @@ public static function isBuiltinType(string $type): bool
3737
/**
3838
* Returns the type of return value of given function or method and normalizes `self`, `static`, and `parent` to actual class names.
3939
* If the function does not have a return type, it returns null.
40+
* If the function has union type, it throws Nette\InvalidStateException.
4041
*/
4142
public static function getReturnType(\ReflectionFunctionAbstract $func): ?string
4243
{
43-
$type = $func->getReturnType();
44-
return $type instanceof \ReflectionNamedType
45-
? ($func instanceof \ReflectionMethod ? self::normalizeType($type->getName(), $func) : $type->getName())
46-
: null;
44+
return self::getType($func, $func->getReturnType());
4745
}
4846

4947

5048
/**
5149
* Returns the type of given parameter and normalizes `self` and `parent` to the actual class names.
5250
* If the parameter does not have a type, it returns null.
51+
* If the parameter has union type, it throws Nette\InvalidStateException.
5352
*/
5453
public static function getParameterType(\ReflectionParameter $param): ?string
5554
{
56-
$type = $param->getType();
57-
return $type instanceof \ReflectionNamedType
58-
? self::normalizeType($type->getName(), $param)
59-
: null;
55+
return self::getType($param, $param->getType());
6056
}
6157

6258

6359
/**
6460
* Returns the type of given property and normalizes `self` and `parent` to the actual class names.
6561
* If the property does not have a type, it returns null.
62+
* If the property has union type, it throws Nette\InvalidStateException.
6663
*/
6764
public static function getPropertyType(\ReflectionProperty $prop): ?string
6865
{
69-
$type = PHP_VERSION_ID >= 70400 ? $prop->getType() : null;
70-
return $type instanceof \ReflectionNamedType
71-
? self::normalizeType($type->getName(), $prop)
72-
: null;
66+
return self::getType($prop, PHP_VERSION_ID >= 70400 ? $prop->getType() : null);
7367
}
7468

7569

7670
/**
77-
* @param \ReflectionMethod|\ReflectionParameter|\ReflectionProperty $reflection
71+
* @param \ReflectionFunction|\ReflectionMethod|\ReflectionParameter|\ReflectionProperty $reflection
72+
*/
73+
private static function getType($reflection, ?\ReflectionType $type): ?string
74+
{
75+
if ($type === null) {
76+
return null;
77+
78+
} elseif ($type instanceof \ReflectionNamedType) {
79+
return self::normalizeType($type->getName(), $reflection);
80+
81+
} elseif ($type instanceof \ReflectionUnionType) {
82+
throw new Nette\InvalidStateException('The ' . self::toString($reflection) . ' is not expected to have a union type.');
83+
84+
} else {
85+
throw new Nette\InvalidStateException('Unexpected type of ' . self::toString($reflection));
86+
}
87+
}
88+
89+
90+
/**
91+
* @param \ReflectionFunction|\ReflectionMethod|\ReflectionParameter|\ReflectionProperty $reflection
7892
*/
7993
private static function normalizeType(string $type, $reflection): string
8094
{
8195
$lower = strtolower($type);
82-
if ($lower === 'self' || $lower === 'static') {
96+
if ($reflection instanceof \ReflectionFunction) {
97+
return $type;
98+
} elseif ($lower === 'self' || $lower === 'static') {
8399
return $reflection->getDeclaringClass()->name;
84100
} elseif ($lower === 'parent' && $reflection->getDeclaringClass()->getParentClass()) {
85101
return $reflection->getDeclaringClass()->getParentClass()->name;
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
/**
4+
* Test: Nette\Utils\Reflection::getParameterType
5+
* @phpversion 8.0
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
use Nette\Utils\Reflection;
11+
use Test\B; // for testing purposes
12+
use Tester\Assert;
13+
14+
15+
require __DIR__ . '/../bootstrap.php';
16+
17+
18+
class A
19+
{
20+
public function method(
21+
Undeclared $undeclared,
22+
B $b,
23+
array $array,
24+
callable $callable,
25+
self $self,
26+
$none,
27+
?B $nullable,
28+
mixed $mixed,
29+
array|self $union,
30+
array|self|null $nullableUnion
31+
) {
32+
}
33+
}
34+
35+
class AExt extends A
36+
{
37+
public function methodExt(parent $parent)
38+
{
39+
}
40+
}
41+
42+
$method = new ReflectionMethod('A', 'method');
43+
$params = $method->getParameters();
44+
45+
Assert::same('Undeclared', Reflection::getParameterType($params[0]));
46+
Assert::same('Test\B', Reflection::getParameterType($params[1]));
47+
Assert::same('array', Reflection::getParameterType($params[2]));
48+
Assert::same('callable', Reflection::getParameterType($params[3]));
49+
Assert::same('A', Reflection::getParameterType($params[4]));
50+
Assert::null(Reflection::getParameterType($params[5]));
51+
Assert::same('Test\B', Reflection::getParameterType($params[6]));
52+
Assert::same('mixed', Reflection::getParameterType($params[7]));
53+
54+
Assert::exception(function () use ($params) {
55+
Reflection::getParameterType($params[8]);
56+
}, Nette\InvalidStateException::class, 'The $union in A::method() is not expected to have a union type.');
57+
58+
Assert::exception(function () use ($params) {
59+
Reflection::getParameterType($params[9]);
60+
}, Nette\InvalidStateException::class, 'The $nullableUnion in A::method() is not expected to have a union type.');
61+
62+
63+
$method = new ReflectionMethod('AExt', 'methodExt');
64+
$params = $method->getParameters();
65+
66+
Assert::same('A', Reflection::getParameterType($params[0]));
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
/**
4+
* Test: Nette\Utils\Reflection::getPropertyType
5+
* @phpversion 8.0
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
use Nette\Utils\Reflection;
11+
use Test\B; // for testing purposes
12+
use Tester\Assert;
13+
14+
15+
require __DIR__ . '/../bootstrap.php';
16+
17+
18+
class A
19+
{
20+
public Undeclared $undeclared;
21+
public B $b;
22+
public array $array;
23+
public self $self;
24+
public $none;
25+
public ?B $nullable;
26+
public mixed $mixed;
27+
public array|self $union;
28+
public array|self|null $nullableUnion;
29+
}
30+
31+
class AExt extends A
32+
{
33+
public parent $parent;
34+
}
35+
36+
$class = new ReflectionClass('A');
37+
$props = $class->getProperties();
38+
39+
Assert::same('Undeclared', Reflection::getPropertyType($props[0]));
40+
Assert::same('Test\B', Reflection::getPropertyType($props[1]));
41+
Assert::same('array', Reflection::getPropertyType($props[2]));
42+
Assert::same('A', Reflection::getPropertyType($props[3]));
43+
Assert::null(Reflection::getPropertyType($props[4]));
44+
Assert::same('Test\B', Reflection::getPropertyType($props[5]));
45+
Assert::same('mixed', Reflection::getPropertyType($props[6]));
46+
47+
Assert::exception(function () use ($props) {
48+
Reflection::getPropertyType($props[7]);
49+
}, Nette\InvalidStateException::class, 'The A::$union is not expected to have a union type.');
50+
51+
Assert::exception(function () use ($props) {
52+
Reflection::getPropertyType($props[8]);
53+
}, Nette\InvalidStateException::class, 'The A::$nullableUnion is not expected to have a union type.');
54+
55+
$class = new ReflectionClass('AExt');
56+
$props = $class->getProperties();
57+
58+
Assert::same('A', Reflection::getPropertyType($props[0]));
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<?php
2+
3+
/**
4+
* Test: Nette\Utils\Reflection::getReturnType
5+
* @phpversion 8.0
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace NS
11+
{
12+
use Test\B;
13+
14+
class A
15+
{
16+
public function noType()
17+
{
18+
}
19+
20+
21+
public function classType(): B
22+
{
23+
}
24+
25+
26+
public function nativeType(): String
27+
{
28+
}
29+
30+
31+
public function selfType(): self
32+
{
33+
}
34+
35+
36+
public function staticType(): static
37+
{
38+
}
39+
40+
41+
public function nullableClassType(): ?B
42+
{
43+
}
44+
45+
46+
public function nullableNativeType(): ?string
47+
{
48+
}
49+
50+
51+
public function nullableSelfType(): ?self
52+
{
53+
}
54+
55+
56+
public function unionType(): array|self
57+
{
58+
}
59+
60+
61+
public function nullableUnionType(): array|self|null
62+
{
63+
}
64+
}
65+
66+
class AExt extends A
67+
{
68+
public function parentTypeExt(): parent
69+
{
70+
}
71+
}
72+
73+
74+
function noType()
75+
{
76+
}
77+
78+
79+
function classType(): B
80+
{
81+
}
82+
83+
84+
function nativeType(): String
85+
{
86+
}
87+
88+
89+
function unionType(): array|A
90+
{
91+
}
92+
}
93+
94+
namespace
95+
{
96+
use Nette\Utils\Reflection;
97+
use Tester\Assert;
98+
99+
require __DIR__ . '/../bootstrap.php';
100+
101+
102+
Assert::null(Reflection::getReturnType(new \ReflectionMethod(NS\A::class, 'noType')));
103+
104+
Assert::same('Test\B', Reflection::getReturnType(new \ReflectionMethod(NS\A::class, 'classType')));
105+
106+
Assert::same('string', Reflection::getReturnType(new \ReflectionMethod(NS\A::class, 'nativeType')));
107+
108+
Assert::same('NS\A', Reflection::getReturnType(new \ReflectionMethod(NS\A::class, 'selfType')));
109+
110+
Assert::same('NS\A', Reflection::getReturnType(new \ReflectionMethod(NS\A::class, 'staticType')));
111+
112+
Assert::same('Test\B', Reflection::getReturnType(new \ReflectionMethod(NS\A::class, 'nullableClassType')));
113+
114+
Assert::same('string', Reflection::getReturnType(new \ReflectionMethod(NS\A::class, 'nullableNativeType')));
115+
116+
Assert::same('NS\A', Reflection::getReturnType(new \ReflectionMethod(NS\A::class, 'nullableSelfType')));
117+
118+
Assert::exception(function () {
119+
Reflection::getReturnType(new \ReflectionMethod(NS\A::class, 'unionType'));
120+
}, Nette\InvalidStateException::class, 'The NS\A::unionType is not expected to have a union type.');
121+
122+
Assert::exception(function () {
123+
Reflection::getReturnType(new \ReflectionMethod(NS\A::class, 'nullableUnionType'));
124+
}, Nette\InvalidStateException::class, 'The NS\A::nullableUnionType is not expected to have a union type.');
125+
126+
Assert::same('NS\A', Reflection::getReturnType(new \ReflectionMethod(NS\AExt::class, 'parentTypeExt')));
127+
128+
Assert::null(Reflection::getReturnType(new \ReflectionFunction('NS\noType')));
129+
130+
Assert::same('Test\B', Reflection::getReturnType(new \ReflectionFunction('NS\classType')));
131+
132+
Assert::same('string', Reflection::getReturnType(new \ReflectionFunction('NS\nativeType')));
133+
134+
Assert::exception(function () {
135+
Reflection::getReturnType(new \ReflectionFunction('NS\unionType'));
136+
}, Nette\InvalidStateException::class, 'The NS\unionType is not expected to have a union type.');
137+
}

0 commit comments

Comments
 (0)