3 declare(strict_types=1);
5 namespace CuyZ\Valinor\Definition\Repository\Reflection;
7 use CuyZ\Valinor\Definition\ClassDefinition;
8 use CuyZ\Valinor\Definition\Exception\ClassTypeAliasesDuplication;
9 use CuyZ\Valinor\Definition\Exception\InvalidTypeAliasImportClass;
10 use CuyZ\Valinor\Definition\Exception\InvalidTypeAliasImportClassType;
11 use CuyZ\Valinor\Definition\Exception\UnknownTypeAliasImport;
12 use CuyZ\Valinor\Definition\MethodDefinition;
13 use CuyZ\Valinor\Definition\Methods;
14 use CuyZ\Valinor\Definition\Properties;
15 use CuyZ\Valinor\Definition\PropertyDefinition;
16 use CuyZ\Valinor\Definition\Repository\AttributesRepository;
17 use CuyZ\Valinor\Definition\Repository\ClassDefinitionRepository;
18 use CuyZ\Valinor\Type\ClassType;
19 use CuyZ\Valinor\Type\GenericType;
20 use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
21 use CuyZ\Valinor\Type\Parser\Factory\Specifications\AliasSpecification;
22 use CuyZ\Valinor\Type\Parser\Factory\Specifications\ClassContextSpecification;
23 use CuyZ\Valinor\Type\Parser\Factory\Specifications\TypeAliasAssignerSpecification;
24 use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
25 use CuyZ\Valinor\Type\Parser\TypeParser;
26 use CuyZ\Valinor\Type\Type;
27 use CuyZ\Valinor\Type\Types\UnresolvableType;
28 use CuyZ\Valinor\Utility\Reflection\Reflection;
31 use ReflectionProperty;
32 use CuyZ\Valinor\Utility\Reflection\DocParser;
34 use function array_filter;
35 use function array_keys;
36 use function array_map;
39 final class ReflectionClassDefinitionRepository implements ClassDefinitionRepository
41 private TypeParserFactory $typeParserFactory;
43 private AttributesRepository $attributesFactory;
45 private ReflectionPropertyDefinitionBuilder $propertyBuilder;
47 private ReflectionMethodDefinitionBuilder $methodBuilder;
49 /** @var array<string, ReflectionTypeResolver> */
50 private array $typeResolver = [];
52 public function __construct(TypeParserFactory $typeParserFactory, AttributesRepository $attributesFactory)
54 $this->typeParserFactory = $typeParserFactory;
55 $this->attributesFactory = $attributesFactory;
56 $this->propertyBuilder = new ReflectionPropertyDefinitionBuilder($attributesFactory);
57 $this->methodBuilder = new ReflectionMethodDefinitionBuilder($attributesFactory);
60 public function for(ClassType $type): ClassDefinition
62 $reflection = Reflection::class($type->className());
64 return new ClassDefinition(
66 $this->attributesFactory->for($reflection),
67 new Properties(...$this->properties($type)),
68 new Methods(...$this->methods($type)),
69 $reflection->isFinal(),
70 $reflection->isAbstract(),
75 * @return list<PropertyDefinition>
77 private function properties(ClassType $type): array
80 function (ReflectionProperty $property) use ($type) {
81 $typeResolver = $this->typeResolver($type, $property->getDeclaringClass());
83 return $this->propertyBuilder->for($property, $typeResolver);
85 Reflection::class($type->className())->getProperties()
90 * @return list<MethodDefinition>
92 private function methods(ClassType $type): array
94 $reflection = Reflection::class($type->className());
95 $methods = $reflection->getMethods();
97 // Because `ReflectionMethod::getMethods()` wont list the constructor if
98 // it comes from a parent class AND is not public, we need to manually
99 // fetch it and add it to the list.
100 if ($reflection->hasMethod('__construct')) {
101 $methods[] = $reflection->getMethod('__construct');
104 return array_map(function (ReflectionMethod $method) use ($type) {
105 $typeResolver = $this->typeResolver($type, $method->getDeclaringClass());
107 return $this->methodBuilder->for($method, $typeResolver);
112 * @param ReflectionClass<object> $target
114 private function typeResolver(ClassType $type, ReflectionClass $target): ReflectionTypeResolver
116 $typeKey = $target->isInterface()
117 ? "{$type->toString()}/{$type->className()}"
118 : "{$type->toString()}/$target->name";
120 if (isset($this->typeResolver[$typeKey])) {
121 return $this->typeResolver[$typeKey];
124 while ($type->className() !== $target->name) {
125 $type = $type->parent();
128 $generics = $type instanceof GenericType ? $type->generics() : [];
129 $localAliases = $this->localTypeAliases($type);
130 $importedAliases = $this->importedTypeAliases($type);
133 $keys = [...array_keys($generics), ...array_keys($localAliases), ...array_keys($importedAliases)];
135 foreach ($keys as $key) {
136 $sameKeys = array_filter($keys, fn ($value) => $value === $key);
138 if (count($sameKeys) > 1) {
139 $duplicates[$key] = null;
143 if (count($duplicates) > 0) {
144 throw new ClassTypeAliasesDuplication($type->className(), ...array_keys($duplicates));
147 $advancedParser = $this->typeParserFactory->get(
148 new ClassContextSpecification($type->className()),
149 new AliasSpecification(Reflection::class($type->className())),
150 new TypeAliasAssignerSpecification($generics + $localAliases + $importedAliases)
153 $nativeParser = $this->typeParserFactory->get(
154 new ClassContextSpecification($type->className())
157 return $this->typeResolver[$typeKey] = new ReflectionTypeResolver($nativeParser, $advancedParser);
161 * @return array<string, Type>
163 private function localTypeAliases(ClassType $type): array
165 $reflection = Reflection::class($type->className());
166 $rawTypes = DocParser::localTypeAliases($reflection);
168 $typeParser = $this->typeParser($type);
172 foreach ($rawTypes as $name => $raw) {
174 $types[$name] = $typeParser->parse($raw);
175 } catch (InvalidType $exception) {
178 $types[$name] = UnresolvableType::forLocalAlias($raw, $name, $type, $exception);
186 * @return array<string, Type>
188 private function importedTypeAliases(ClassType $type): array
190 $reflection = Reflection::class($type->className());
191 $importedTypesRaw = DocParser::importedTypeAliases($reflection);
193 $typeParser = $this->typeParser($type);
197 foreach ($importedTypesRaw as $class => $types) {
199 $classType = $typeParser->parse($class);
200 } catch (InvalidType) {
201 throw new InvalidTypeAliasImportClass($type, $class);
204 if (! $classType instanceof ClassType) {
205 throw new InvalidTypeAliasImportClassType($type, $classType);
208 $localTypes = $this->localTypeAliases($classType);
210 foreach ($types as $importedType) {
211 if (! isset($localTypes[$importedType])) {
212 throw new UnknownTypeAliasImport($type, $classType->className(), $importedType);
215 $importedTypes[$importedType] = $localTypes[$importedType];
219 return $importedTypes;
222 private function typeParser(ClassType $type): TypeParser
225 new ClassContextSpecification($type->className()),
226 new AliasSpecification(Reflection::class($type->className())),
229 if ($type instanceof GenericType) {
230 $specs[] = new TypeAliasAssignerSpecification($type->generics());
233 return $this->typeParserFactory->get(...$specs);