JFIF  x x C         C     "        } !1AQa "q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz        w !1AQ aq"2B #3Rbr{ gilour
<?php declare(strict_types=1); namespace CuyZ\Valinor\Definition\Repository\Reflection; use CuyZ\Valinor\Definition\Attributes; use CuyZ\Valinor\Definition\ClassDefinition; use CuyZ\Valinor\Definition\MethodDefinition; use CuyZ\Valinor\Definition\Methods; use CuyZ\Valinor\Definition\Properties; use CuyZ\Valinor\Definition\PropertyDefinition; use CuyZ\Valinor\Definition\Repository\AttributesRepository; use CuyZ\Valinor\Definition\Repository\ClassDefinitionRepository; use CuyZ\Valinor\Definition\Repository\Reflection\TypeResolver\ClassImportedTypeAliasResolver; use CuyZ\Valinor\Definition\Repository\Reflection\TypeResolver\ClassLocalTypeAliasResolver; use CuyZ\Valinor\Definition\Repository\Reflection\TypeResolver\ClassParentTypeResolver; use CuyZ\Valinor\Definition\Repository\Reflection\TypeResolver\ClassGenericResolver; use CuyZ\Valinor\Definition\Repository\Reflection\TypeResolver\ReflectionTypeResolver; use CuyZ\Valinor\Mapper\Object\Constructor; use CuyZ\Valinor\Type\ObjectType; use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory; use CuyZ\Valinor\Type\Parser\UnresolvableTypeFinderParser; use CuyZ\Valinor\Type\Parser\VacantTypeAssignerParser; use CuyZ\Valinor\Type\Type; use CuyZ\Valinor\Type\Types\InterfaceType; use CuyZ\Valinor\Type\Types\NativeClassType; use CuyZ\Valinor\Type\Types\UnresolvableType; use CuyZ\Valinor\Utility\Reflection\Reflection; use ReflectionMethod; use ReflectionProperty; use function array_filter; use function array_keys; use function array_map; /** @internal */ final class ReflectionClassDefinitionRepository implements ClassDefinitionRepository { private TypeParserFactory $typeParserFactory; private AttributesRepository $attributesRepository; private ReflectionPropertyDefinitionBuilder $propertyBuilder; private ReflectionMethodDefinitionBuilder $methodBuilder; private ClassParentTypeResolver $parentTypeResolver; private ClassGenericResolver $genericResolver; private ClassLocalTypeAliasResolver $localTypeAliasResolver; private ClassImportedTypeAliasResolver $importedTypeAliasResolver; /** * @param list<class-string> $allowedAttributes */ public function __construct( TypeParserFactory $typeParserFactory, array $allowedAttributes, ) { $this->typeParserFactory = $typeParserFactory; $this->attributesRepository = new ReflectionAttributesRepository($this, $allowedAttributes); $this->propertyBuilder = new ReflectionPropertyDefinitionBuilder($this->attributesRepository); $this->methodBuilder = new ReflectionMethodDefinitionBuilder($this->attributesRepository); $this->parentTypeResolver = new ClassParentTypeResolver($this->typeParserFactory); $this->genericResolver = new ClassGenericResolver($this->typeParserFactory); $this->localTypeAliasResolver = new ClassLocalTypeAliasResolver($this->typeParserFactory); $this->importedTypeAliasResolver = new ClassImportedTypeAliasResolver($this->typeParserFactory); } public function for(ObjectType $type): ClassDefinition { $reflection = Reflection::class($type->className()); $vacantTypes = $this->vacantTypes($type); $nativeTypeParser = $this->typeParserFactory->buildNativeTypeParserForClass($type->className()); $advancedTypeParser = $this->typeParserFactory->buildAdvancedTypeParserForClass($type->className()); $advancedTypeParser = new VacantTypeAssignerParser($advancedTypeParser, $vacantTypes); $advancedTypeParser = new UnresolvableTypeFinderParser($advancedTypeParser); $typeResolver = new ReflectionTypeResolver($nativeTypeParser, $advancedTypeParser); return new ClassDefinition( $reflection->name, $type, new Attributes(...$this->attributesRepository->for($reflection)), new Properties(...$this->properties($type, $typeResolver)), new Methods(...$this->methods($type, $typeResolver)), $reflection->isFinal(), $reflection->isAbstract(), ); } /** * @return array<non-empty-string, Type> */ private function vacantTypes(ObjectType $type): array { $generics = []; if ($type instanceof NativeClassType || $type instanceof InterfaceType) { $generics = $this->genericResolver->resolveGenerics($type); } $localTypes = $this->localTypeAliasResolver->resolveLocalTypeAliases($type); $importedTypes = $this->importedTypeAliasResolver->resolveImportedTypeAliases($type); $vacantTypes = [...$generics, ...$localTypes, ...$importedTypes]; $keys = [...array_keys($generics), ...array_keys($localTypes), ...array_keys($importedTypes)]; // PHP8.5 use pipes $aliasCollision = array_filter( array_count_values($keys), fn (int $count) => $count > 1 ); foreach ($aliasCollision as $alias => $numberOfCollisions) { /** @var non-empty-string $alias */ $vacantTypes[$alias] = UnresolvableType::forClassTypeAliasesCollision($alias, $numberOfCollisions); } return $vacantTypes; } /** * @return list<PropertyDefinition> */ private function properties(ObjectType $type, ReflectionTypeResolver $typeResolver): array { $reflection = Reflection::class($type->className()); $properties = []; foreach ($reflection->getProperties() as $property) { $declaringClass = $property->getDeclaringClass(); if ($declaringClass->name === $type->className()) { $properties[$property->name] = $this->propertyBuilder->for($property, $typeResolver); } else { $parentClass = $this->parentTypeResolver->resolveParentTypeFor($type); $properties[$property->name] = $this->for($parentClass)->properties->get($property->name); } } // Properties will be sorted by inheritance order, from parent to child. $sortedProperties = []; while ($reflection) { $currentProperties = array_map( fn (ReflectionProperty $property) => $properties[$property->name], array_filter( $reflection->getProperties(), fn (ReflectionProperty $property) => isset($properties[$property->name]), ), ); $sortedProperties = [...$currentProperties, ...$sortedProperties]; $reflection = $reflection->getParentClass(); } return $sortedProperties; } /** * @return array<MethodDefinition> */ private function methods(ObjectType $type, ReflectionTypeResolver $typeResolver): array { $reflection = Reflection::class($type->className()); $methods = array_filter($reflection->getMethods(), $this->shouldMethodBeIncluded(...)); // Because `ReflectionMethod::getMethods()` wont list the constructor if // it comes from a parent class AND is not public, we need to manually // fetch it and add it to the list. if ($reflection->hasMethod('__construct')) { $methods[] = $reflection->getMethod('__construct'); } return array_map(function (ReflectionMethod $method) use ($type, $typeResolver) { $declaringClass = $method->getDeclaringClass(); if ($declaringClass->name === $type->className()) { return $this->methodBuilder->for($method, $typeResolver); } $parentClass = $this->parentTypeResolver->resolveParentTypeFor($type); return $this->for($parentClass)->methods->get($method->name); }, $methods); } private function shouldMethodBeIncluded(ReflectionMethod $method): bool { return $method->name === 'map' || $method->name === 'normalize' || $method->name === 'normalizeKey' || $method->getAttributes(Constructor::class) !== []; } }