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

nette / utils / 3976814683

pending completion
3976814683

push

github

David Grudl
Type: supports Disjunctive Normal Form Types

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

1578 of 1751 relevant lines covered (90.12%)

0.9 hits per line

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

90.82
/src/Utils/Type.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\Utils;
11

12
use Nette;
13

14

15
/**
16
 * PHP type reflection.
17
 */
18
final class Type
19
{
20
        /** @var array<int, string|self> */
21
        private $types;
22

23
        /** @var bool */
24
        private $simple;
25

26
        /** @var string  |, & */
27
        private $kind;
28

29

30
        /**
31
         * Creates a Type object based on reflection. Resolves self, static and parent to the actual class name.
32
         * If the subject has no type, it returns null.
33
         * @param  \ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty  $reflection
34
         */
35
        public static function fromReflection($reflection): ?self
36
        {
37
                if ($reflection instanceof \ReflectionProperty && PHP_VERSION_ID < 70400) {
1✔
38
                        return null;
×
39
                } elseif ($reflection instanceof \ReflectionMethod) {
1✔
40
                        $type = $reflection->getReturnType() ?? (PHP_VERSION_ID >= 80100 ? $reflection->getTentativeReturnType() : null);
1✔
41
                } else {
42
                        $type = $reflection instanceof \ReflectionFunctionAbstract
1✔
43
                                ? $reflection->getReturnType()
1✔
44
                                : $reflection->getType();
1✔
45
                }
46

47
                return $type ? self::fromReflectionType($type, $reflection, true) : null;
1✔
48
        }
49

50

51
        private static function fromReflectionType(\ReflectionType $type, $of, bool $asObject)
1✔
52
        {
53
                if ($type instanceof \ReflectionNamedType) {
1✔
54
                        $name = self::resolve($type->getName(), $of);
1✔
55
                        return $asObject
1✔
56
                                ? new self($type->allowsNull() && $name !== 'mixed' ? [$name, 'null'] : [$name])
1✔
57
                                : $name;
1✔
58

59
                } elseif ($type instanceof \ReflectionUnionType || $type instanceof \ReflectionIntersectionType) {
×
60
                        return new self(
×
61
                                array_map(
×
62
                                        function ($t) use ($of) { return self::fromReflectionType($t, $of, false); },
×
63
                                        $type->getTypes()
×
64
                                ),
65
                                $type instanceof \ReflectionUnionType ? '|' : '&'
×
66
                        );
67

68
                } else {
69
                        throw new Nette\InvalidStateException('Unexpected type of ' . Reflection::toString($of));
×
70
                }
71
        }
72

73

74
        /**
75
         * Creates the Type object according to the text notation.
76
         */
77
        public static function fromString(string $type): self
1✔
78
        {
79
                if (!Validators::isTypeDeclaration($type)) {
1✔
80
                        throw new Nette\InvalidArgumentException("Invalid type '$type'.");
×
81
                }
82

83
                if ($type[0] === '?') {
1✔
84
                        return new self([substr($type, 1), 'null']);
1✔
85
                }
86

87
                $unions = [];
1✔
88
                foreach (explode('|', $type) as $part) {
1✔
89
                        $part = explode('&', trim($part, '()'));
1✔
90
                        $unions[] = count($part) === 1 ? $part[0] : new self($part, '&');
1✔
91
                }
92

93
                return count($unions) === 1 && $unions[0] instanceof self
1✔
94
                        ? $unions[0]
1✔
95
                        : new self($unions);
1✔
96
        }
97

98

99
        /**
100
         * Resolves 'self', 'static' and 'parent' to the actual class name.
101
         * @param  \ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty  $of
102
         */
103
        public static function resolve(string $type, $of): string
1✔
104
        {
105
                $lower = strtolower($type);
1✔
106
                if ($of instanceof \ReflectionFunction) {
1✔
107
                        return $type;
1✔
108
                } elseif ($lower === 'self' || $lower === 'static') {
1✔
109
                        return $of->getDeclaringClass()->name;
1✔
110
                } elseif ($lower === 'parent' && $of->getDeclaringClass()->getParentClass()) {
1✔
111
                        return $of->getDeclaringClass()->getParentClass()->name;
1✔
112
                } else {
113
                        return $type;
1✔
114
                }
115
        }
116

117

118
        private function __construct(array $types, string $kind = '|')
1✔
119
        {
120
                $o = array_search('null', $types, true);
1✔
121
                if ($o !== false) { // null as last
1✔
122
                        array_splice($types, $o, 1);
1✔
123
                        $types[] = 'null';
1✔
124
                }
125

126
                $this->types = $types;
1✔
127
                $this->simple = is_string($types[0]) && ($types[1] ?? 'null') === 'null';
1✔
128
                $this->kind = count($types) > 1 ? $kind : '';
1✔
129
        }
1✔
130

131

132
        public function __toString(): string
133
        {
134
                $multi = count($this->types) > 1;
1✔
135
                if ($this->simple) {
1✔
136
                        return ($multi ? '?' : '') . $this->types[0];
1✔
137
                }
138

139
                $res = [];
1✔
140
                foreach ($this->types as $type) {
1✔
141
                        $res[] = $type instanceof self && $multi ? "($type)" : $type;
1✔
142
                }
143
                return implode($this->kind, $res);
1✔
144
        }
145

146

147
        /**
148
         * Returns the array of subtypes that make up the compound type as strings.
149
         * @return array<int, string|string[]>
150
         */
151
        public function getNames(): array
152
        {
153
                return array_map(function ($t) {
1✔
154
                        return $t instanceof self ? $t->getNames() : $t;
1✔
155
                }, $this->types);
1✔
156
        }
157

158

159
        /**
160
         * Returns the array of subtypes that make up the compound type as Type objects:
161
         * @return self[]
162
         */
163
        public function getTypes(): array
164
        {
165
                return array_map(function ($t) {
1✔
166
                        return $t instanceof self ? $t : new self([$t]);
1✔
167
                }, $this->types);
1✔
168
        }
169

170

171
        /**
172
         * Returns the type name for simple types, otherwise null.
173
         */
174
        public function getSingleName(): ?string
175
        {
176
                return $this->simple
1✔
177
                        ? $this->types[0]
1✔
178
                        : null;
1✔
179
        }
180

181

182
        /**
183
         * Returns true whether it is a union type.
184
         */
185
        public function isUnion(): bool
186
        {
187
                return $this->kind === '|';
1✔
188
        }
189

190

191
        /**
192
         * Returns true whether it is an intersection type.
193
         */
194
        public function isIntersection(): bool
195
        {
196
                return $this->kind === '&';
1✔
197
        }
198

199

200
        /**
201
         * Returns true whether it is a simple type. Single nullable types are also considered to be simple types.
202
         */
203
        public function isSimple(): bool
204
        {
205
                return $this->simple;
1✔
206
        }
207

208

209
        /** @deprecated use isSimple() */
210
        public function isSingle(): bool
211
        {
212
                return $this->simple;
1✔
213
        }
214

215

216
        /**
217
         * Returns true whether the type is both a simple and a PHP built-in type.
218
         */
219
        public function isBuiltin(): bool
220
        {
221
                return $this->simple && Validators::isBuiltinType($this->types[0]);
1✔
222
        }
223

224

225
        /**
226
         * Returns true whether the type is both a simple and a class name.
227
         */
228
        public function isClass(): bool
229
        {
230
                return $this->simple && !Validators::isBuiltinType($this->types[0]);
1✔
231
        }
232

233

234
        /**
235
         * Determines if type is special class name self/parent/static.
236
         */
237
        public function isClassKeyword(): bool
238
        {
239
                return $this->simple && Validators::isClassKeyword($this->types[0]);
1✔
240
        }
241

242

243
        /**
244
         * Verifies type compatibility. For example, it checks if a value of a certain type could be passed as a parameter.
245
         */
246
        public function allows(string $subtype): bool
1✔
247
        {
248
                if ($this->types === ['mixed']) {
1✔
249
                        return true;
1✔
250
                }
251

252
                $subtype = self::fromString($subtype);
1✔
253
                return $subtype->isUnion()
1✔
254
                        ? Arrays::every($subtype->types, function ($t) {
1✔
255
                                return $this->allows2($t instanceof self ? $t->types : [$t]);
1✔
256
                        })
1✔
257
                        : $this->allows2($subtype->types);
1✔
258
        }
259

260

261
        private function allows2(array $subtypes): bool
1✔
262
        {
263
                return $this->isUnion()
1✔
264
                        ? Arrays::some($this->types, function ($t) use ($subtypes) {
1✔
265
                                return $this->allows3($t instanceof self ? $t->types : [$t], $subtypes);
1✔
266
                        })
1✔
267
                        : $this->allows3($this->types, $subtypes);
1✔
268
        }
269

270

271
        private function allows3(array $types, array $subtypes): bool
1✔
272
        {
273
                return Arrays::every($types, function ($type) use ($subtypes) {
1✔
274
                        $builtin = Validators::isBuiltinType($type);
1✔
275
                        return Arrays::some($subtypes, function ($subtype) use ($type, $builtin) {
1✔
276
                                return $builtin
1✔
277
                                        ? strcasecmp($type, $subtype) === 0
1✔
278
                                        : is_a($subtype, $type, true);
1✔
279
                        });
1✔
280
                });
1✔
281
        }
282
}
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