Skip to content

Commit 81962ab

Browse files
committed
TypeValidator: refactoring
1 parent 59de7fc commit 81962ab

File tree

8 files changed

+107
-145
lines changed

8 files changed

+107
-145
lines changed

src/TypeValidator.php

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,18 @@
44

55
class TypeValidator
66
{
7-
/** @var array<string, array<string, TypeValidator\Helpers\Runtime>> */
8-
private static array $cache = [];
9-
107

118
public static function isType(mixed $value, string $type): bool
129
{
13-
return self::getRuntime($type)->check($value);
10+
return TypeValidator\Helpers\Runtime::check($type, $value);
1411
}
1512

1613

1714
public static function checkType(mixed $value, string $type): void
1815
{
19-
if (!self::getRuntime($type)->check($value)) {
16+
if (!TypeValidator\Helpers\Runtime::check($type, $value)) {
2017
throw new TypeValidator\Exceptions\CheckException($type, $value);
2118
}
2219
}
2320

24-
25-
private static function getRuntime(string $type): TypeValidator\Helpers\Runtime
26-
{
27-
$filename = '';
28-
foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) as $item) {
29-
if (!str_starts_with($item['file'] ?? '', __DIR__)) {
30-
$filename = $item['file'] ?? '';
31-
break;
32-
}
33-
}
34-
35-
if (!isset(self::$cache[$filename][$type])) {
36-
self::$cache[$filename][$type] = new TypeValidator\Helpers\Runtime($filename, $type);
37-
}
38-
39-
return self::$cache[$filename][$type];
40-
}
41-
4221
}

src/TypeValidator/Helpers/FullyQualifiedClassNameResolver.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
class FullyQualifiedClassNameResolver
88
{
99
/** @var array<string, PhpParser\NameContext|null> */
10-
private static array $nameContextsCache = [];
10+
private static array $cache = [];
1111

1212

1313
public static function resolve(string $filename, string $class): string
@@ -16,11 +16,11 @@ public static function resolve(string $filename, string $class): string
1616
return $class;
1717
}
1818

19-
if (!isset(self::$nameContextsCache[$filename])) {
20-
self::$nameContextsCache[$filename] = self::createNameContext($filename);
19+
if (!isset(self::$cache[$filename])) {
20+
self::$cache[$filename] = self::createNameContext($filename);
2121
}
2222

23-
$nameContext = self::$nameContextsCache[$filename];
23+
$nameContext = self::$cache[$filename];
2424
if ($nameContext === null) {
2525
return $class;
2626
}

src/TypeValidator/Helpers/Runtime.php

Lines changed: 25 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,15 @@
77

88
class Runtime
99
{
10-
private string $filename;
1110

12-
private string $typeDescription;
13-
14-
15-
public function __construct(string $filename, string $typeDescription)
16-
{
17-
SupportedTypes::check($filename, $typeDescription);
18-
19-
$this->filename = $filename;
20-
$this->typeDescription = $typeDescription;
21-
}
22-
23-
24-
public function check(mixed $value): bool
11+
public static function check(string $typeDescription, mixed $value): bool
2512
{
26-
return $this->checkTypeNode(PhpDocParser::parseType($this->typeDescription), $value);
13+
SupportedTypes::check($typeDescription, static fn (): string => RuntimeSourceFilename::detect());
14+
return self::checkTypeNode(PhpDocParser::parseType($typeDescription), $value);
2715
}
2816

2917

30-
private function checkTypeNode(Ast\Type\TypeNode $typeNode, mixed $value): bool
18+
private static function checkTypeNode(Ast\Type\TypeNode $typeNode, mixed $value): bool
3119
{
3220
// PHPStan source - src/PhpDoc/TypeNodeResolver.php + https://phpstan.org/writing-php-code/phpdoc-types
3321
if ($typeNode instanceof Ast\Type\IdentifierTypeNode) {
@@ -83,7 +71,7 @@ private function checkTypeNode(Ast\Type\TypeNode $typeNode, mixed $value): bool
8371
'interface-string' => is_string($value) && interface_exists($value),
8472
'trait-string' => is_string($value) && trait_exists($value),
8573
'enum-string' => is_string($value) && enum_exists($value),
86-
default => $this->instanceOf($typeNode->name, $value),
74+
default => self::instanceOf($typeNode->name, $value),
8775
};
8876

8977
if (!$result) {
@@ -116,7 +104,7 @@ private function checkTypeNode(Ast\Type\TypeNode $typeNode, mixed $value): bool
116104
return true;
117105
}
118106

119-
return $this->checkTypeNode($typeNode->type, $value);
107+
return self::checkTypeNode($typeNode->type, $value);
120108
} else if ($typeNode instanceof Ast\Type\ConstTypeNode) {
121109
$constExpr = $typeNode->constExpr;
122110
if ($constExpr instanceof Ast\ConstExpr\ConstExprIntegerNode) {
@@ -132,7 +120,7 @@ private function checkTypeNode(Ast\Type\TypeNode $typeNode, mixed $value): bool
132120
}
133121

134122
foreach ($value as $item) {
135-
if (!$this->checkTypeNode($typeNode->type, $item)) {
123+
if (!self::checkTypeNode($typeNode->type, $item)) {
136124
return false;
137125
}
138126
}
@@ -158,7 +146,7 @@ private function checkTypeNode(Ast\Type\TypeNode $typeNode, mixed $value): bool
158146

159147
if (!$arrayShapeItem->optional && !array_key_exists($key, $value)) {
160148
return false;
161-
} else if (array_key_exists($key, $value) && !$this->checkTypeNode($arrayShapeItem->valueType, $value[$key])) {
149+
} else if (array_key_exists($key, $value) && !self::checkTypeNode($arrayShapeItem->valueType, $value[$key])) {
162150
return false;
163151
}
164152
}
@@ -179,7 +167,7 @@ private function checkTypeNode(Ast\Type\TypeNode $typeNode, mixed $value): bool
179167

180168
if (!$objectShapeItem->optional && !property_exists($value, $key)) {
181169
return false;
182-
} else if (property_exists($value, $key) && !$this->checkTypeNode($objectShapeItem->valueType, $value->{$key})) {
170+
} else if (property_exists($value, $key) && !self::checkTypeNode($objectShapeItem->valueType, $value->{$key})) {
183171
return false;
184172
}
185173
}
@@ -200,7 +188,7 @@ private function checkTypeNode(Ast\Type\TypeNode $typeNode, mixed $value): bool
200188
foreach ($value as $key => $item) {
201189
$checkItems = count($typeNode->genericTypes) === 1 ? [$item] : [$key, $item];
202190
foreach ($typeNode->genericTypes as $i => $genericType) {
203-
if (!$this->checkTypeNode($genericType, $checkItems[$i])) {
191+
if (!self::checkTypeNode($genericType, $checkItems[$i])) {
204192
return false;
205193
}
206194
}
@@ -215,7 +203,7 @@ private function checkTypeNode(Ast\Type\TypeNode $typeNode, mixed $value): bool
215203
}
216204

217205
foreach ($value as $item) {
218-
if (!$this->checkTypeNode($typeNode->genericTypes[0], $item)) {
206+
if (!self::checkTypeNode($typeNode->genericTypes[0], $item)) {
219207
return false;
220208
}
221209
}
@@ -270,21 +258,21 @@ private function checkTypeNode(Ast\Type\TypeNode $typeNode, mixed $value): bool
270258
return false;
271259
}
272260

273-
return $this->isClassStringOrInterfaceStringOf($typeNode->genericTypes[0], $value, $name === 'interface-string');
261+
return self::isClassStringOrInterfaceStringOf($typeNode->genericTypes[0], $value, $name === 'interface-string');
274262
}
275263

276264
return true;
277265
} else if ($typeNode instanceof Ast\Type\UnionTypeNode) {
278266
foreach ($typeNode->types as $typeNodeItem) {
279-
if ($this->checkTypeNode($typeNodeItem, $value)) {
267+
if (self::checkTypeNode($typeNodeItem, $value)) {
280268
return true;
281269
}
282270
}
283271

284272
return false;
285273
} else if ($typeNode instanceof Ast\Type\IntersectionTypeNode) {
286274
foreach ($typeNode->types as $typeNodeItem) {
287-
if (!$this->checkTypeNode($typeNodeItem, $value)) {
275+
if (!self::checkTypeNode($typeNodeItem, $value)) {
288276
return false;
289277
}
290278
}
@@ -296,30 +284,34 @@ private function checkTypeNode(Ast\Type\TypeNode $typeNode, mixed $value): bool
296284
}
297285

298286

299-
private function instanceOf(string $class, mixed $value): bool
287+
private static function instanceOf(string $class, mixed $value): bool
300288
{
301-
$fullyQualifiedClassName = FullyQualifiedClassNameResolver::resolve($this->filename, $class);
289+
$fullyQualifiedClassName = FullyQualifiedClassNameResolver::resolve(RuntimeSourceFilename::detect(), $class);
302290
return $value instanceof $fullyQualifiedClassName;
303291
}
304292

305293

306-
private function isClassStringOrInterfaceStringOf(Ast\Type\TypeNode $typeNode, string $value, bool $interface): bool
294+
private static function isClassStringOrInterfaceStringOf(
295+
Ast\Type\TypeNode $typeNode,
296+
string $value,
297+
bool $interface,
298+
): bool
307299
{
308300
if ($typeNode instanceof Ast\Type\IdentifierTypeNode) {
309301
return $interface
310-
? is_subclass_of($value, FullyQualifiedClassNameResolver::resolve($this->filename, $typeNode->name))
311-
: is_a($value, FullyQualifiedClassNameResolver::resolve($this->filename, $typeNode->name), true);
302+
? is_subclass_of($value, FullyQualifiedClassNameResolver::resolve(RuntimeSourceFilename::detect(), $typeNode->name))
303+
: is_a($value, FullyQualifiedClassNameResolver::resolve(RuntimeSourceFilename::detect(), $typeNode->name), true);
312304
} else if ($typeNode instanceof Ast\Type\UnionTypeNode) {
313305
foreach ($typeNode->types as $type) {
314-
if ($this->isClassStringOrInterfaceStringOf($type, $value, $interface)) {
306+
if (self::isClassStringOrInterfaceStringOf($type, $value, $interface)) {
315307
return true;
316308
}
317309
}
318310

319311
return false;
320312
} else if ($typeNode instanceof Ast\Type\IntersectionTypeNode) {
321313
foreach ($typeNode->types as $type) {
322-
if (!$this->isClassStringOrInterfaceStringOf($type, $value, $interface)) {
314+
if (!self::isClassStringOrInterfaceStringOf($type, $value, $interface)) {
323315
return false;
324316
}
325317
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Forrest79\TypeValidator\Helpers;
4+
5+
use Forrest79\TypeValidator\Exceptions;
6+
7+
class RuntimeSourceFilename
8+
{
9+
private static string|null $rootDir = null;
10+
11+
12+
public static function detect(): string
13+
{
14+
$filename = '';
15+
foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) as $item) {
16+
if (!str_starts_with($item['file'] ?? '', self::getRootDir())) {
17+
$filename = $item['file'] ?? '';
18+
break;
19+
}
20+
}
21+
22+
return $filename;
23+
}
24+
25+
26+
private static function getRootDir(): string
27+
{
28+
if (self::$rootDir === null) {
29+
$dir = realpath(__DIR__ . '/..');
30+
if ($dir === false) {
31+
throw new Exceptions\ShouldNotHappenException();
32+
}
33+
34+
self::$rootDir = $dir;
35+
}
36+
37+
return self::$rootDir;
38+
}
39+
40+
}

0 commit comments

Comments
 (0)