Passed
Push — master ( 52cb59...719d42 )
by Vincent
04:52
created

ArrayElementBuilder::buildElement()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 2

Importance

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