From b883e733c4e8f2bc4df2db70b09795dd668e93ee Mon Sep 17 00:00:00 2001 From: Romain Canon Date: Sun, 17 Mar 2024 21:59:05 +0100 Subject: [PATCH] misc: reduce number of calls to class autoloader during type parsing This change aims to reduce the number of calls made to the global class autoloader, by limiting the class existence checks during type parsing. --- .../ConstructorObjectBuilderFactory.php | 6 +++- .../ReflectionObjectBuilderFactory.php | 5 ++- .../Factory/LexingTypeParserFactory.php | 8 +++-- src/Type/Parser/Lexer/NativeLexer.php | 19 ++---------- src/Type/Parser/Lexer/ObjectLexer.php | 31 +++++++++++++++++++ src/Utility/Reflection/Reflection.php | 20 +++++++++--- .../Type/Parser/Lexer/NativeLexerTest.php | 3 +- .../Type/Parser/Lexer/NativeLexerTest.php | 3 +- 8 files changed, 67 insertions(+), 28 deletions(-) create mode 100644 src/Type/Parser/Lexer/ObjectLexer.php diff --git a/src/Mapper/Object/Factory/ConstructorObjectBuilderFactory.php b/src/Mapper/Object/Factory/ConstructorObjectBuilderFactory.php index 15232c1b..4c6bed8a 100644 --- a/src/Mapper/Object/Factory/ConstructorObjectBuilderFactory.php +++ b/src/Mapper/Object/Factory/ConstructorObjectBuilderFactory.php @@ -24,6 +24,7 @@ use CuyZ\Valinor\Type\Types\ClassStringType; use CuyZ\Valinor\Type\Types\EnumType; use CuyZ\Valinor\Type\Types\NativeStringType; +use CuyZ\Valinor\Utility\Reflection\Reflection; use function array_key_exists; use function array_values; @@ -176,7 +177,10 @@ private function filteredConstructors(): array foreach ($this->constructors as $constructor) { $function = $constructor->definition; - if (enum_exists($function->class ?? '') && in_array($function->name, ['from', 'tryFrom'], true)) { + if ($function->class + && Reflection::enumExists($function->class) + && in_array($function->name, ['from', 'tryFrom'], true) + ) { continue; } diff --git a/src/Mapper/Object/Factory/ReflectionObjectBuilderFactory.php b/src/Mapper/Object/Factory/ReflectionObjectBuilderFactory.php index d7f1391d..187f11bc 100644 --- a/src/Mapper/Object/Factory/ReflectionObjectBuilderFactory.php +++ b/src/Mapper/Object/Factory/ReflectionObjectBuilderFactory.php @@ -6,15 +6,14 @@ use CuyZ\Valinor\Definition\ClassDefinition; use CuyZ\Valinor\Mapper\Object\ReflectionObjectBuilder; - -use function enum_exists; +use CuyZ\Valinor\Utility\Reflection\Reflection; /** @internal */ final class ReflectionObjectBuilderFactory implements ObjectBuilderFactory { public function for(ClassDefinition $class): array { - if (enum_exists($class->name)) { + if (Reflection::enumExists($class->name)) { return []; } diff --git a/src/Type/Parser/Factory/LexingTypeParserFactory.php b/src/Type/Parser/Factory/LexingTypeParserFactory.php index facde920..186e5ee8 100644 --- a/src/Type/Parser/Factory/LexingTypeParserFactory.php +++ b/src/Type/Parser/Factory/LexingTypeParserFactory.php @@ -8,6 +8,7 @@ use CuyZ\Valinor\Type\Parser\Factory\Specifications\TypeParserSpecification; use CuyZ\Valinor\Type\Parser\Lexer\AdvancedClassLexer; use CuyZ\Valinor\Type\Parser\Lexer\NativeLexer; +use CuyZ\Valinor\Type\Parser\Lexer\ObjectLexer; use CuyZ\Valinor\Type\Parser\LexingParser; use CuyZ\Valinor\Type\Parser\TypeParser; @@ -22,20 +23,23 @@ public function get(TypeParserSpecification ...$specifications): TypeParser return $this->nativeParser ??= $this->nativeParser(); } - $lexer = new NativeLexer(); + $lexer = new ObjectLexer(); $lexer = new AdvancedClassLexer($lexer, $this); foreach ($specifications as $specification) { $lexer = $specification->transform($lexer); } + $lexer = new NativeLexer($lexer); + return new LexingParser($lexer); } private function nativeParser(): TypeParser { - $lexer = new NativeLexer(); + $lexer = new ObjectLexer(); $lexer = new AdvancedClassLexer($lexer, $this); + $lexer = new NativeLexer($lexer); $parser = new LexingParser($lexer); return new CachedParser($parser); diff --git a/src/Type/Parser/Lexer/NativeLexer.php b/src/Type/Parser/Lexer/NativeLexer.php index c1ce8e58..3f8340a9 100644 --- a/src/Type/Parser/Lexer/NativeLexer.php +++ b/src/Type/Parser/Lexer/NativeLexer.php @@ -6,7 +6,6 @@ use CuyZ\Valinor\Type\Parser\Lexer\Token\ArrayToken; use CuyZ\Valinor\Type\Parser\Lexer\Token\CallableToken; -use CuyZ\Valinor\Type\Parser\Lexer\Token\ClassNameToken; use CuyZ\Valinor\Type\Parser\Lexer\Token\ClassStringToken; use CuyZ\Valinor\Type\Parser\Lexer\Token\ClosingBracketToken; use CuyZ\Valinor\Type\Parser\Lexer\Token\ClosingCurlyBracketToken; @@ -14,7 +13,6 @@ use CuyZ\Valinor\Type\Parser\Lexer\Token\ColonToken; use CuyZ\Valinor\Type\Parser\Lexer\Token\CommaToken; use CuyZ\Valinor\Type\Parser\Lexer\Token\DoubleColonToken; -use CuyZ\Valinor\Type\Parser\Lexer\Token\EnumNameToken; use CuyZ\Valinor\Type\Parser\Lexer\Token\FloatValueToken; use CuyZ\Valinor\Type\Parser\Lexer\Token\IntegerToken; use CuyZ\Valinor\Type\Parser\Lexer\Token\IntegerValueToken; @@ -29,9 +27,6 @@ use CuyZ\Valinor\Type\Parser\Lexer\Token\QuoteToken; use CuyZ\Valinor\Type\Parser\Lexer\Token\Token; use CuyZ\Valinor\Type\Parser\Lexer\Token\UnionToken; -use CuyZ\Valinor\Type\Parser\Lexer\Token\UnknownSymbolToken; -use CuyZ\Valinor\Utility\Reflection\Reflection; -use UnitEnum; use function filter_var; use function is_numeric; @@ -40,6 +35,8 @@ /** @internal */ final class NativeLexer implements TypeLexer { + public function __construct(private TypeLexer $delegate) {} + public function tokenize(string $symbol): Token { if (NativeToken::accepts($symbol)) { @@ -83,16 +80,6 @@ public function tokenize(string $symbol): Token return new FloatValueToken((float)$symbol); } - if (enum_exists($symbol)) { - /** @var class-string $symbol */ - return new EnumNameToken($symbol); - } - - if (Reflection::classOrInterfaceExists($symbol)) { - /** @var class-string $symbol */ - return new ClassNameToken($symbol); - } - - return new UnknownSymbolToken($symbol); + return $this->delegate->tokenize($symbol); } } diff --git a/src/Type/Parser/Lexer/ObjectLexer.php b/src/Type/Parser/Lexer/ObjectLexer.php new file mode 100644 index 00000000..7f016fbf --- /dev/null +++ b/src/Type/Parser/Lexer/ObjectLexer.php @@ -0,0 +1,31 @@ + $symbol */ + return new EnumNameToken($symbol); + } + + if (Reflection::classOrInterfaceExists($symbol)) { + /** @var class-string $symbol */ + return new ClassNameToken($symbol); + } + + return new UnknownSymbolToken($symbol); + } +} diff --git a/src/Utility/Reflection/Reflection.php b/src/Utility/Reflection/Reflection.php index 5d26e148..36878a45 100644 --- a/src/Utility/Reflection/Reflection.php +++ b/src/Utility/Reflection/Reflection.php @@ -23,8 +23,10 @@ use function array_filter; use function array_map; use function class_exists; +use function enum_exists; use function implode; use function interface_exists; +use function ltrim; use function spl_object_hash; use function str_contains; @@ -37,16 +39,26 @@ final class Reflection /** @var array */ private static array $functionReflection = []; + /** @var array */ + private static array $classOrInterfaceExists = []; + + /** @var array */ + private static array $enumExists = []; + /** * Case-sensitive implementation of `class_exists` and `interface_exists`. */ public static function classOrInterfaceExists(string $name): bool { - if (! class_exists($name) && ! interface_exists($name)) { - return false; - } + // @infection-ignore-all / We don't need to test the cache + return self::$classOrInterfaceExists[$name] ??= (class_exists($name) || interface_exists($name)) + && self::class($name)->name === ltrim($name, '\\'); + } - return self::class($name)->name === ltrim($name, '\\'); + public static function enumExists(string $name): bool + { + // @infection-ignore-all / We don't need to test the cache + return self::$enumExists[$name] ??= enum_exists($name); } /** diff --git a/tests/Functional/Type/Parser/Lexer/NativeLexerTest.php b/tests/Functional/Type/Parser/Lexer/NativeLexerTest.php index 9f9e3850..cda6bdd5 100644 --- a/tests/Functional/Type/Parser/Lexer/NativeLexerTest.php +++ b/tests/Functional/Type/Parser/Lexer/NativeLexerTest.php @@ -42,6 +42,7 @@ use CuyZ\Valinor\Type\Parser\Exception\Scalar\IntegerRangeMissingMinValue; use CuyZ\Valinor\Type\Parser\Exception\Scalar\InvalidClassStringSubType; use CuyZ\Valinor\Type\Parser\Lexer\NativeLexer; +use CuyZ\Valinor\Type\Parser\Lexer\ObjectLexer; use CuyZ\Valinor\Type\Parser\LexingParser; use CuyZ\Valinor\Type\Parser\TypeParser; use CuyZ\Valinor\Type\StringType; @@ -89,7 +90,7 @@ protected function setUp(): void { parent::setUp(); - $lexer = new NativeLexer(); + $lexer = new NativeLexer(new ObjectLexer()); $this->parser = new LexingParser($lexer); } diff --git a/tests/Unit/Type/Parser/Lexer/NativeLexerTest.php b/tests/Unit/Type/Parser/Lexer/NativeLexerTest.php index fd3fb5c7..289bf3ab 100644 --- a/tests/Unit/Type/Parser/Lexer/NativeLexerTest.php +++ b/tests/Unit/Type/Parser/Lexer/NativeLexerTest.php @@ -6,6 +6,7 @@ use CuyZ\Valinor\Tests\Fixture\Enum\PureEnum; use CuyZ\Valinor\Type\Parser\Lexer\NativeLexer; +use CuyZ\Valinor\Type\Parser\Lexer\ObjectLexer; use CuyZ\Valinor\Type\Parser\Lexer\Token\ArrayToken; use CuyZ\Valinor\Type\Parser\Lexer\Token\ClassNameToken; use CuyZ\Valinor\Type\Parser\Lexer\Token\ClassStringToken; @@ -41,7 +42,7 @@ protected function setUp(): void { parent::setUp(); - $this->lexer = new NativeLexer(); + $this->lexer = new NativeLexer(new ObjectLexer()); } /**