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
43 public function for(ClassDefinition $class): array
45 $builders = $this->builders($class);
47 if (count($builders) === 0) {
48 if ($class->methods()->hasConstructor()) {
49 throw new CannotInstantiateObject($class);
52 return $this->delegate->for($class);
59 * @return list<ObjectBuilder>
61 private function builders(ClassDefinition $class): array
63 $className = $class->name();
64 $classType = $class->type();
65 $methods = $class->methods();
69 foreach ($this->filteredConstructors() as $constructor) {
70 if (! $this->constructorMatches($constructor, $classType)) {
74 $definition = $constructor->definition();
75 $functionClass = $definition->class();
77 if ($functionClass && $definition->isStatic() && ! $definition->isClosure()) {
78 $scopedClass = is_a($className, $functionClass, true) ? $className : $functionClass;
80 $builders[] = new MethodObjectBuilder($scopedClass, $definition->name(), $definition->parameters());
82 $builders[] = new FunctionObjectBuilder($constructor, $classType);
86 if (! array_key_exists($className, $this->nativeConstructors) && count($builders) > 0) {
90 if ($classType instanceof EnumType) {
91 $builders[] = new NativeEnumObjectBuilder($classType);
92 } elseif ($methods->hasConstructor() && $methods->constructor()->isPublic()) {
93 $builders[] = new NativeConstructorObjectBuilder($class);
99 private function constructorMatches(FunctionObject $function, ClassType $classType): bool
101 $definition = $function->definition();
102 $parameters = $definition->parameters();
103 $returnType = $definition->returnType();
105 if (! $classType->matches($returnType)) {
109 if (! $definition->attributes()->has(DynamicConstructor::class)) {
113 if (count($parameters) === 0) {
114 throw new MissingConstructorClassTypeParameter($definition);
117 $parameterType = $parameters->at(0)->type();
119 if ($parameterType instanceof NativeStringType) {
120 $parameterType = ClassStringType::get();
123 if (! $parameterType instanceof ClassStringType) {
124 throw new InvalidConstructorClassTypeParameter($definition, $parameterType);
127 $subType = $parameterType->subType();
130 return $classType->matches($subType);
137 * @return list<FunctionObject>
139 private function filteredConstructors(): array
141 if (! isset($this->filteredConstructors)) {
142 $this->filteredConstructors = [];
144 foreach ($this->constructors as $constructor) {
145 $function = $constructor->definition();
147 if (enum_exists($function->class() ?? '') && in_array($function->name(), ['from', 'tryFrom'], true)) {
151 if (! $function->returnType() instanceof ObjectType) {
152 throw new InvalidConstructorReturnType($function);
155 $this->filteredConstructors[] = $constructor;
159 return $this->filteredConstructors;