fix: handle classes in a case-sensitive way in type parser

This commit is contained in:
Romain Canon 2022-08-04 18:03:34 +02:00
parent 6414e9cf14
commit 2540741171
6 changed files with 40 additions and 14 deletions

View File

@ -6,14 +6,12 @@ namespace CuyZ\Valinor\Type\Parser\Lexer;
use CuyZ\Valinor\Type\Parser\Lexer\Token\Token; use CuyZ\Valinor\Type\Parser\Lexer\Token\Token;
use CuyZ\Valinor\Utility\Reflection\ClassAliasParser; use CuyZ\Valinor\Utility\Reflection\ClassAliasParser;
use CuyZ\Valinor\Utility\Reflection\Reflection;
use ReflectionClass; use ReflectionClass;
use ReflectionFunction; use ReflectionFunction;
use Reflector; use Reflector;
use function class_exists; use function strtolower;
use function interface_exists;
/** @internal */ /** @internal */
final class AliasLexer implements TypeLexer final class AliasLexer implements TypeLexer
@ -43,7 +41,7 @@ final class AliasLexer implements TypeLexer
{ {
$alias = ClassAliasParser::get()->resolveAlias($symbol, $this->reflection); $alias = ClassAliasParser::get()->resolveAlias($symbol, $this->reflection);
if ($alias !== $symbol) { if (strtolower($alias) !== strtolower($symbol)) {
return $alias; return $alias;
} }
@ -77,7 +75,7 @@ final class AliasLexer implements TypeLexer
$full = $namespace . '\\' . $symbol; $full = $namespace . '\\' . $symbol;
if (class_exists($full) || interface_exists($full)) { if (Reflection::classOrInterfaceExists($full)) {
return $full; return $full;
} }

View File

@ -29,11 +29,10 @@ use CuyZ\Valinor\Type\Parser\Lexer\Token\Token;
use CuyZ\Valinor\Type\Parser\Lexer\Token\UnionToken; use CuyZ\Valinor\Type\Parser\Lexer\Token\UnionToken;
use CuyZ\Valinor\Type\Parser\Lexer\Token\UnknownSymbolToken; use CuyZ\Valinor\Type\Parser\Lexer\Token\UnknownSymbolToken;
use CuyZ\Valinor\Utility\Polyfill; use CuyZ\Valinor\Utility\Polyfill;
use CuyZ\Valinor\Utility\Reflection\Reflection;
use UnitEnum; use UnitEnum;
use function class_exists;
use function filter_var; use function filter_var;
use function interface_exists;
use function is_numeric; use function is_numeric;
use function strtolower; use function strtolower;
use function substr; use function substr;
@ -109,7 +108,8 @@ final class NativeLexer implements TypeLexer
return new EnumNameToken($symbol); return new EnumNameToken($symbol);
} }
if (class_exists($symbol) || interface_exists($symbol)) { if (Reflection::classOrInterfaceExists($symbol)) {
/** @var class-string $symbol */
return new ClassNameToken($symbol); return new ClassNameToken($symbol);
} }

View File

@ -11,9 +11,8 @@ use CuyZ\Valinor\Type\Type;
use CuyZ\Valinor\Type\Types\Exception\CannotCastValue; use CuyZ\Valinor\Type\Types\Exception\CannotCastValue;
use CuyZ\Valinor\Type\Types\Exception\InvalidClassString; use CuyZ\Valinor\Type\Types\Exception\InvalidClassString;
use CuyZ\Valinor\Type\Types\Exception\InvalidUnionOfClassString; use CuyZ\Valinor\Type\Types\Exception\InvalidUnionOfClassString;
use CuyZ\Valinor\Utility\Reflection\Reflection;
use function class_exists;
use function interface_exists;
use function is_object; use function is_object;
use function is_string; use function is_string;
use function method_exists; use function method_exists;
@ -51,7 +50,7 @@ final class ClassStringType implements StringType, CompositeType
return false; return false;
} }
if (! class_exists($value) && ! interface_exists($value)) { if (! Reflection::classOrInterfaceExists($value)) {
return false; return false;
} }

View File

@ -18,8 +18,10 @@ use ReflectionUnionType;
use Reflector; use Reflector;
use RuntimeException; use RuntimeException;
use function class_exists;
use function get_class; use function get_class;
use function implode; use function implode;
use function interface_exists;
use function preg_match_all; use function preg_match_all;
use function preg_replace; use function preg_replace;
use function spl_object_hash; use function spl_object_hash;
@ -39,6 +41,18 @@ final class Reflection
/** @var array<string, ReflectionFunction> */ /** @var array<string, ReflectionFunction> */
private static array $functionReflection = []; private static array $functionReflection = [];
/**
* 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;
}
return self::class($name)->name === ltrim($name, '\\');
}
/** /**
* @param class-string $className * @param class-string $className
* @return ReflectionClass<object> * @return ReflectionClass<object>
@ -132,7 +146,7 @@ final class Reflection
if (PHP_VERSION_ID >= 8_00_00 && $reflection->isPromoted()) { if (PHP_VERSION_ID >= 8_00_00 && $reflection->isPromoted()) {
$type = self::parseDocBlock( $type = self::parseDocBlock(
// @phpstan-ignore-next-line / parameter is promoted so class exists for sure // @phpstan-ignore-next-line / parameter is promoted so class exists for sure
self::sanitizeDocComment($reflection->getDeclaringClass()->getProperty($reflection->name)), self::sanitizeDocComment($reflection->getDeclaringClass()->getProperty($reflection->name)),
sprintf('@%s?var\s+%s', self::TOOL_EXPRESSION, self::TYPE_EXPRESSION) sprintf('@%s?var\s+%s', self::TOOL_EXPRESSION, self::TYPE_EXPRESSION)
); );

View File

@ -563,6 +563,11 @@ final class NativeLexerTest extends TestCase
'transformed' => 'array{foo: string}', 'transformed' => 'array{foo: string}',
'type' => ShapedArrayType::class, 'type' => ShapedArrayType::class,
], ],
'Shaped array with key equal to class name' => [
'raw' => 'array{stdclass: string}',
'transformed' => 'array{stdclass: string}',
'type' => ShapedArrayType::class,
],
'Iterable type' => [ 'Iterable type' => [
'raw' => 'iterable', 'raw' => 'iterable',
'transformed' => 'iterable', 'transformed' => 'iterable',

View File

@ -38,6 +38,9 @@ final class ShapedArrayValuesMappingTest extends IntegrationTest
1337, 1337,
42.404, 42.404,
], ],
'shapedArrayWithClassNameAsKey' => [
'stdclass' => 'foo',
],
]; ];
foreach ([ShapedArrayValues::class, ShapedArrayValuesWithConstructor::class] as $class) { foreach ([ShapedArrayValues::class, ShapedArrayValuesWithConstructor::class] as $class) {
@ -55,6 +58,7 @@ final class ShapedArrayValuesMappingTest extends IntegrationTest
self::assertSame('bar', $result->advancedShapedArray['mandatoryString']); self::assertSame('bar', $result->advancedShapedArray['mandatoryString']);
self::assertSame(1337, $result->advancedShapedArray[0]); self::assertSame(1337, $result->advancedShapedArray[0]);
self::assertSame(42.404, $result->advancedShapedArray[1]); self::assertSame(42.404, $result->advancedShapedArray[1]);
self::assertSame('foo', $result->shapedArrayWithClassNameAsKey['stdclass']);
} }
} }
@ -100,6 +104,9 @@ class ShapedArrayValues
/** @var array{0: int, float, optionalString?: string, mandatoryString: string} */ /** @var array{0: int, float, optionalString?: string, mandatoryString: string} */
public array $advancedShapedArray; public array $advancedShapedArray;
/** @var array{stdclass: string} */
public array $shapedArrayWithClassNameAsKey;
} }
class ShapedArrayValuesWithConstructor extends ShapedArrayValues class ShapedArrayValuesWithConstructor extends ShapedArrayValues
@ -114,6 +121,7 @@ class ShapedArrayValuesWithConstructor extends ShapedArrayValues
* bar: int * bar: int
* } $shapedArrayOnSeveralLines * } $shapedArrayOnSeveralLines
* @param array{0: int, float, optionalString?: string, mandatoryString: string} $advancedShapedArray * @param array{0: int, float, optionalString?: string, mandatoryString: string} $advancedShapedArray
* @param array{stdclass: string} $shapedArrayWithClassNameAsKey
*/ */
public function __construct( public function __construct(
array $basicShapedArrayWithStringKeys, array $basicShapedArrayWithStringKeys,
@ -121,7 +129,8 @@ class ShapedArrayValuesWithConstructor extends ShapedArrayValues
array $shapedArrayWithObject, array $shapedArrayWithObject,
array $shapedArrayWithOptionalValue, array $shapedArrayWithOptionalValue,
array $shapedArrayOnSeveralLines, array $shapedArrayOnSeveralLines,
array $advancedShapedArray array $advancedShapedArray,
array $shapedArrayWithClassNameAsKey
) { ) {
$this->basicShapedArrayWithStringKeys = $basicShapedArrayWithStringKeys; $this->basicShapedArrayWithStringKeys = $basicShapedArrayWithStringKeys;
$this->basicShapedArrayWithIntegerKeys = $basicShapedArrayWithIntegerKeys; $this->basicShapedArrayWithIntegerKeys = $basicShapedArrayWithIntegerKeys;
@ -129,5 +138,6 @@ class ShapedArrayValuesWithConstructor extends ShapedArrayValues
$this->shapedArrayWithOptionalValue = $shapedArrayWithOptionalValue; $this->shapedArrayWithOptionalValue = $shapedArrayWithOptionalValue;
$this->shapedArrayOnSeveralLines = $shapedArrayOnSeveralLines; $this->shapedArrayOnSeveralLines = $shapedArrayOnSeveralLines;
$this->advancedShapedArray = $advancedShapedArray; $this->advancedShapedArray = $advancedShapedArray;
$this->shapedArrayWithClassNameAsKey = $shapedArrayWithClassNameAsKey;
} }
} }