3 declare(strict_types=1);
5 namespace CuyZ\Valinor\Mapper\Object\Factory;
7 use CuyZ\Valinor\Definition\ClassDefinition;
8 use CuyZ\Valinor\Definition\FunctionObject;
9 use CuyZ\Valinor\Definition\FunctionsContainer;
10 use CuyZ\Valinor\Mapper\Object\DynamicConstructor;
11 use CuyZ\Valinor\Mapper\Object\Exception\CannotInstantiateObject;
12 use CuyZ\Valinor\Mapper\Object\Exception\InvalidConstructorClassTypeParameter;
13 use CuyZ\Valinor\Mapper\Object\Exception\InvalidConstructorReturnType;
14 use CuyZ\Valinor\Mapper\Object\Exception\MissingConstructorClassTypeParameter;
15 use CuyZ\Valinor\Mapper\Object\FunctionObjectBuilder;
16 use CuyZ\Valinor\Mapper\Object\MethodObjectBuilder;
17 use CuyZ\Valinor\Mapper\Object\NativeConstructorObjectBuilder;
18 use CuyZ\Valinor\Mapper\Object\NativeEnumObjectBuilder;
19 use CuyZ\Valinor\Mapper\Object\ObjectBuilder;
20 use CuyZ\Valinor\Type\ClassType;
21 use CuyZ\Valinor\Type\ObjectType;
22 use CuyZ\Valinor\Type\Types\ClassStringType;
23 use CuyZ\Valinor\Type\Types\EnumType;
24 use CuyZ\Valinor\Type\Types\NativeStringType;
26 use function array_key_exists;
31 final class ConstructorObjectBuilderFactory implements ObjectBuilderFactory
33 /** @var list<FunctionObject> */
34 private array $filteredConstructors;
36 public function __construct(
37 private ObjectBuilderFactory $delegate,
38 /** @var array<class-string, null> */
39 private array $nativeConstructors,
40 private FunctionsContainer $constructors
44 public function for(ClassDefinition $class): array
46 $builders = $this->builders($class);
48 if (count($builders) === 0) {
49 if ($class->methods()->hasConstructor()) {
50 throw new CannotInstantiateObject($class);
53 return $this->delegate->for($class);
60 * @return list<ObjectBuilder>
62 private function builders(ClassDefinition $class): array
64 $className = $class->name();
65 $classType = $class->type();
66 $methods = $class->methods();
70 foreach ($this->filteredConstructors() as $constructor) {
71 if (! $this->constructorMatches($constructor, $classType)) {
75 $definition = $constructor->definition();
76 $functionClass = $definition->class();
78 if ($functionClass && $definition->isStatic() && ! $definition->isClosure()) {
79 $scopedClass = is_a($className, $functionClass, true) ? $className : $functionClass;
81 $builders[] = new MethodObjectBuilder($scopedClass, $definition->name(), $definition->parameters());
83 $builders[] = new FunctionObjectBuilder($constructor, $classType);
87 if (! array_key_exists($className, $this->nativeConstructors) && count($builders) > 0) {
91 if ($classType instanceof EnumType) {
92 $builders[] = new NativeEnumObjectBuilder($classType);
93 } elseif ($methods->hasConstructor() && $methods->constructor()->isPublic()) {
94 $builders[] = new NativeConstructorObjectBuilder($class);
100 private function constructorMatches(FunctionObject $function, ClassType $classType): bool
102 $definition = $function->definition();
103 $parameters = $definition->parameters();
104 $returnType = $definition->returnType();
106 if (! $classType->matches($returnType)) {
110 if (! $definition->attributes()->has(DynamicConstructor::class)) {
114 if (count($parameters) === 0) {
115 throw new MissingConstructorClassTypeParameter($definition);
118 $parameterType = $parameters->at(0)->type();
120 if ($parameterType instanceof NativeStringType) {
121 $parameterType = ClassStringType::get();
124 if (! $parameterType instanceof ClassStringType) {
125 throw new InvalidConstructorClassTypeParameter($definition, $parameterType);
128 $subType = $parameterType->subType();
131 return $classType->matches($subType);
138 * @return list<FunctionObject>
140 private function filteredConstructors(): array
142 if (! isset($this->filteredConstructors)) {
143 $this->filteredConstructors = [];
145 foreach ($this->constructors as $constructor) {
146 $function = $constructor->definition();
148 if (enum_exists($function->class() ?? '') && in_array($function->name(), ['from', 'tryFrom'], true)) {
152 if (! $function->returnType() instanceof ObjectType) {
153 throw new InvalidConstructorReturnType($function);
156 $this->filteredConstructors[] = $constructor;
160 return $this->filteredConstructors;