Skip to content

Commit c00dc21

Browse files
committed
TypeValidator: refactoring
1 parent 59de7fc commit c00dc21

File tree

8 files changed

+136
-138
lines changed

8 files changed

+136
-138
lines changed

src/TypeValidator.php

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,33 @@
44

55
class TypeValidator
66
{
7-
/** @var array<string, array<string, TypeValidator\Helpers\Runtime>> */
8-
private static array $cache = [];
7+
private static \Closure|null $filenameCallback = null;
98

109

1110
public static function isType(mixed $value, string $type): bool
1211
{
13-
return self::getRuntime($type)->check($value);
12+
return TypeValidator\Helpers\Runtime::check($type, self::getFilenameCallback(), $value);
1413
}
1514

1615

1716
public static function checkType(mixed $value, string $type): void
1817
{
19-
if (!self::getRuntime($type)->check($value)) {
18+
if (!TypeValidator\Helpers\Runtime::check($type, self::getFilenameCallback(), $value)) {
2019
throw new TypeValidator\Exceptions\CheckException($type, $value);
2120
}
2221
}
2322

2423

25-
private static function getRuntime(string $type): TypeValidator\Helpers\Runtime
24+
/**
25+
* @return callable(): string
26+
*/
27+
private static function getFilenameCallback(): callable
2628
{
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-
}
29+
if (self::$filenameCallback === null) {
30+
self::$filenameCallback = static fn (): string => TypeValidator\Helpers\RuntimeSourceFilename::detect();
3331
}
3432

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];
33+
return self::$filenameCallback;
4034
}
4135

4236
}

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: 38 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,21 @@
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+
/**
12+
* @param callable(): string $filenameCallback
13+
*/
14+
public static function check(string $typeDescription, callable $filenameCallback, mixed $value): bool
2515
{
26-
return $this->checkTypeNode(PhpDocParser::parseType($this->typeDescription), $value);
16+
SupportedTypes::check($typeDescription, $filenameCallback);
17+
return self::checkTypeNode(PhpDocParser::parseType($typeDescription), $filenameCallback, $value);
2718
}
2819

2920

30-
private function checkTypeNode(Ast\Type\TypeNode $typeNode, mixed $value): bool
21+
/**
22+
* @param callable(): string $filenameCallback
23+
*/
24+
private static function checkTypeNode(Ast\Type\TypeNode $typeNode, callable $filenameCallback, mixed $value): bool
3125
{
3226
// PHPStan source - src/PhpDoc/TypeNodeResolver.php + https://phpstan.org/writing-php-code/phpdoc-types
3327
if ($typeNode instanceof Ast\Type\IdentifierTypeNode) {
@@ -83,7 +77,7 @@ private function checkTypeNode(Ast\Type\TypeNode $typeNode, mixed $value): bool
8377
'interface-string' => is_string($value) && interface_exists($value),
8478
'trait-string' => is_string($value) && trait_exists($value),
8579
'enum-string' => is_string($value) && enum_exists($value),
86-
default => $this->instanceOf($typeNode->name, $value),
80+
default => self::instanceOf($typeNode->name, $filenameCallback, $value),
8781
};
8882

8983
if (!$result) {
@@ -116,7 +110,7 @@ private function checkTypeNode(Ast\Type\TypeNode $typeNode, mixed $value): bool
116110
return true;
117111
}
118112

119-
return $this->checkTypeNode($typeNode->type, $value);
113+
return self::checkTypeNode($typeNode->type, $filenameCallback, $value);
120114
} else if ($typeNode instanceof Ast\Type\ConstTypeNode) {
121115
$constExpr = $typeNode->constExpr;
122116
if ($constExpr instanceof Ast\ConstExpr\ConstExprIntegerNode) {
@@ -132,7 +126,7 @@ private function checkTypeNode(Ast\Type\TypeNode $typeNode, mixed $value): bool
132126
}
133127

134128
foreach ($value as $item) {
135-
if (!$this->checkTypeNode($typeNode->type, $item)) {
129+
if (!self::checkTypeNode($typeNode->type, $filenameCallback, $item)) {
136130
return false;
137131
}
138132
}
@@ -158,7 +152,7 @@ private function checkTypeNode(Ast\Type\TypeNode $typeNode, mixed $value): bool
158152

159153
if (!$arrayShapeItem->optional && !array_key_exists($key, $value)) {
160154
return false;
161-
} else if (array_key_exists($key, $value) && !$this->checkTypeNode($arrayShapeItem->valueType, $value[$key])) {
155+
} else if (array_key_exists($key, $value) && !self::checkTypeNode($arrayShapeItem->valueType, $filenameCallback, $value[$key])) {
162156
return false;
163157
}
164158
}
@@ -179,7 +173,7 @@ private function checkTypeNode(Ast\Type\TypeNode $typeNode, mixed $value): bool
179173

180174
if (!$objectShapeItem->optional && !property_exists($value, $key)) {
181175
return false;
182-
} else if (property_exists($value, $key) && !$this->checkTypeNode($objectShapeItem->valueType, $value->{$key})) {
176+
} else if (property_exists($value, $key) && !self::checkTypeNode($objectShapeItem->valueType, $filenameCallback, $value->{$key})) {
183177
return false;
184178
}
185179
}
@@ -200,7 +194,7 @@ private function checkTypeNode(Ast\Type\TypeNode $typeNode, mixed $value): bool
200194
foreach ($value as $key => $item) {
201195
$checkItems = count($typeNode->genericTypes) === 1 ? [$item] : [$key, $item];
202196
foreach ($typeNode->genericTypes as $i => $genericType) {
203-
if (!$this->checkTypeNode($genericType, $checkItems[$i])) {
197+
if (!self::checkTypeNode($genericType, $filenameCallback, $checkItems[$i])) {
204198
return false;
205199
}
206200
}
@@ -215,7 +209,7 @@ private function checkTypeNode(Ast\Type\TypeNode $typeNode, mixed $value): bool
215209
}
216210

217211
foreach ($value as $item) {
218-
if (!$this->checkTypeNode($typeNode->genericTypes[0], $item)) {
212+
if (!self::checkTypeNode($typeNode->genericTypes[0], $filenameCallback, $item)) {
219213
return false;
220214
}
221215
}
@@ -270,21 +264,21 @@ private function checkTypeNode(Ast\Type\TypeNode $typeNode, mixed $value): bool
270264
return false;
271265
}
272266

273-
return $this->isClassStringOrInterfaceStringOf($typeNode->genericTypes[0], $value, $name === 'interface-string');
267+
return self::isClassStringOrInterfaceStringOf($typeNode->genericTypes[0], $filenameCallback, $value, $name === 'interface-string');
274268
}
275269

276270
return true;
277271
} else if ($typeNode instanceof Ast\Type\UnionTypeNode) {
278272
foreach ($typeNode->types as $typeNodeItem) {
279-
if ($this->checkTypeNode($typeNodeItem, $value)) {
273+
if (self::checkTypeNode($typeNodeItem, $filenameCallback, $value)) {
280274
return true;
281275
}
282276
}
283277

284278
return false;
285279
} else if ($typeNode instanceof Ast\Type\IntersectionTypeNode) {
286280
foreach ($typeNode->types as $typeNodeItem) {
287-
if (!$this->checkTypeNode($typeNodeItem, $value)) {
281+
if (!self::checkTypeNode($typeNodeItem, $filenameCallback, $value)) {
288282
return false;
289283
}
290284
}
@@ -296,30 +290,41 @@ private function checkTypeNode(Ast\Type\TypeNode $typeNode, mixed $value): bool
296290
}
297291

298292

299-
private function instanceOf(string $class, mixed $value): bool
293+
/**
294+
* @param callable(): string $filenameCallback
295+
*/
296+
private static function instanceOf(string $class, callable $filenameCallback, mixed $value): bool
300297
{
301-
$fullyQualifiedClassName = FullyQualifiedClassNameResolver::resolve($this->filename, $class);
298+
$fullyQualifiedClassName = FullyQualifiedClassNameResolver::resolve($filenameCallback(), $class);
302299
return $value instanceof $fullyQualifiedClassName;
303300
}
304301

305302

306-
private function isClassStringOrInterfaceStringOf(Ast\Type\TypeNode $typeNode, string $value, bool $interface): bool
303+
/**
304+
* @param callable(): string $filenameCallback
305+
*/
306+
private static function isClassStringOrInterfaceStringOf(
307+
Ast\Type\TypeNode $typeNode,
308+
callable $filenameCallback,
309+
string $value,
310+
bool $interface,
311+
): bool
307312
{
308313
if ($typeNode instanceof Ast\Type\IdentifierTypeNode) {
309314
return $interface
310-
? is_subclass_of($value, FullyQualifiedClassNameResolver::resolve($this->filename, $typeNode->name))
311-
: is_a($value, FullyQualifiedClassNameResolver::resolve($this->filename, $typeNode->name), true);
315+
? is_subclass_of($value, FullyQualifiedClassNameResolver::resolve($filenameCallback(), $typeNode->name))
316+
: is_a($value, FullyQualifiedClassNameResolver::resolve($filenameCallback(), $typeNode->name), true);
312317
} else if ($typeNode instanceof Ast\Type\UnionTypeNode) {
313318
foreach ($typeNode->types as $type) {
314-
if ($this->isClassStringOrInterfaceStringOf($type, $value, $interface)) {
319+
if (self::isClassStringOrInterfaceStringOf($type, $filenameCallback, $value, $interface)) {
315320
return true;
316321
}
317322
}
318323

319324
return false;
320325
} else if ($typeNode instanceof Ast\Type\IntersectionTypeNode) {
321326
foreach ($typeNode->types as $type) {
322-
if (!$this->isClassStringOrInterfaceStringOf($type, $value, $interface)) {
327+
if (!self::isClassStringOrInterfaceStringOf($type, $filenameCallback, $value, $interface)) {
323328
return false;
324329
}
325330
}
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)