Issues (116)

src/Aggregate/ArrayElementBuilder.php (1 issue)

1
<?php
2
3
namespace Bdf\Form\Aggregate;
4
5
use Bdf\Form\Choice\ArrayChoice;
6
use Bdf\Form\Choice\ChoiceBuilderTrait;
7
use Bdf\Form\Choice\ChoiceInterface;
8
use Bdf\Form\Choice\LazyChoice;
9
use Bdf\Form\ElementBuilderInterface;
10
use Bdf\Form\ElementInterface;
11
use Bdf\Form\Leaf\BooleanElement;
12
use Bdf\Form\Leaf\Date\DateTimeElement;
13
use Bdf\Form\Leaf\FloatElement;
14
use Bdf\Form\Leaf\IntegerElement;
15
use Bdf\Form\Leaf\StringElement;
16
use Bdf\Form\Phone\PhoneElement;
17
use Bdf\Form\Registry\Registry;
18
use Bdf\Form\Registry\RegistryInterface;
19
use Bdf\Form\Util\MagicCallForwarding;
20
use Bdf\Form\Util\TransformerBuilderTrait;
21
use Bdf\Form\Util\ValidatorBuilderTrait;
22
use Symfony\Component\Validator\Constraint;
23
use Symfony\Component\Validator\Constraints\Choice as ChoiceConstraint;
24
use Symfony\Component\Validator\Constraints\Count;
25
use Symfony\Component\Validator\Constraints\NotBlank;
26
27
/**
28
 * Builder for the array element
29
 *
30
 * <code>
31
 * $builder->array('names')->string()
32
 *     ->length(['min' => 3]) // Add "length" constraint to inner string
33
 *     ->count(['min' => 1, 'max' => 6]) // Add count constraint
34
 *     ->satisfyArray(new MyArrayConstraint()) // Add a constraint for the array
35
 * ;
36
 * </code>
37
 *
38
 * @see ArrayElement
39
 * @see FormBuilderInterface::array()
40
 *
41
 * @template T
42
 * @implements ElementBuilderInterface<ArrayElement<T>>
43
 */
44
class ArrayElementBuilder implements ElementBuilderInterface
45
{
46
    use MagicCallForwarding;
47
48
    use ChoiceBuilderTrait {
49
        ChoiceBuilderTrait::choices as protected baseChoices;
50
    }
51
52
    use TransformerBuilderTrait {
53
        transformer as arrayTransformer;
54
    }
55
56
    use ValidatorBuilderTrait {
57
        ValidatorBuilderTrait::satisfy as arrayConstraint;
58
    }
59
60
    /**
61
     * @var RegistryInterface
62
     */
63
    private $registry;
64
65
    /**
66
     * @var ElementBuilderInterface<ElementInterface<T>>|null
67
     */
68
    private $element;
69
70
    /**
71
     * @var mixed
72
     */
73
    private $value;
74
75
76
    /**
77
     * ArrayBuilder constructor.
78
     *
79
     * @param RegistryInterface|null $registry
80
     */
81 43
    public function __construct(RegistryInterface $registry = null)
82
    {
83 43
        $this->registry = $registry ?? new Registry();
84 43
    }
85
86
    /**
87
     * {@inheritdoc}
88
     *
89
     * Define a constraint on the inner element
90
     */
91 4
    public function satisfy($constraint, $options = null, bool $append = true)
92
    {
93 4
        $this->getElementBuilder()->satisfy($constraint, $options, $append);
94
95 4
        return $this;
96
    }
97
98
    /**
99
     * {@inheritdoc}
100
     *
101
     * Define a transformer on the inner element
102
     */
103 4
    public function transformer($transformer, bool $append = true)
104
    {
105 4
        $this->getElementBuilder()->transformer($transformer, $append);
106
107 4
        return $this;
108
    }
109
110
    /**
111
     * {@inheritdoc}
112
     */
113 1
    public function value($value)
114
    {
115 1
        $this->value = $value;
116
117 1
        return $this;
118
    }
119
120
    /**
121
     * Define the inner element
122
     *
123
     * <code>
124
     * $builder->array('phones')->element(PhoneElement::class, function (PhoneElementBuilder $builder) {
125
     *     $builder->regionInput('../../address/country');
126
     * });
127
     * </code>
128
     *
129
     * @param class-string<E> $element The element class name
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<E> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<E>.
Loading history...
130
     * @param callable(EB):void|null $configurator Callback for configure the inner element builder. Takes as parameter the element builder
131
     *
132
     * @template RT
133
     * @template E as ElementInterface<RT>
134
     * @template EB as ElementBuilderInterface<E>
135
     *
136
     * @return ArrayElementBuilder<RT>
137
     */
138 41
    public function element(string $element, ?callable $configurator = null): ArrayElementBuilder
139
    {
140
        /** @var ArrayElementBuilder<RT> $this */
141
        // @todo exception if already defined ?
142 41
        $this->element = $this->registry->elementBuilder($element);
143
144 41
        if ($configurator) {
145
            /** @psalm-suppress InvalidArgument */
146 11
            $configurator($this->element);
147
        }
148
149 41
        return $this;
150
    }
151
152
    /**
153
     * {@inheritdoc}
154
     *
155
     * @return ElementBuilderInterface<ElementInterface<T>>
156
     * @psalm-suppress InvalidNullableReturnType
157
     */
158 41
    public function getElementBuilder(): ElementBuilderInterface
159
    {
160 41
        if (!$this->element) {
161 27
            $this->element(StringElement::class);
162
        }
163
164
        /** @psalm-suppress NullableReturnStatement */
165 41
        return $this->element;
166
    }
167
168
    /**
169
     * Define as array of string
170
     *
171
     * <code>
172
     * $builder->array('names')->string(function (StringElementBuilder $builder) {
173
     *     $builder->length(['min' => 3, 'max' => 32])->regex('/[a-z -]+/i');
174
     * });
175
     * </code>
176
     *
177
     * @param callable(ElementBuilderInterface<StringElement>):void|null $configurator Callback for configure the inner element builder
178
     *
179
     * @return ArrayElementBuilder<string>
180
     */
181 1
    public function string(?callable $configurator = null): ArrayElementBuilder
182
    {
183 1
        return $this->element(StringElement::class, $configurator);
184
    }
185
186
    /**
187
     * Define as array of integer
188
     *
189
     * <code>
190
     * $builder->array('ids')->integer(function (IntegerElementBuilder $builder) {
191
     *     $builder->min(1)->max(9999);
192
     * });
193
     * </code>
194
     *
195
     * @param callable(ElementBuilderInterface<IntegerElement>):void|null $configurator Callback for configure the inner element builder
196
     *
197
     * @return ArrayElementBuilder<int>
198
     */
199 2
    public function integer(?callable $configurator = null): ArrayElementBuilder
200
    {
201 2
        return $this->element(IntegerElement::class, $configurator);
202
    }
203
204
    /**
205
     * Define as array of float
206
     *
207
     * <code>
208
     * $builder->array('prices')->float(function (FloatElementBuilder $builder) {
209
     *     $builder->min(0.01)->scale(2);
210
     * });
211
     * </code>
212
     *
213
     * @param callable(ElementBuilderInterface<FloatElement>):void|null $configurator Callback for configure the inner element builder
214
     *
215
     * @return ArrayElementBuilder<float>
216
     */
217 2
    public function float(?callable $configurator = null): ArrayElementBuilder
218
    {
219 2
        return $this->element(FloatElement::class, $configurator);
220
    }
221
222
    /**
223
     * Define as array of boolean
224
     *
225
     * <code>
226
     * $builder->array('flags')->boolean();
227
     * </code>
228
     *
229
     * @param callable(ElementBuilderInterface<BooleanElement>):void|null $configurator Callback for configure the inner element builder
230
     *
231
     * @return ArrayElementBuilder<bool>
232
     */
233 1
    public function boolean(?callable $configurator = null): ArrayElementBuilder
234
    {
235 1
        return $this->element(BooleanElement::class, $configurator);
236
    }
237
238
    /**
239
     * Define as array of date time
240
     *
241
     * <code>
242
     * $builder->array('dates')->dateTime(function (DateTimeElementBuilder $builder) {
243
     *     $builder->after(new DateTime());
244
     * });
245
     * </code>
246
     *
247
     * @param callable(ElementBuilderInterface<ElementInterface<\DateTimeInterface>>):void|null $configurator Callback for configure the inner element builder
248
     *
249
     * @return ArrayElementBuilder<\DateTimeInterface>
250
     */
251 2
    public function dateTime(?callable $configurator = null): ArrayElementBuilder
252
    {
253 2
        return $this->element(DateTimeElement::class, $configurator);
254
    }
255
256
    /**
257
     * Define as array of phone number
258
     *
259
     * <code>
260
     * $builder->array('phones')->phone(function (PhoneElementBuilder $builder) {
261
     *     $builder->regionInput('../../address/country');
262
     * });
263
     * </code>
264
     *
265
     * @param callable(ElementBuilderInterface<ElementInterface<\libphonenumber\PhoneNumber>>):void|null $configurator Callback for configure the inner element builder
266
     *
267
     * @return ArrayElementBuilder<\libphonenumber\PhoneNumber>
268
     */
269 1
    public function phone(?callable $configurator = null): ArrayElementBuilder
270
    {
271 1
        return $this->element(PhoneElement::class, $configurator);
272
    }
273
274
    /**
275
     * Define as array of embedded forms
276
     *
277
     * <code>
278
     * $builder->array('addresses')->form(function (FormBuilder $builder) {
279
     *     $builder->string('address');
280
     *     $builder->string('city');
281
     *     $builder->string('zipcode');
282
     *     $builder->string('country');
283
     * });
284
     * </code>
285
     *
286
     * @param callable|null $configurator Configure the embedded form
287
     *
288
     * @return ArrayElementBuilder<mixed>
289
     */
290 3
    public function form(?callable $configurator = null): ArrayElementBuilder
291
    {
292 3
        return $this->element(Form::class, $configurator);
293
    }
294
295
    /**
296
     * Add a count constraint on the array
297
     *
298
     * Ex: `$builder->count(['min' => 3, 'max' => 5])`
299
     *
300
     * @param array $options Constraint options. Keys are "min", "max"
301
     *
302
     * @return $this
303
     *
304
     * @see Count For the list of options
305
     */
306 3
    public function count(array $options): ArrayElementBuilder
307
    {
308 3
        return $this->arrayConstraint(new Count($options));
309
    }
310
311
    /**
312
     * {@inheritdoc}
313
     *
314
     * @return $this
315
     */
316 6
    final public function required($options = null)
317
    {
318 6
        if (!$options instanceof Constraint) {
319 5
            if (is_string($options)) {
320 1
                $options = ['message' => $options];
321
            }
322
323 5
            $options = new NotBlank($options);
324
        }
325
326 6
        return $this->arrayConstraint($options);
327
    }
328
329
    /**
330
     * {@inheritdoc}
331
     */
332 4
    final public function choices($choices, $options = null): self
333
    {
334
        // @fixme c/c choice from trait
335 4
        if (!$choices instanceof ChoiceInterface) {
336 4
            $choices = is_array($choices) ? new ArrayChoice($choices) : new LazyChoice($choices);
337
        }
338
339 4
        if (is_string($options)) {
340
            $options = ['multipleMessage' => $options];
341
        }
342
343 4
        $options['callback'] = [$choices, 'values'];
344 4
        $options['multiple'] = true;
345
346 4
        $this->choices = $choices;
347
348 4
        return $this->arrayConstraint(new ChoiceConstraint($options));
349
    }
350
351
    /**
352
     * {@inheritdoc}
353
     *
354
     * @return ArrayElement<T>
355
     */
356 41
    public function buildElement(): ElementInterface
357
    {
358 41
        $element = new ArrayElement(
359 41
            $this->getElementBuilder()->buildElement(),
360 41
            $this->buildTransformer(),
361 41
            $this->buildValidator(),
362 41
            $this->getChoices()
363
        );
364
365 41
        if ($this->value) {
366 1
            $element->import($this->value);
367
        }
368
369 41
        return $element;
370
    }
371
372
    /**
373
     * {@inheritdoc}
374
     */
375 41
    protected function registry(): RegistryInterface
376
    {
377 41
        return $this->registry;
378
    }
379
}
380