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

nette / utils / 15764605036

19 Jun 2025 06:55PM UTC coverage: 92.467% (-0.05%) from 92.516%
15764605036

push

github

dg
Strings::trim() trims Line Separator and Ideographic Space (#326)

These characters occur in our data.

There are more characters listed at https://en.wikipedia.org/wiki/Whitespace_character#Unicode, maybe all of them should be included?

2050 of 2217 relevant lines covered (92.47%)

0.92 hits per line

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

97.7
/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
use function array_map, array_search, array_splice, count, explode, implode, is_a, is_string, strcasecmp, strtolower, substr, trim;
14
use const PHP_VERSION_ID;
15

16

17
/**
18
 * PHP type reflection.
19
 */
20
final class Type
21
{
22
        /** @var array<int, string|self> */
23
        private array $types;
24
        private bool $simple;
25
        private string $kind; // | &
26

27

28
        /**
29
         * Creates a Type object based on reflection. Resolves self, static and parent to the actual class name.
30
         * If the subject has no type, it returns null.
31
         */
32
        public static function fromReflection(
1✔
33
                \ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $reflection,
34
        ): ?self
35
        {
36
                $type = $reflection instanceof \ReflectionFunctionAbstract
1✔
37
                        ? $reflection->getReturnType() ?? (PHP_VERSION_ID >= 80100 && $reflection instanceof \ReflectionMethod ? $reflection->getTentativeReturnType() : null)
1✔
38
                        : $reflection->getType();
1✔
39

40
                return $type ? self::fromReflectionType($type, $reflection, asObject: true) : null;
1✔
41
        }
42

43

44
        private static function fromReflectionType(\ReflectionType $type, $of, bool $asObject): self|string
1✔
45
        {
46
                if ($type instanceof \ReflectionNamedType) {
1✔
47
                        $name = self::resolve($type->getName(), $of);
1✔
48
                        return $asObject
1✔
49
                                ? new self($type->allowsNull() && $name !== 'mixed' ? [$name, 'null'] : [$name])
1✔
50
                                : $name;
1✔
51

52
                } elseif ($type instanceof \ReflectionUnionType || $type instanceof \ReflectionIntersectionType) {
1✔
53
                        return new self(
1✔
54
                                array_map(fn($t) => self::fromReflectionType($t, $of, asObject: false), $type->getTypes()),
1✔
55
                                $type instanceof \ReflectionUnionType ? '|' : '&',
1✔
56
                        );
57

58
                } else {
59
                        throw new Nette\InvalidStateException('Unexpected type of ' . Reflection::toString($of));
×
60
                }
61
        }
62

63

64
        /**
65
         * Creates the Type object according to the text notation.
66
         */
67
        public static function fromString(string $type): self
1✔
68
        {
69
                if (!Validators::isTypeDeclaration($type)) {
1✔
70
                        throw new Nette\InvalidArgumentException("Invalid type '$type'.");
×
71
                }
72

73
                if ($type[0] === '?') {
1✔
74
                        return new self([substr($type, 1), 'null']);
1✔
75
                }
76

77
                $unions = [];
1✔
78
                foreach (explode('|', $type) as $part) {
1✔
79
                        $part = explode('&', trim($part, '()'));
1✔
80
                        $unions[] = count($part) === 1 ? $part[0] : new self($part, '&');
1✔
81
                }
82

83
                return count($unions) === 1 && $unions[0] instanceof self
1✔
84
                        ? $unions[0]
1✔
85
                        : new self($unions);
1✔
86
        }
87

88

89
        /**
90
         * Resolves 'self', 'static' and 'parent' to the actual class name.
91
         */
92
        public static function resolve(
1✔
93
                string $type,
94
                \ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $of,
95
        ): string
96
        {
97
                $lower = strtolower($type);
1✔
98
                if ($of instanceof \ReflectionFunction) {
1✔
99
                        return $type;
1✔
100
                } elseif ($lower === 'self') {
1✔
101
                        return $of->getDeclaringClass()->name;
1✔
102
                } elseif ($lower === 'static') {
1✔
103
                        return ($of instanceof ReflectionMethod ? $of->getOriginalClass() : $of->getDeclaringClass())->name;
1✔
104
                } elseif ($lower === 'parent' && $of->getDeclaringClass()->getParentClass()) {
1✔
105
                        return $of->getDeclaringClass()->getParentClass()->name;
1✔
106
                } else {
107
                        return $type;
1✔
108
                }
109
        }
110

111

112
        private function __construct(array $types, string $kind = '|')
1✔
113
        {
114
                $o = array_search('null', $types, strict: true);
1✔
115
                if ($o !== false) { // null as last
1✔
116
                        array_splice($types, $o, 1);
1✔
117
                        $types[] = 'null';
1✔
118
                }
119

120
                $this->types = $types;
1✔
121
                $this->simple = is_string($types[0]) && ($types[1] ?? 'null') === 'null';
1✔
122
                $this->kind = count($types) > 1 ? $kind : '';
1✔
123
        }
1✔
124

125

126
        public function __toString(): string
127
        {
128
                $multi = count($this->types) > 1;
1✔
129
                if ($this->simple) {
1✔
130
                        return ($multi ? '?' : '') . $this->types[0];
1✔
131
                }
132

133
                $res = [];
1✔
134
                foreach ($this->types as $type) {
1✔
135
                        $res[] = $type instanceof self && $multi ? "($type)" : $type;
1✔
136
                }
137
                return implode($this->kind, $res);
1✔
138
        }
139

140

141
        /**
142
         * Returns the array of subtypes that make up the compound type as strings.
143
         * @return array<int, string|string[]>
144
         */
145
        public function getNames(): array
146
        {
147
                return array_map(fn($t) => $t instanceof self ? $t->getNames() : $t, $this->types);
1✔
148
        }
149

150

151
        /**
152
         * Returns the array of subtypes that make up the compound type as Type objects:
153
         * @return self[]
154
         */
155
        public function getTypes(): array
156
        {
157
                return array_map(fn($t) => $t instanceof self ? $t : new self([$t]), $this->types);
1✔
158
        }
159

160

161
        /**
162
         * Returns the type name for simple types, otherwise null.
163
         */
164
        public function getSingleName(): ?string
165
        {
166
                return $this->simple
1✔
167
                        ? $this->types[0]
1✔
168
                        : null;
1✔
169
        }
170

171

172
        /**
173
         * Returns true whether it is a union type.
174
         */
175
        public function isUnion(): bool
176
        {
177
                return $this->kind === '|';
1✔
178
        }
179

180

181
        /**
182
         * Returns true whether it is an intersection type.
183
         */
184
        public function isIntersection(): bool
185
        {
186
                return $this->kind === '&';
1✔
187
        }
188

189

190
        /**
191
         * Returns true whether it is a simple type. Single nullable types are also considered to be simple types.
192
         */
193
        public function isSimple(): bool
194
        {
195
                return $this->simple;
1✔
196
        }
197

198

199
        /** @deprecated use isSimple() */
200
        public function isSingle(): bool
201
        {
202
                return $this->simple;
1✔
203
        }
204

205

206
        /**
207
         * Returns true whether the type is both a simple and a PHP built-in type.
208
         */
209
        public function isBuiltin(): bool
210
        {
211
                return $this->simple && Validators::isBuiltinType($this->types[0]);
1✔
212
        }
213

214

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

223

224
        /**
225
         * Determines if type is special class name self/parent/static.
226
         */
227
        public function isClassKeyword(): bool
228
        {
229
                return $this->simple && Validators::isClassKeyword($this->types[0]);
1✔
230
        }
231

232

233
        /**
234
         * Verifies type compatibility. For example, it checks if a value of a certain type could be passed as a parameter.
235
         */
236
        public function allows(string $subtype): bool
1✔
237
        {
238
                if ($this->types === ['mixed']) {
1✔
239
                        return true;
1✔
240
                }
241

242
                $subtype = self::fromString($subtype);
1✔
243
                return $subtype->isUnion()
1✔
244
                        ? Arrays::every($subtype->types, fn($t) => $this->allows2($t instanceof self ? $t->types : [$t]))
1✔
245
                        : $this->allows2($subtype->types);
1✔
246
        }
247

248

249
        private function allows2(array $subtypes): bool
1✔
250
        {
251
                return $this->isUnion()
1✔
252
                        ? Arrays::some($this->types, fn($t) => $this->allows3($t instanceof self ? $t->types : [$t], $subtypes))
1✔
253
                        : $this->allows3($this->types, $subtypes);
1✔
254
        }
255

256

257
        private function allows3(array $types, array $subtypes): bool
1✔
258
        {
259
                return Arrays::every(
1✔
260
                        $types,
1✔
261
                        fn($type) => Arrays::some(
1✔
262
                                $subtypes,
1✔
263
                                fn($subtype) => Validators::isBuiltinType($type)
1✔
264
                                        ? strcasecmp($type, $subtype) === 0
1✔
265
                                        : is_a($subtype, $type, allow_string: true),
1✔
266
                        ),
1✔
267
                );
268
        }
269
}
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