Skip to content

Commit 2e5b601

Browse files
committed
make array optmization configurable
1 parent 8f18374 commit 2e5b601

5 files changed

Lines changed: 63 additions & 13 deletions

File tree

src/Command/ConfigInitCommand.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,21 @@ private static function getTemplate(): string
110110
// \Acme\Domain\TypePHPExtension::class,
111111
],
112112
113+
/*
114+
|--------------------------------------------------------------------------
115+
| Array Validation Strategy
116+
|--------------------------------------------------------------------------
117+
| Controls how collections (list<T>, array<K, V>, Type[]) are verified:
118+
|
119+
| - 'full' : (Default / Strict) 100% exhaustive scan. Checks every single
120+
| item in every array, guaranteeing every single offending item
121+
| is caught without exception.
122+
|
123+
| - 'hybrid' : (Beartype O(1) Mode) Fast boundary + random sampling on
124+
| arrays > 64 items. Ideal for massive production datasets.
125+
*/
126+
'array_validation' => 'full',
127+
113128
/*
114129
|--------------------------------------------------------------------------
115130
| Inline Variable Validation (@var $x = ...)

src/Internal/Config.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ final class Config
4242

4343
private static bool $respectIgnoreTags = true;
4444

45+
private static string $arrayValidation = 'full';
46+
4547
public static function isEnabled(): bool
4648
{
4749
if (self::$cachedConfig === null) {
@@ -96,6 +98,24 @@ public static function isRespectIgnoreTagsEnabled(): bool
9698
return self::$respectIgnoreTags;
9799
}
98100

101+
public static function isArrayValidationHybrid(): bool
102+
{
103+
if (self::$cachedConfig === null) {
104+
self::get();
105+
}
106+
107+
return self::$arrayValidation === 'hybrid';
108+
}
109+
110+
public static function getArrayValidationStrategy(): string
111+
{
112+
if (self::$cachedConfig === null) {
113+
self::get();
114+
}
115+
116+
return self::$arrayValidation;
117+
}
118+
99119
/**
100120
* Locates the project root directory by searching upwards for vendor/autoload.php or composer.json.
101121
* Caches the result in memory so the search happens exactly once.
@@ -158,6 +178,7 @@ public static function get(): array
158178
'magic_properties' => true,
159179
'magic_methods' => true,
160180
'respect_ignore_tags' => true,
181+
'array_validation' => 'full',
161182
'cache' => true,
162183
'cache_dir' => null,
163184
'inline_vars' => [
@@ -232,6 +253,7 @@ public static function reset(): void
232253
self::$magicProperties = true;
233254
self::$magicMethods = true;
234255
self::$respectIgnoreTags = true;
256+
self::$arrayValidation = 'full';
235257

236258
ContractParser::reset();
237259
ParamChecker::reset();
@@ -256,5 +278,6 @@ private static function syncFlags(array $config): void
256278
self::$magicProperties = (bool) ($config['magic_properties'] ?? true);
257279
self::$magicMethods = (bool) ($config['magic_methods'] ?? true);
258280
self::$respectIgnoreTags = (bool) ($config['respect_ignore_tags'] ?? true);
281+
self::$arrayValidation = (string) ($config['array_validation'] ?? 'full');
259282
}
260283
}

src/Validator/ArrayValidator.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,20 @@
88
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
99
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
1010
use Traversable;
11+
use TypePHP\Internal\Config;
1112
use TypePHP\Internal\ErrorFactory;
1213
use TypePHP\Internal\ErrorMessage;
1314
use TypePHP\Internal\TypeFormatter;
1415

1516
/**
1617
* Validates array and Traversable collection instances against ArrayTypeNode ASTs (Type[]).
17-
* Implements a Beartype-inspired hybrid O(1) sampling algorithm for large collections.
18+
* Supports both exhaustive O(n) verification and Beartype-style hybrid O(1) sampling.
1819
*
1920
* @internal
2021
*/
2122
final class ArrayValidator implements TypeValidatorInterface
2223
{
23-
private const HYBRID_SAMPLE_THRESHOLD = 64;
24+
private const HYBRID_SAMPLE_THRESHOLD = 128;
2425

2526
/**
2627
* Validates an array or Traversable collection against an ArrayTypeNode (Type[]).
@@ -46,7 +47,7 @@ public function validate(mixed $value, TypeNode $node, string $context, TypeVali
4647
return null;
4748
}
4849

49-
if ($count > self::HYBRID_SAMPLE_THRESHOLD) {
50+
if ($count > self::HYBRID_SAMPLE_THRESHOLD && Config::isArrayValidationHybrid()) {
5051
return $this->validateArrayHybrid($value, $arrayNode, $context, $registry, $count);
5152
}
5253

@@ -119,4 +120,4 @@ private function validateArrayHybrid(
119120

120121
return null;
121122
}
122-
}
123+
}

src/Validator/GenericValidator.php

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
1414
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
1515
use TypePHP\Internal\ClassNameValidator;
16+
use TypePHP\Internal\Config;
1617
use TypePHP\Internal\ErrorFactory;
1718
use TypePHP\Internal\ErrorMessage;
1819
use TypePHP\Internal\RuntimeTypeChecker;
@@ -361,7 +362,6 @@ private function validateClassString(mixed $value, GenericTypeNode $node, string
361362

362363
/**
363364
* Validates sequential list structures (e.g. list<string> or non-empty-list<int>).
364-
* Uses Beartype-style O(1) hybrid sampling for large lists (> 64 items).
365365
*/
366366
private function validateList(mixed $value, GenericTypeNode $node, string $context, TypeValidatorRegistry $registry): ?ErrorMessage
367367
{
@@ -381,8 +381,7 @@ private function validateList(mixed $value, GenericTypeNode $node, string $conte
381381
if ($valueTypeNode !== null && $count > 0) {
382382
$isComplexObjectGeneric = ($valueTypeNode instanceof GenericTypeNode && ! \in_array(strtolower($valueTypeNode->type->name), ['class-string', 'list', 'array', 'iterable'], strict: true));
383383

384-
// O(1) Beartype Hybrid Sampling for large lists (> 64 items)
385-
if ($count > self::HYBRID_SAMPLE_THRESHOLD) {
384+
if ($count > self::HYBRID_SAMPLE_THRESHOLD && Config::isArrayValidationHybrid()) {
386385
$sampleIndices = [0, $count - 1];
387386
$samplesToTake = min(3, $count - 2);
388387
for ($i = 0; $i < $samplesToTake; $i++) {
@@ -403,7 +402,6 @@ private function validateList(mixed $value, GenericTypeNode $node, string $conte
403402
return null;
404403
}
405404

406-
// Full O(n) scan for small/medium lists (<= 64 items)
407405
foreach ($value as $k => $v) {
408406
$err = $isComplexObjectGeneric
409407
? $this->validateObjectGeneric($v, $valueTypeNode, '')
@@ -420,7 +418,6 @@ private function validateList(mixed $value, GenericTypeNode $node, string $conte
420418

421419
/**
422420
* Validates key-value array structures (e.g. array<string, int>).
423-
* Uses Beartype-style O(1) hybrid sampling for large maps (> 64 items).
424421
*/
425422
private function validateArray(mixed $value, GenericTypeNode $node, string $context, TypeValidatorRegistry $registry): ?ErrorMessage
426423
{
@@ -448,8 +445,7 @@ private function validateArray(mixed $value, GenericTypeNode $node, string $cont
448445
if ($typesCount === 1) {
449446
$valTypeNode = $node->genericTypes[0];
450447
$isComplexObjectGeneric = ($valTypeNode instanceof GenericTypeNode && ! \in_array(strtolower($valTypeNode->type->name), ['class-string', 'list', 'array', 'iterable'], strict: true));
451-
452-
if ($count > self::HYBRID_SAMPLE_THRESHOLD) {
448+
if ($count > self::HYBRID_SAMPLE_THRESHOLD && Config::isArrayValidationHybrid()) {
453449
$keys = array_keys($value);
454450
$sampleKeys = [$keys[0], $keys[$count - 1]];
455451
$samplesToTake = min(3, $count - 2);
@@ -485,7 +481,7 @@ private function validateArray(mixed $value, GenericTypeNode $node, string $cont
485481
$valTypeNode = $node->genericTypes[1];
486482
$isComplexObjectGeneric = ($valTypeNode instanceof GenericTypeNode && ! \in_array(strtolower($valTypeNode->type->name), ['class-string', 'list', 'array', 'iterable'], strict: true));
487483

488-
if ($count > self::HYBRID_SAMPLE_THRESHOLD) {
484+
if ($count > self::HYBRID_SAMPLE_THRESHOLD && Config::isArrayValidationHybrid()) {
489485
$keys = array_keys($value);
490486
$sampleKeys = [$keys[0], $keys[$count - 1]];
491487
$samplesToTake = min(3, $count - 2);
@@ -547,4 +543,4 @@ private function validateObjectGeneric(mixed $value, GenericTypeNode $node, stri
547543

548544
return RuntimeTypeChecker::bindInstanceFromNode($value, $node, $context);
549545
}
550-
}
546+
}

typephp.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,21 @@
6767
// \Acme\Domain\TypePHPExtension::class,
6868
],
6969

70+
/*
71+
|--------------------------------------------------------------------------
72+
| Array Validation Strategy
73+
|--------------------------------------------------------------------------
74+
| Controls how collections (list<T>, array<K, V>, Type[]) are verified:
75+
|
76+
| - 'full' : (Default / Strict) 100% exhaustive scan. Checks every single
77+
| item in every array, guaranteeing every single offending item
78+
| is caught without exception.
79+
|
80+
| - 'hybrid' : (Beartype O(1) Mode) Fast boundary + random sampling on
81+
| arrays > 64 items. Ideal for massive production datasets.
82+
*/
83+
'array_validation' => 'hybrid',
84+
7085
/*
7186
|--------------------------------------------------------------------------
7287
| Inline Variable Validation (@var $x = ...)

0 commit comments

Comments
 (0)