hp$0   A
last analyzed

Complexity

Total Complexity 5

Size/Duplication

Total Lines 30
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 30
ccs 8
cts 8
cp 1
rs 10
c 1
b 0
f 0
wmc 5
1
<?php
2
3
namespace Bdf\Form\Attribute\Child;
4
5
use Attribute;
6
use Bdf\Form\Attribute\AttributeForm;
7
use Bdf\Form\Attribute\ChildBuilderAttributeInterface;
8
use Bdf\Form\Attribute\Element\CallbackTransformer;
9
use Bdf\Form\Attribute\Processor\CodeGenerator\AttributesProcessorGenerator;
10
use Bdf\Form\Attribute\Processor\CodeGenerator\TransformerClassGenerator;
11
use Bdf\Form\Attribute\Processor\GenerateConfiguratorStrategy;
12
use Bdf\Form\Child\ChildBuilderInterface;
13
use Bdf\Form\ElementInterface;
14
use Bdf\Form\Transformer\TransformerInterface;
15
use Nette\PhpGenerator\ClassType;
16
use Nette\PhpGenerator\Literal;
17
use Nette\PhpGenerator\Method;
18
use Nette\PhpGenerator\PsrPrinter;
19
20
/**
21
 * Add a model transformer on the child element, by using method
22
 *
23
 * Transformation to entity and to input can be separated in two different method.
24
 * Those methods take the value and the input element as parameters, and should return the transformed value
25
 * If dedicated methods are not used, but the unified one, the third parameter is provided :
26
 * - on true the transformation is to the entity
27
 * - on false the transformation is to the input
28
 *
29
 * This attribute is equivalent to call :
30
 * <code>
31
 * // For unified callback
32
 * $builder->string('foo')->modelTransformer([$this, 'myTransformer']);
33
 *
34
 * // When using two methods (toEntity: 'transformFooToEntity', toInput: 'transformFooToInput')
35
 * $builder->string('foo')->modelTransformer(function ($value, ElementInterface $input, bool $toEntity) {
36
 *     return $toEntity ? $this->transformFooToEntity($value, $input) : $this->transformFooToInput($value, $input);
37
 * });
38
 * </code>
39
 *
40
 * Usage:
41
 * <code>
42
 * class MyForm extends AttributeForm
43
 * {
44
 *     #[CallbackModelTransformer(toEntity: 'fooToModel', toInput: 'fooToInput')]
45
 *     private IntegerElement $foo;
46
 *
47
 *     // With unified transformer (same as above)
48
 *     #[CallbackModelTransformer('barTransformer')]
49
 *     private IntegerElement $bar;
50
 *
51
 *     public function fooToModel(int $value, IntegerElement $input): string
52
 *     {
53
 *         return dechex($value);
54
 *     }
55
 *
56
 *     public function fooToInput(string $value, IntegerElement $input): int
57
 *     {
58
 *         return hexdec($value);
59
 *     }
60
 *
61
 *     public function barTransformer($value, IntegerElement $input, bool $toEntity)
62
 *     {
63
 *         return $toEntity ? dechex($value) : hexdec($value);
64
 *     }
65
 * }
66
 * </code>
67
 *
68
 * @implements ChildBuilderAttributeInterface<\Bdf\Form\ElementBuilderInterface>
69
 *
70
 * @see ChildBuilderInterface::modelTransformer() The called method
71
 * @see ModelTransformer For use a transformer class as model transformer
72
 * @see CallbackTransformer For use transformer in same way, but for http transformer intead of model one
73
 */
74
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
75
final class CallbackModelTransformer implements ChildBuilderAttributeInterface
76
{
77 5
    public function __construct(
78
        /**
79
         * Method name use to define the unified transformer method
80
         * If defined, the other parameters will be ignored
81
         *
82
         * @var literal-string|null
83
         * @readonly
84
         */
85
        private ?string $callback = null,
86
        /**
87
         * Method name use to define the transformation process from input value to the entity
88
         *
89
         * @var literal-string|null
90
         * @readonly
91
         */
92
        private ?string $toEntity = null,
93
        /**
94
         * Method name use to define the transformation process from entity value to input
95
         *
96
         * @var literal-string|null
97
         * @readonly
98
         */
99
        private ?string $toInput = null,
100
    ) {
101 5
    }
102
103
    /**
104
     * {@inheritdoc}
105
     */
106 2
    public function applyOnChildBuilder(AttributeForm $form, ChildBuilderInterface $builder): void
107
    {
108 2
        if ($this->callback) {
109 1
            $builder->modelTransformer([$form, $this->callback]);
110 1
            return;
111
        }
112
113 2
        $builder->modelTransformer(new class ($form, $this->toInput, $this->toEntity) implements TransformerInterface {
114
            public function __construct(
115
                private AttributeForm $form,
116
                private ?string $toInput,
117
                private ?string $toEntity,
118
            ) {
119 2
            }
120
121
            /**
122
             * {@inheritdoc}
123
             */
124
            public function transformToHttp($value, ElementInterface $input)
125
            {
126 2
                if (!$this->toInput) {
127 1
                    return $value;
128
                }
129
130 2
                return $this->form->{$this->toInput}($value, $input);
131
            }
132
133
            /**
134
             * {@inheritdoc}
135
             */
136
            public function transformFromHttp($value, ElementInterface $input)
137
            {
138 2
                if (!$this->toEntity) {
139 1
                    return $value;
140
                }
141
142 2
                return $this->form->{$this->toEntity}($value, $input);
143
            }
144 2
        });
145
    }
146
147
    /**
148
     * {@inheritdoc}
149
     */
150 3
    public function generateCodeForChildBuilder(string $name, AttributesProcessorGenerator $generator, AttributeForm $form): void
151
    {
152 3
        if ($this->callback) {
153 1
            $generator->line('$?->modelTransformer([$form, ?]);', [$name, $this->callback]);
154 1
            return;
155
        }
156
157 3
        $transformer = new TransformerClassGenerator($generator->namespace(), $generator->printer());
158
159 3
        $transformer->withPromotedProperty('form')->setPrivate();
160
161 3
        if ($this->toInput) {
162 3
            $transformer->toHttp()->setBody('return $this->form->?($value, $input);', [$this->toInput]);
163
        } else {
164 2
            $transformer->toHttp()->setBody('return $value;');
165
        }
166
167 3
        if ($this->toEntity) {
168 3
            $transformer->fromHttp()->setBody('return $this->form->?($value, $input);', [$this->toEntity]);
169
        } else {
170 2
            $transformer->fromHttp()->setBody('return $value;');
171
        }
172
173 3
        $generator->line(
174 3
            '$?->modelTransformer(new class ($form) ?);',
175 3
            [$name, new Literal($transformer->generateClass())]
176 3
        );
177
    }
178
}
179