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\Utility\Reflection\ClassAliasParser;
use CuyZ\Valinor\Utility\Reflection\Reflection;
use ReflectionClass;
use ReflectionFunction;
use Reflector;
use function class_exists;
use function interface_exists;
use function strtolower;
/** @internal */
final class AliasLexer implements TypeLexer
@ -43,7 +41,7 @@ final class AliasLexer implements TypeLexer
{
$alias = ClassAliasParser::get()->resolveAlias($symbol, $this->reflection);
if ($alias !== $symbol) {
if (strtolower($alias) !== strtolower($symbol)) {
return $alias;
}
@ -77,7 +75,7 @@ final class AliasLexer implements TypeLexer
$full = $namespace . '\\' . $symbol;
if (class_exists($full) || interface_exists($full)) {
if (Reflection::classOrInterfaceExists($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\UnknownSymbolToken;
use CuyZ\Valinor\Utility\Polyfill;
use CuyZ\Valinor\Utility\Reflection\Reflection;
use UnitEnum;
use function class_exists;
use function filter_var;
use function interface_exists;
use function is_numeric;
use function strtolower;
use function substr;
@ -109,7 +108,8 @@ final class NativeLexer implements TypeLexer
return new EnumNameToken($symbol);
}
if (class_exists($symbol) || interface_exists($symbol)) {
if (Reflection::classOrInterfaceExists($symbol)) {
/** @var class-string $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\InvalidClassString;
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_string;
use function method_exists;
@ -51,7 +50,7 @@ final class ClassStringType implements StringType, CompositeType
return false;
}
if (! class_exists($value) && ! interface_exists($value)) {
if (! Reflection::classOrInterfaceExists($value)) {
return false;
}

View File

@ -18,8 +18,10 @@ use ReflectionUnionType;
use Reflector;
use RuntimeException;
use function class_exists;
use function get_class;
use function implode;
use function interface_exists;
use function preg_match_all;
use function preg_replace;
use function spl_object_hash;
@ -39,6 +41,18 @@ final class Reflection
/** @var array<string, ReflectionFunction> */
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
* @return ReflectionClass<object>
@ -132,7 +146,7 @@ final class Reflection
if (PHP_VERSION_ID >= 8_00_00 && $reflection->isPromoted()) {
$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)),
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}',
'type' => ShapedArrayType::class,
],
'Shaped array with key equal to class name' => [
'raw' => 'array{stdclass: string}',
'transformed' => 'array{stdclass: string}',
'type' => ShapedArrayType::class,
],
'Iterable type' => [
'raw' => 'iterable',
'transformed' => 'iterable',

View File

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