Skip to content

Commit

Permalink
misc: reduce number of calls to class autoloader during type parsing
Browse files Browse the repository at this point in the history
This change aims to reduce the number of calls made to the global class
autoloader, by limiting the class existence checks during type parsing.
  • Loading branch information
romm committed Mar 17, 2024
1 parent 4f555d9 commit 0f0e35e
Show file tree
Hide file tree
Showing 8 changed files with 67 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Check warning on line 184 in src/Mapper/Object/Factory/ConstructorObjectBuilderFactory.php

View workflow job for this annotation

GitHub Actions / Mutation tests

Escaped Mutant for Mutator "Continue_": --- Original +++ New @@ @@ foreach ($this->constructors as $constructor) { $function = $constructor->definition; if ($function->class && Reflection::enumExists($function->class) && in_array($function->name, ['from', 'tryFrom'], true)) { - continue; + break; } if (!$function->returnType instanceof ObjectType) { throw new InvalidConstructorReturnType($function);
}

Expand Down
5 changes: 2 additions & 3 deletions src/Mapper/Object/Factory/ReflectionObjectBuilderFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 [];
}

Expand Down
8 changes: 6 additions & 2 deletions src/Type/Parser/Factory/LexingTypeParserFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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);
Expand Down
19 changes: 3 additions & 16 deletions src/Type/Parser/Lexer/NativeLexer.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@

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;
use CuyZ\Valinor\Type\Parser\Lexer\Token\ClosingSquareBracketToken;
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;
Expand All @@ -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;
Expand All @@ -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)) {
Expand Down Expand Up @@ -83,16 +80,6 @@ public function tokenize(string $symbol): Token
return new FloatValueToken((float)$symbol);
}

if (enum_exists($symbol)) {
/** @var class-string<UnitEnum> $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);
}
}
31 changes: 31 additions & 0 deletions src/Type/Parser/Lexer/ObjectLexer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Type\Parser\Lexer;

use CuyZ\Valinor\Type\Parser\Lexer\Token\ClassNameToken;
use CuyZ\Valinor\Type\Parser\Lexer\Token\EnumNameToken;
use CuyZ\Valinor\Type\Parser\Lexer\Token\Token;
use CuyZ\Valinor\Type\Parser\Lexer\Token\UnknownSymbolToken;
use CuyZ\Valinor\Utility\Reflection\Reflection;
use UnitEnum;

/** @internal */
final class ObjectLexer implements TypeLexer
{
public function tokenize(string $symbol): Token
{
if (Reflection::enumExists($symbol)) {
/** @var class-string<UnitEnum> $symbol */
return new EnumNameToken($symbol);
}

if (Reflection::classOrInterfaceExists($symbol)) {
/** @var class-string $symbol */
return new ClassNameToken($symbol);
}

return new UnknownSymbolToken($symbol);
}
}
20 changes: 16 additions & 4 deletions src/Utility/Reflection/Reflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -37,16 +39,26 @@ final class Reflection
/** @var array<string, ReflectionFunction> */
private static array $functionReflection = [];

/** @var array<string, bool> */
private static array $classOrInterfaceExists = [];

/** @var array<string, bool> */
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);
}

/**
Expand Down
3 changes: 2 additions & 1 deletion tests/Functional/Type/Parser/Lexer/NativeLexerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -89,7 +90,7 @@ protected function setUp(): void
{
parent::setUp();

$lexer = new NativeLexer();
$lexer = new NativeLexer(new ObjectLexer());

$this->parser = new LexingParser($lexer);
}
Expand Down
3 changes: 2 additions & 1 deletion tests/Unit/Type/Parser/Lexer/NativeLexerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -41,7 +42,7 @@ protected function setUp(): void
{
parent::setUp();

$this->lexer = new NativeLexer();
$this->lexer = new NativeLexer(new ObjectLexer());
}

/**
Expand Down

0 comments on commit 0f0e35e

Please # to comment.