ad5b629f46469e34c0b69575edf014d512e2f442
[GitHub/WoltLab/WCF.git] /
1 <?php
2
3 declare(strict_types=1);
4
5 namespace CuyZ\Valinor\Normalizer\Transformer;
6
7 use CuyZ\Valinor\Definition\FunctionDefinition;
8 use CuyZ\Valinor\Definition\Repository\FunctionDefinitionRepository;
9 use CuyZ\Valinor\Normalizer\Exception\TransformerHasInvalidCallableParameter;
10 use CuyZ\Valinor\Normalizer\Exception\TransformerHasNoParameter;
11 use CuyZ\Valinor\Normalizer\Exception\TransformerHasTooManyParameters;
12 use CuyZ\Valinor\Type\Types\CallableType;
13
14 use function array_shift;
15 use function call_user_func;
16 use function method_exists;
17
18 /** @internal */
19 final class ValueTransformersHandler
20 {
21 /** @var array<string, true> */
22 private array $transformerCheck = [];
23
24 public function __construct(
25 private FunctionDefinitionRepository $functionDefinitionRepository,
26 ) {}
27
28 /**
29 * @param array<object> $attributes
30 * @param list<callable> $transformers
31 * @return array<mixed>|scalar|null
32 */
33 public function transform(mixed $value, array $attributes, array $transformers, callable $defaultTransformer): mixed
34 {
35 return call_user_func(
36 $this->next($transformers, $value, $attributes, $defaultTransformer),
37 );
38 }
39
40 /**
41 * @param list<callable> $transformers
42 * @param list<object> $attributes
43 */
44 private function next(array $transformers, mixed $value, array $attributes, callable $defaultTransformer): callable
45 {
46 if ($attributes !== []) {
47 return $this->nextAttribute(
48 $value,
49 $attributes,
50 fn () => call_user_func($this->next($transformers, $value, [], $defaultTransformer)),
51 );
52 }
53
54 $transformer = array_shift($transformers);
55
56 if ($transformer === null) {
57 return fn () => $defaultTransformer($value);
58 }
59
60 $function = $this->functionDefinitionRepository->for($transformer);
61
62 $this->checkTransformer($function);
63
64 if (! $function->parameters()->at(0)->type()->accepts($value)) {
65 return $this->next($transformers, $value, [], $defaultTransformer);
66 }
67
68 return fn () => $transformer($value, fn () => call_user_func($this->next($transformers, $value, [], $defaultTransformer)));
69 }
70
71 /**
72 * @param array<object> $attributes
73 */
74 private function nextAttribute(mixed $value, array $attributes, callable $next): callable
75 {
76 $attribute = array_shift($attributes);
77
78 if ($attribute === null) {
79 return $next;
80 }
81
82 if (! method_exists($attribute, 'normalize')) {
83 return $this->nextAttribute($value, $attributes, $next);
84 }
85
86 // PHP8.1 First-class callable syntax
87 $function = $this->functionDefinitionRepository->for([$attribute, 'normalize']);
88
89 $this->checkTransformer($function);
90
91 if (! $function->parameters()->at(0)->type()->accepts($value)) {
92 return $this->nextAttribute($value, $attributes, $next);
93 }
94
95 return fn () => $attribute->normalize($value, fn () => call_user_func($this->nextAttribute($value, $attributes, $next)));
96 }
97
98 private function checkTransformer(FunctionDefinition $function): void
99 {
100 if (isset($this->transformerCheck[$function->signature()])) {
101 return;
102 }
103
104 // @infection-ignore-all
105 $this->transformerCheck[$function->signature()] = true;
106
107 $parameters = $function->parameters();
108
109 if ($parameters->count() === 0) {
110 throw new TransformerHasNoParameter($function);
111 }
112
113 if ($parameters->count() > 2) {
114 throw new TransformerHasTooManyParameters($function);
115 }
116
117 if ($parameters->count() > 1 && ! $parameters->at(1)->type() instanceof CallableType) {
118 throw new TransformerHasInvalidCallableParameter($function, $parameters->at(1)->type());
119 }
120 }
121 }