mirror of
https://github.com/danog/Valinor.git
synced 2025-01-22 05:11:52 +01:00
refactor: extract reflection type resolving to class
This commit is contained in:
parent
680941687b
commit
99b4f4f7aa
@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Definition\Exception;
|
||||
|
||||
use CuyZ\Valinor\Type\Type;
|
||||
use CuyZ\Valinor\Utility\Reflection\Reflection;
|
||||
use LogicException;
|
||||
use ReflectionMethod;
|
||||
|
||||
final class MethodReturnTypesDoNotMatch extends LogicException
|
||||
{
|
||||
public function __construct(ReflectionMethod $parameter, Type $typeFromDocBlock, Type $typeFromReflection)
|
||||
{
|
||||
$signature = Reflection::signature($parameter);
|
||||
|
||||
parent::__construct(
|
||||
"Return types for method `$signature` do not match: `$typeFromDocBlock` (docblock) does not accept `$typeFromReflection` (native).",
|
||||
1634048916
|
||||
);
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Definition\Exception;
|
||||
|
||||
use CuyZ\Valinor\Type\Type;
|
||||
use CuyZ\Valinor\Utility\Reflection\Reflection;
|
||||
use LogicException;
|
||||
use ReflectionParameter;
|
||||
|
||||
final class ParameterTypesDoNotMatch extends LogicException
|
||||
{
|
||||
public function __construct(ReflectionParameter $parameter, Type $typeFromDocBlock, Type $typeFromReflection)
|
||||
{
|
||||
$signature = Reflection::signature($parameter);
|
||||
|
||||
parent::__construct(
|
||||
"Types for parameter `$signature` do not match: `$typeFromDocBlock` (docblock) does not accept `$typeFromReflection` (native).",
|
||||
1617220595
|
||||
);
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Definition\Exception;
|
||||
|
||||
use CuyZ\Valinor\Type\Type;
|
||||
use CuyZ\Valinor\Utility\Reflection\Reflection;
|
||||
use LogicException;
|
||||
use ReflectionProperty;
|
||||
|
||||
final class PropertyTypesDoNotMatch extends LogicException
|
||||
{
|
||||
public function __construct(ReflectionProperty $property, Type $typeFromDocBlock, Type $typeFromReflection)
|
||||
{
|
||||
$signature = Reflection::signature($property);
|
||||
|
||||
parent::__construct(
|
||||
"Types for property `$signature` do not match: `$typeFromDocBlock` (docblock) does not accept `$typeFromReflection` (native).",
|
||||
1617218939
|
||||
);
|
||||
}
|
||||
}
|
34
src/Definition/Exception/TypesDoNotMatch.php
Normal file
34
src/Definition/Exception/TypesDoNotMatch.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Definition\Exception;
|
||||
|
||||
use CuyZ\Valinor\Type\Type;
|
||||
use CuyZ\Valinor\Utility\Reflection\Reflection;
|
||||
use LogicException;
|
||||
use ReflectionFunctionAbstract;
|
||||
use ReflectionParameter;
|
||||
use ReflectionProperty;
|
||||
use Reflector;
|
||||
|
||||
final class TypesDoNotMatch extends LogicException
|
||||
{
|
||||
/**
|
||||
* @param ReflectionProperty|ReflectionParameter|ReflectionFunctionAbstract $reflection
|
||||
*/
|
||||
public function __construct(Reflector $reflection, Type $typeFromDocBlock, Type $typeFromReflection)
|
||||
{
|
||||
$signature = Reflection::signature($reflection);
|
||||
|
||||
if ($reflection instanceof ReflectionProperty) {
|
||||
$message = "Types for property `$signature` do not match: `$typeFromDocBlock` (docblock) does not accept `$typeFromReflection` (native).";
|
||||
} elseif ($reflection instanceof ReflectionParameter) {
|
||||
$message = "Types for parameter `$signature` do not match: `$typeFromDocBlock` (docblock) does not accept `$typeFromReflection` (native).";
|
||||
} else {
|
||||
$message = "Return types for method `$signature` do not match: `$typeFromDocBlock` (docblock) does not accept `$typeFromReflection` (native).";
|
||||
}
|
||||
|
||||
parent::__construct($message, 1638471381);
|
||||
}
|
||||
}
|
@ -10,6 +10,10 @@ use CuyZ\Valinor\Definition\Methods;
|
||||
use CuyZ\Valinor\Definition\Properties;
|
||||
use CuyZ\Valinor\Definition\Repository\AttributesRepository;
|
||||
use CuyZ\Valinor\Definition\Repository\ClassDefinitionRepository;
|
||||
use CuyZ\Valinor\Type\Parser\Factory\Specifications\ClassAliasSpecification;
|
||||
use CuyZ\Valinor\Type\Parser\Factory\Specifications\ClassContextSpecification;
|
||||
use CuyZ\Valinor\Type\Parser\Factory\Specifications\HandleClassGenericSpecification;
|
||||
use CuyZ\Valinor\Type\Parser\Factory\Specifications\TypeAliasAssignerSpecification;
|
||||
use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
|
||||
use CuyZ\Valinor\Utility\Reflection\Reflection;
|
||||
use ReflectionMethod;
|
||||
@ -21,6 +25,8 @@ final class ReflectionClassDefinitionRepository implements ClassDefinitionReposi
|
||||
{
|
||||
private AttributesRepository $attributesFactory;
|
||||
|
||||
private TypeParserFactory $typeParserFactory;
|
||||
|
||||
private ReflectionPropertyDefinitionBuilder $propertyBuilder;
|
||||
|
||||
private ReflectionMethodDefinitionBuilder $methodBuilder;
|
||||
@ -28,22 +34,24 @@ final class ReflectionClassDefinitionRepository implements ClassDefinitionReposi
|
||||
public function __construct(TypeParserFactory $typeParserFactory, AttributesRepository $attributesRepository)
|
||||
{
|
||||
$this->attributesFactory = $attributesRepository;
|
||||
$this->typeParserFactory = $typeParserFactory;
|
||||
|
||||
$this->propertyBuilder = new ReflectionPropertyDefinitionBuilder($typeParserFactory, $attributesRepository);
|
||||
$this->methodBuilder = new ReflectionMethodDefinitionBuilder($typeParserFactory, $attributesRepository);
|
||||
$this->propertyBuilder = new ReflectionPropertyDefinitionBuilder($attributesRepository);
|
||||
$this->methodBuilder = new ReflectionMethodDefinitionBuilder($attributesRepository);
|
||||
}
|
||||
|
||||
public function for(ClassSignature $signature): ClassDefinition
|
||||
{
|
||||
$reflection = Reflection::class($signature->className());
|
||||
$typeResolver = $this->typeResolver($signature);
|
||||
|
||||
$properties = array_map(
|
||||
fn (ReflectionProperty $property) => $this->propertyBuilder->for($signature, $property),
|
||||
fn (ReflectionProperty $property) => $this->propertyBuilder->for($property, $typeResolver),
|
||||
$reflection->getProperties()
|
||||
);
|
||||
|
||||
$methods = array_map(
|
||||
fn (ReflectionMethod $method) => $this->methodBuilder->for($signature, $method),
|
||||
fn (ReflectionMethod $method) => $this->methodBuilder->for($method, $typeResolver),
|
||||
$reflection->getMethods()
|
||||
);
|
||||
|
||||
@ -55,4 +63,20 @@ final class ReflectionClassDefinitionRepository implements ClassDefinitionReposi
|
||||
new Methods(...$methods),
|
||||
);
|
||||
}
|
||||
|
||||
private function typeResolver(ClassSignature $signature): ReflectionTypeResolver
|
||||
{
|
||||
$nativeParser = $this->typeParserFactory->get(
|
||||
new ClassContextSpecification($signature->className())
|
||||
);
|
||||
|
||||
$advancedParser = $this->typeParserFactory->get(
|
||||
new ClassContextSpecification($signature->className()),
|
||||
new ClassAliasSpecification($signature->className()),
|
||||
new HandleClassGenericSpecification(),
|
||||
new TypeAliasAssignerSpecification($signature->generics()),
|
||||
);
|
||||
|
||||
return new ReflectionTypeResolver($nativeParser, $advancedParser);
|
||||
}
|
||||
}
|
||||
|
@ -4,48 +4,32 @@ declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Definition\Repository\Reflection;
|
||||
|
||||
use CuyZ\Valinor\Definition\ClassSignature;
|
||||
use CuyZ\Valinor\Definition\Exception\MethodReturnTypesDoNotMatch;
|
||||
use CuyZ\Valinor\Definition\MethodDefinition;
|
||||
use CuyZ\Valinor\Definition\Parameters;
|
||||
use CuyZ\Valinor\Definition\Repository\AttributesRepository;
|
||||
use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
|
||||
use CuyZ\Valinor\Type\Parser\Factory\Specifications\ClassAliasSpecification;
|
||||
use CuyZ\Valinor\Type\Parser\Factory\Specifications\ClassContextSpecification;
|
||||
use CuyZ\Valinor\Type\Parser\Factory\Specifications\TypeAliasAssignerSpecification;
|
||||
use CuyZ\Valinor\Type\Parser\Factory\Specifications\HandleClassGenericSpecification;
|
||||
use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
|
||||
use CuyZ\Valinor\Type\Parser\TypeParser;
|
||||
use CuyZ\Valinor\Type\Type;
|
||||
use CuyZ\Valinor\Type\Types\MixedType;
|
||||
use CuyZ\Valinor\Type\Types\UnresolvableType;
|
||||
use CuyZ\Valinor\Utility\Reflection\Reflection;
|
||||
use ReflectionMethod;
|
||||
use ReflectionParameter;
|
||||
|
||||
use function array_map;
|
||||
use function trim;
|
||||
|
||||
final class ReflectionMethodDefinitionBuilder
|
||||
{
|
||||
private TypeParserFactory $typeParserFactory;
|
||||
|
||||
private ReflectionParameterDefinitionBuilder $parameterBuilder;
|
||||
|
||||
public function __construct(TypeParserFactory $typeParserFactory, AttributesRepository $attributesRepository)
|
||||
public function __construct(AttributesRepository $attributesRepository)
|
||||
{
|
||||
$this->typeParserFactory = $typeParserFactory;
|
||||
$this->parameterBuilder = new ReflectionParameterDefinitionBuilder($typeParserFactory, $attributesRepository);
|
||||
$this->parameterBuilder = new ReflectionParameterDefinitionBuilder($attributesRepository);
|
||||
}
|
||||
|
||||
public function for(ClassSignature $signature, ReflectionMethod $reflection): MethodDefinition
|
||||
public function for(ReflectionMethod $reflection, ReflectionTypeResolver $typeResolver): MethodDefinition
|
||||
{
|
||||
$parameters = array_map(
|
||||
fn (ReflectionParameter $parameter) => $this->parameterBuilder->for($signature, $parameter),
|
||||
fn (ReflectionParameter $parameter) => $this->parameterBuilder->for($parameter, $typeResolver),
|
||||
$reflection->getParameters()
|
||||
);
|
||||
|
||||
$returnType = $this->resolveType($signature, $reflection);
|
||||
$returnType = $typeResolver->resolveType($reflection);
|
||||
|
||||
return new MethodDefinition(
|
||||
$reflection->name,
|
||||
@ -56,80 +40,4 @@ final class ReflectionMethodDefinitionBuilder
|
||||
$returnType
|
||||
);
|
||||
}
|
||||
|
||||
private function resolveType(ClassSignature $signature, ReflectionMethod $reflection): Type
|
||||
{
|
||||
$nativeType = $this->nativeType($reflection);
|
||||
$typeFromDocBlock = $this->typeFromDocBlock($signature, $reflection);
|
||||
|
||||
if (! $nativeType && ! $typeFromDocBlock) {
|
||||
return MixedType::get();
|
||||
}
|
||||
|
||||
if (! $nativeType) {
|
||||
/** @var Type $typeFromDocBlock */
|
||||
return $typeFromDocBlock;
|
||||
}
|
||||
|
||||
if (! $typeFromDocBlock) {
|
||||
return $nativeType;
|
||||
}
|
||||
|
||||
if (! $typeFromDocBlock instanceof UnresolvableType
|
||||
&& ! $nativeType instanceof UnresolvableType
|
||||
&& ! $typeFromDocBlock->matches($nativeType)
|
||||
) {
|
||||
throw new MethodReturnTypesDoNotMatch($reflection, $typeFromDocBlock, $nativeType);
|
||||
}
|
||||
|
||||
return $typeFromDocBlock;
|
||||
}
|
||||
|
||||
private function typeFromDocBlock(ClassSignature $signature, ReflectionMethod $reflection): ?Type
|
||||
{
|
||||
$type = Reflection::docBlockReturnType($reflection);
|
||||
|
||||
if ($type === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parser = $this->typeParserFactory->get(
|
||||
new ClassContextSpecification($signature->className()),
|
||||
new ClassAliasSpecification($signature->className()),
|
||||
new HandleClassGenericSpecification(),
|
||||
new TypeAliasAssignerSpecification($signature->generics()),
|
||||
);
|
||||
|
||||
return $this->parseType($type, $reflection, $parser);
|
||||
}
|
||||
|
||||
private function nativeType(ReflectionMethod $reflection): ?Type
|
||||
{
|
||||
$reflectionType = $reflection->getReturnType();
|
||||
|
||||
if (! $reflectionType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$type = Reflection::flattenType($reflectionType);
|
||||
$parser = $this->typeParserFactory->get(
|
||||
new ClassContextSpecification($reflection->getDeclaringClass()->name)
|
||||
);
|
||||
|
||||
return $this->parseType($type, $reflection, $parser);
|
||||
}
|
||||
|
||||
private function parseType(string $raw, ReflectionMethod $reflection, TypeParser $parser): Type
|
||||
{
|
||||
try {
|
||||
return $parser->parse($raw);
|
||||
} catch (InvalidType $exception) {
|
||||
$raw = trim($raw);
|
||||
$signature = Reflection::signature($reflection);
|
||||
|
||||
return new UnresolvableType(
|
||||
"The type `$raw` for return type of method `$signature` could not be resolved: {$exception->getMessage()}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,43 +4,27 @@ declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Definition\Repository\Reflection;
|
||||
|
||||
use CuyZ\Valinor\Definition\ClassSignature;
|
||||
use CuyZ\Valinor\Definition\Exception\InvalidParameterDefaultValue;
|
||||
use CuyZ\Valinor\Definition\Exception\ParameterTypesDoNotMatch;
|
||||
use CuyZ\Valinor\Definition\ParameterDefinition;
|
||||
use CuyZ\Valinor\Definition\Repository\AttributesRepository;
|
||||
use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
|
||||
use CuyZ\Valinor\Type\Parser\Factory\Specifications\ClassAliasSpecification;
|
||||
use CuyZ\Valinor\Type\Parser\Factory\Specifications\ClassContextSpecification;
|
||||
use CuyZ\Valinor\Type\Parser\Factory\Specifications\TypeAliasAssignerSpecification;
|
||||
use CuyZ\Valinor\Type\Parser\Factory\Specifications\HandleClassGenericSpecification;
|
||||
use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
|
||||
use CuyZ\Valinor\Type\Parser\TypeParser;
|
||||
use CuyZ\Valinor\Type\Type;
|
||||
use CuyZ\Valinor\Type\Types\MixedType;
|
||||
use CuyZ\Valinor\Type\Types\UnresolvableType;
|
||||
use CuyZ\Valinor\Utility\Reflection\Reflection;
|
||||
use ReflectionParameter;
|
||||
|
||||
use function trim;
|
||||
|
||||
final class ReflectionParameterDefinitionBuilder
|
||||
{
|
||||
private TypeParserFactory $typeParserFactory;
|
||||
|
||||
private AttributesRepository $attributesFactory;
|
||||
|
||||
public function __construct(TypeParserFactory $typeParserFactory, AttributesRepository $attributesRepository)
|
||||
public function __construct(AttributesRepository $attributesRepository)
|
||||
{
|
||||
$this->typeParserFactory = $typeParserFactory;
|
||||
$this->attributesFactory = $attributesRepository;
|
||||
}
|
||||
|
||||
public function for(ClassSignature $classSignature, ReflectionParameter $reflection): ParameterDefinition
|
||||
public function for(ReflectionParameter $reflection, ReflectionTypeResolver $typeResolver): ParameterDefinition
|
||||
{
|
||||
$name = $reflection->name;
|
||||
$signature = Reflection::signature($reflection);
|
||||
$type = $this->resolveType($classSignature, $reflection);
|
||||
$type = $typeResolver->resolveType($reflection);
|
||||
$isOptional = $reflection->isOptional();
|
||||
$defaultValue = $reflection->isDefaultValueAvailable() ? $reflection->getDefaultValue() : null;
|
||||
$attributes = $this->attributesFactory->for($reflection);
|
||||
@ -54,84 +38,4 @@ final class ReflectionParameterDefinitionBuilder
|
||||
|
||||
return new ParameterDefinition($name, $signature, $type, $isOptional, $defaultValue, $attributes);
|
||||
}
|
||||
|
||||
private function resolveType(ClassSignature $signature, ReflectionParameter $reflection): Type
|
||||
{
|
||||
$nativeType = $this->nativeType($reflection);
|
||||
$typeFromDocBlock = $this->typeFromDocBlock($signature, $reflection);
|
||||
|
||||
if (! $nativeType && ! $typeFromDocBlock) {
|
||||
return MixedType::get();
|
||||
}
|
||||
|
||||
if (! $nativeType) {
|
||||
/** @var Type $typeFromDocBlock */
|
||||
return $typeFromDocBlock;
|
||||
}
|
||||
|
||||
if (! $typeFromDocBlock) {
|
||||
return $nativeType;
|
||||
}
|
||||
|
||||
if (! $typeFromDocBlock instanceof UnresolvableType
|
||||
&& ! $nativeType instanceof UnresolvableType
|
||||
&& ! $typeFromDocBlock->matches($nativeType)
|
||||
) {
|
||||
throw new ParameterTypesDoNotMatch($reflection, $typeFromDocBlock, $nativeType);
|
||||
}
|
||||
|
||||
return $typeFromDocBlock;
|
||||
}
|
||||
|
||||
private function typeFromDocBlock(ClassSignature $signature, ReflectionParameter $reflection): ?Type
|
||||
{
|
||||
$type = Reflection::docBlockType($reflection);
|
||||
|
||||
if ($type === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parser = $this->typeParserFactory->get(
|
||||
new ClassContextSpecification($signature->className()),
|
||||
new ClassAliasSpecification($signature->className()),
|
||||
new HandleClassGenericSpecification(),
|
||||
new TypeAliasAssignerSpecification($signature->generics()),
|
||||
);
|
||||
|
||||
return $this->parseType($type, $reflection, $parser);
|
||||
}
|
||||
|
||||
private function nativeType(ReflectionParameter $reflection): ?Type
|
||||
{
|
||||
$reflectionType = $reflection->getType();
|
||||
|
||||
if (! $reflectionType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$class = $reflection->getDeclaringClass();
|
||||
|
||||
assert($class !== null);
|
||||
|
||||
$type = Reflection::flattenType($reflectionType);
|
||||
$parser = $this->typeParserFactory->get(
|
||||
new ClassContextSpecification($class->name)
|
||||
);
|
||||
|
||||
return $this->parseType($type, $reflection, $parser);
|
||||
}
|
||||
|
||||
private function parseType(string $raw, ReflectionParameter $reflection, TypeParser $parser): Type
|
||||
{
|
||||
try {
|
||||
return $parser->parse($raw);
|
||||
} catch (InvalidType $exception) {
|
||||
$raw = trim($raw);
|
||||
$signature = Reflection::signature($reflection);
|
||||
|
||||
return new UnresolvableType(
|
||||
"The type `$raw` for parameter `$signature` could not be resolved: {$exception->getMessage()}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,43 +4,27 @@ declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Definition\Repository\Reflection;
|
||||
|
||||
use CuyZ\Valinor\Definition\ClassSignature;
|
||||
use CuyZ\Valinor\Definition\Exception\InvalidPropertyDefaultValue;
|
||||
use CuyZ\Valinor\Definition\Exception\PropertyTypesDoNotMatch;
|
||||
use CuyZ\Valinor\Definition\PropertyDefinition;
|
||||
use CuyZ\Valinor\Definition\Repository\AttributesRepository;
|
||||
use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
|
||||
use CuyZ\Valinor\Type\Parser\Factory\Specifications\ClassAliasSpecification;
|
||||
use CuyZ\Valinor\Type\Parser\Factory\Specifications\ClassContextSpecification;
|
||||
use CuyZ\Valinor\Type\Parser\Factory\Specifications\HandleClassGenericSpecification;
|
||||
use CuyZ\Valinor\Type\Parser\Factory\Specifications\TypeAliasAssignerSpecification;
|
||||
use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
|
||||
use CuyZ\Valinor\Type\Parser\TypeParser;
|
||||
use CuyZ\Valinor\Type\Type;
|
||||
use CuyZ\Valinor\Type\Types\MixedType;
|
||||
use CuyZ\Valinor\Type\Types\UnresolvableType;
|
||||
use CuyZ\Valinor\Utility\Reflection\Reflection;
|
||||
use ReflectionProperty;
|
||||
|
||||
use function trim;
|
||||
|
||||
final class ReflectionPropertyDefinitionBuilder
|
||||
{
|
||||
private TypeParserFactory $typeParserFactory;
|
||||
|
||||
private AttributesRepository $attributesRepository;
|
||||
|
||||
public function __construct(TypeParserFactory $typeParserFactory, AttributesRepository $attributesRepository)
|
||||
public function __construct(AttributesRepository $attributesRepository)
|
||||
{
|
||||
$this->typeParserFactory = $typeParserFactory;
|
||||
$this->attributesRepository = $attributesRepository;
|
||||
}
|
||||
|
||||
public function for(ClassSignature $classSignature, ReflectionProperty $reflection): PropertyDefinition
|
||||
public function for(ReflectionProperty $reflection, ReflectionTypeResolver $typeResolver): PropertyDefinition
|
||||
{
|
||||
$name = $reflection->name;
|
||||
$signature = Reflection::signature($reflection);
|
||||
$type = $this->resolveType($classSignature, $reflection);
|
||||
$type = $typeResolver->resolveType($reflection);
|
||||
$hasDefaultValue = $this->hasDefaultValue($reflection);
|
||||
$defaultValue = $this->defaultValue($reflection);
|
||||
$isPublic = $reflection->isPublic();
|
||||
@ -64,82 +48,6 @@ final class ReflectionPropertyDefinitionBuilder
|
||||
);
|
||||
}
|
||||
|
||||
private function resolveType(ClassSignature $signature, ReflectionProperty $reflection): Type
|
||||
{
|
||||
$nativeType = $this->nativeType($reflection);
|
||||
$typeFromDocBlock = $this->typeFromDocBlock($signature, $reflection);
|
||||
|
||||
if (! $nativeType && ! $typeFromDocBlock) {
|
||||
return MixedType::get();
|
||||
}
|
||||
|
||||
if (! $nativeType) {
|
||||
/** @var Type $typeFromDocBlock */
|
||||
return $typeFromDocBlock;
|
||||
}
|
||||
|
||||
if (! $typeFromDocBlock) {
|
||||
return $nativeType;
|
||||
}
|
||||
|
||||
if (! $typeFromDocBlock instanceof UnresolvableType
|
||||
&& ! $nativeType instanceof UnresolvableType
|
||||
&& ! $typeFromDocBlock->matches($nativeType)
|
||||
) {
|
||||
throw new PropertyTypesDoNotMatch($reflection, $typeFromDocBlock, $nativeType);
|
||||
}
|
||||
|
||||
return $typeFromDocBlock;
|
||||
}
|
||||
|
||||
private function typeFromDocBlock(ClassSignature $signature, ReflectionProperty $reflection): ?Type
|
||||
{
|
||||
$type = Reflection::docBlockType($reflection);
|
||||
|
||||
if ($type === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parser = $this->typeParserFactory->get(
|
||||
new ClassContextSpecification($signature->className()),
|
||||
new ClassAliasSpecification($signature->className()),
|
||||
new HandleClassGenericSpecification(),
|
||||
new TypeAliasAssignerSpecification($signature->generics()),
|
||||
);
|
||||
|
||||
return $this->parseType($type, $reflection, $parser);
|
||||
}
|
||||
|
||||
private function nativeType(ReflectionProperty $reflection): ?Type
|
||||
{
|
||||
$reflectionType = $reflection->getType();
|
||||
|
||||
if (! $reflectionType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$type = Reflection::flattenType($reflectionType);
|
||||
$parser = $this->typeParserFactory->get(
|
||||
new ClassContextSpecification($reflection->getDeclaringClass()->name)
|
||||
);
|
||||
|
||||
return $this->parseType($type, $reflection, $parser);
|
||||
}
|
||||
|
||||
private function parseType(string $raw, ReflectionProperty $reflection, TypeParser $parser): Type
|
||||
{
|
||||
try {
|
||||
return $parser->parse($raw);
|
||||
} catch (InvalidType $exception) {
|
||||
$raw = trim($raw);
|
||||
$signature = Reflection::signature($reflection);
|
||||
|
||||
return new UnresolvableType(
|
||||
"The type `$raw` for property `$signature` could not be resolved: {$exception->getMessage()}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function hasDefaultValue(ReflectionProperty $reflection): bool
|
||||
{
|
||||
// @PHP8.0 `$reflection->hasDefaultValue()`
|
||||
|
118
src/Definition/Repository/Reflection/ReflectionTypeResolver.php
Normal file
118
src/Definition/Repository/Reflection/ReflectionTypeResolver.php
Normal file
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Definition\Repository\Reflection;
|
||||
|
||||
use CuyZ\Valinor\Definition\Exception\TypesDoNotMatch;
|
||||
use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
|
||||
use CuyZ\Valinor\Type\Parser\TypeParser;
|
||||
use CuyZ\Valinor\Type\Type;
|
||||
use CuyZ\Valinor\Type\Types\MixedType;
|
||||
use CuyZ\Valinor\Type\Types\UnresolvableType;
|
||||
use CuyZ\Valinor\Utility\Reflection\Reflection;
|
||||
use ReflectionFunctionAbstract;
|
||||
use ReflectionParameter;
|
||||
use ReflectionProperty;
|
||||
use Reflector;
|
||||
|
||||
final class ReflectionTypeResolver
|
||||
{
|
||||
private TypeParser $nativeParser;
|
||||
|
||||
private TypeParser $advancedParser;
|
||||
|
||||
public function __construct(TypeParser $nativeParser, TypeParser $advancedParser)
|
||||
{
|
||||
$this->nativeParser = $nativeParser;
|
||||
$this->advancedParser = $advancedParser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ReflectionProperty|ReflectionParameter|ReflectionFunctionAbstract $reflection
|
||||
*/
|
||||
public function resolveType(Reflector $reflection): Type
|
||||
{
|
||||
$nativeType = $this->nativeType($reflection);
|
||||
$typeFromDocBlock = $this->typeFromDocBlock($reflection);
|
||||
|
||||
if (! $nativeType && ! $typeFromDocBlock) {
|
||||
return MixedType::get();
|
||||
}
|
||||
|
||||
if (! $nativeType) {
|
||||
/** @var Type $typeFromDocBlock */
|
||||
return $typeFromDocBlock;
|
||||
}
|
||||
|
||||
if (! $typeFromDocBlock) {
|
||||
return $nativeType;
|
||||
}
|
||||
|
||||
if (! $typeFromDocBlock instanceof UnresolvableType
|
||||
&& ! $nativeType instanceof UnresolvableType
|
||||
&& ! $typeFromDocBlock->matches($nativeType)
|
||||
) {
|
||||
throw new TypesDoNotMatch($reflection, $typeFromDocBlock, $nativeType);
|
||||
}
|
||||
|
||||
return $typeFromDocBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ReflectionProperty|ReflectionParameter|ReflectionFunctionAbstract $reflection
|
||||
*/
|
||||
private function typeFromDocBlock(Reflector $reflection): ?Type
|
||||
{
|
||||
$type = $reflection instanceof ReflectionFunctionAbstract
|
||||
? Reflection::docBlockReturnType($reflection)
|
||||
: Reflection::docBlockType($reflection);
|
||||
|
||||
if ($type === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->parseType($type, $reflection, $this->advancedParser);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ReflectionProperty|ReflectionParameter|ReflectionFunctionAbstract $reflection
|
||||
*/
|
||||
private function nativeType(Reflector $reflection): ?Type
|
||||
{
|
||||
$reflectionType = $reflection instanceof ReflectionFunctionAbstract
|
||||
? $reflection->getReturnType()
|
||||
: $reflection->getType();
|
||||
|
||||
if (! $reflectionType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$type = Reflection::flattenType($reflectionType);
|
||||
|
||||
return $this->parseType($type, $reflection, $this->nativeParser);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ReflectionProperty|ReflectionParameter|ReflectionFunctionAbstract $reflection
|
||||
*/
|
||||
private function parseType(string $raw, Reflector $reflection, TypeParser $parser): Type
|
||||
{
|
||||
try {
|
||||
return $parser->parse($raw);
|
||||
} catch (InvalidType $exception) {
|
||||
$raw = trim($raw);
|
||||
$signature = Reflection::signature($reflection);
|
||||
|
||||
if ($reflection instanceof ReflectionProperty) {
|
||||
$message = "The type `$raw` for property `$signature` could not be resolved: {$exception->getMessage()}";
|
||||
} elseif ($reflection instanceof ReflectionParameter) {
|
||||
$message = "The type `$raw` for parameter `$signature` could not be resolved: {$exception->getMessage()}";
|
||||
} else {
|
||||
$message = "The type `$raw` for return type of method `$signature` could not be resolved: {$exception->getMessage()}";
|
||||
}
|
||||
|
||||
return new UnresolvableType($message);
|
||||
}
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@ final class BasicTemplateParser implements TemplateParser
|
||||
{
|
||||
$templates = [];
|
||||
|
||||
preg_match_all('/@(phpstan-|psalm-)?template\s+(\w+)(\s+of\s+([\w\s|&<>\'",-\[\]\\\\]+))?/', $source, $raw);
|
||||
preg_match_all("/@(phpstan-|psalm-)?template\s+(\w+)(\s+of\s+([\w\s?|&<>'\",-:\\\\\[\]{}]+))?/", $source, $raw);
|
||||
|
||||
/** @var string[] $list */
|
||||
$list = $raw[2];
|
||||
|
@ -92,18 +92,13 @@ final class Reflection
|
||||
public static function docBlockType(Reflector $reflection): ?string
|
||||
{
|
||||
if ($reflection instanceof ReflectionProperty) {
|
||||
$docComment = $reflection->getDocComment() ?: '';
|
||||
$regex = '@var\s+([\w\s?|&<>\'",-\[\]{}:\\\\]+)';
|
||||
$docComment = self::sanitizeDocComment($reflection);
|
||||
$regex = "@var\s+([\w\s?|&<>'\",-:\\\\\[\]{}]+)";
|
||||
} else {
|
||||
$docComment = $reflection->getDeclaringFunction()->getDocComment() ?: '';
|
||||
$regex = "@param\s+([\w\s?|&<>'\",-\[\]{}:\\\\]+)\s+\\$$reflection->name(\s+|$)";
|
||||
$docComment = self::sanitizeDocComment($reflection->getDeclaringFunction());
|
||||
$regex = "@param\s+([\w\s?|&<>'\",-:\\\\\[\]{}]+)\s+\\$$reflection->name(\W+|$)";
|
||||
}
|
||||
|
||||
/** @var string $docComment */
|
||||
$docComment = preg_replace('#^\s*/\*\*([^/]+)/\s*$#', '$1', $docComment);
|
||||
$docComment = preg_replace('/\s*\*([^\s]*)/', '$1', $docComment);
|
||||
|
||||
/** @var string $docComment */
|
||||
if (! preg_match("/$regex/", $docComment, $matches)) {
|
||||
return null;
|
||||
}
|
||||
@ -113,9 +108,9 @@ final class Reflection
|
||||
|
||||
public static function docBlockReturnType(ReflectionFunctionAbstract $reflection): ?string
|
||||
{
|
||||
$docComment = $reflection->getDocComment() ?: '';
|
||||
$docComment = self::sanitizeDocComment($reflection);
|
||||
|
||||
if (! preg_match('/@return\s+([\w\s?|&<>\'",-\[\]{}:\\\\]+)/', $docComment, $matches)) {
|
||||
if (! preg_match("/@return\s+([\w\s?|&<>'\",-:\\\\\[\]{}]+)(\W*|$)/", $docComment, $matches)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -142,4 +137,14 @@ final class Reflection
|
||||
|
||||
return new ReflectionMethod($callable[0], $callable[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ReflectionClass<object>|ReflectionProperty|ReflectionFunctionAbstract $reflection
|
||||
*/
|
||||
private static function sanitizeDocComment(Reflector $reflection): string
|
||||
{
|
||||
$docComment = preg_replace('#^\s*/\*\*([^/]+)/\s*$#', '$1', $reflection->getDocComment() ?: '');
|
||||
|
||||
return preg_replace('/\s*\*\s*(\S*)/', '$1', $docComment); // @phpstan-ignore-line
|
||||
}
|
||||
}
|
||||
|
@ -7,9 +7,7 @@ namespace CuyZ\Valinor\Tests\Unit\Definition\Repository\Reflection;
|
||||
use CuyZ\Valinor\Definition\ClassSignature;
|
||||
use CuyZ\Valinor\Definition\Exception\InvalidParameterDefaultValue;
|
||||
use CuyZ\Valinor\Definition\Exception\InvalidPropertyDefaultValue;
|
||||
use CuyZ\Valinor\Definition\Exception\MethodReturnTypesDoNotMatch;
|
||||
use CuyZ\Valinor\Definition\Exception\ParameterTypesDoNotMatch;
|
||||
use CuyZ\Valinor\Definition\Exception\PropertyTypesDoNotMatch;
|
||||
use CuyZ\Valinor\Definition\Exception\TypesDoNotMatch;
|
||||
use CuyZ\Valinor\Definition\Repository\Reflection\ReflectionClassDefinitionRepository;
|
||||
use CuyZ\Valinor\Tests\Fake\Definition\Repository\FakeAttributesRepository;
|
||||
use CuyZ\Valinor\Tests\Fake\Type\Parser\Factory\FakeTypeParserFactory;
|
||||
@ -187,8 +185,8 @@ final class ReflectionClassDefinitionRepositoryTest extends TestCase
|
||||
public bool $propertyWithNotMatchingTypes;
|
||||
});
|
||||
|
||||
$this->expectException(PropertyTypesDoNotMatch::class);
|
||||
$this->expectExceptionCode(1617218939);
|
||||
$this->expectException(TypesDoNotMatch::class);
|
||||
$this->expectExceptionCode(1638471381);
|
||||
$this->expectExceptionMessage("Types for property `$class::\$propertyWithNotMatchingTypes` do not match: `string` (docblock) does not accept `bool` (native).");
|
||||
|
||||
$this->repository->for(new ClassSignature($class));
|
||||
@ -247,8 +245,8 @@ final class ReflectionClassDefinitionRepositoryTest extends TestCase
|
||||
}
|
||||
});
|
||||
|
||||
$this->expectException(ParameterTypesDoNotMatch::class);
|
||||
$this->expectExceptionCode(1617220595);
|
||||
$this->expectException(TypesDoNotMatch::class);
|
||||
$this->expectExceptionCode(1638471381);
|
||||
$this->expectExceptionMessage("Types for parameter `$class::publicMethod(\$parameterWithNotMatchingTypes)` do not match: `string` (docblock) does not accept `bool` (native).");
|
||||
|
||||
$this->repository->for(new ClassSignature($class));
|
||||
@ -267,8 +265,8 @@ final class ReflectionClassDefinitionRepositoryTest extends TestCase
|
||||
}
|
||||
});
|
||||
|
||||
$this->expectException(MethodReturnTypesDoNotMatch::class);
|
||||
$this->expectExceptionCode(1634048916);
|
||||
$this->expectException(TypesDoNotMatch::class);
|
||||
$this->expectExceptionCode(1638471381);
|
||||
$this->expectExceptionMessage("Return types for method `$class::publicMethod()` do not match: `bool` (docblock) does not accept `string` (native).");
|
||||
|
||||
$this->repository->for(new ClassSignature($class));
|
||||
|
Loading…
x
Reference in New Issue
Block a user