mirror of
https://github.com/danog/Valinor.git
synced 2024-11-26 20:24:40 +01:00
fix: handle classes in a case-sensitive way in type parser
This commit is contained in:
parent
6414e9cf14
commit
2540741171
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
);
|
||||
|
@ -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',
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user