Passed
Push — master ( 779d84...3d5507 )
by Vincent
05:31
created

ArrayElementBuilder::__call()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 2
c 1
b 0
f 0
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 10
cc 1
nc 1
nop 2
crap 1
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\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 39
    public function __construct(RegistryInterface $registry = null)
82
    {
83 39
        $this->registry = $registry ?? new Registry();
84 39
    }
85
86
    /**
87
     * {@inheritdoc}
88
     *
89
     * Define a constraint on the inner element
90
     */
91 3
    public function satisfy($constraint, $options = null, bool $append = true)
92
    {
93 3
        $this->getElementBuilder()->satisfy($constraint, $options, $append);
94
95 3
        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<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...
130
     * @param callable|null $configurator Callback for configure the inner element builder. Takes as parameter the element builder
131
     *
132
     * @template RT
133
     *
134
     * @return ArrayElementBuilder<RT>
135
     */
136 38
    public function element(string $element, ?callable $configurator = null): ArrayElementBuilder
137
    {
138
        /** @var ArrayElementBuilder<RT> $this */
139
        // @todo exception if already defined ?
140 38
        $this->element = $this->registry->elementBuilder($element);
141
142 38
        if ($configurator) {
143 10
            $configurator($this->element);
144
        }
145
146 38
        return $this;
147
    }
148
149
    /**
150
     * {@inheritdoc}
151
     *
152
     * @return ElementBuilderInterface<ElementInterface<T>>
153
     */
154 38
    public function getElementBuilder(): ElementBuilderInterface
155
    {
156 38
        if (!$this->element) {
157 25
            $this->element(StringElement::class);
158
        }
159
160 38
        return $this->element;
161
    }
162
163
    /**
164
     * Define as array of string
165
     *
166
     * <code>
167
     * $builder->array('names')->string(function (StringElementBuilder $builder) {
168
     *     $builder->length(['min' => 3, 'max' => 32])->regex('/[a-z -]+/i');
169
     * });
170
     * </code>
171
     *
172
     * @param callable|null $configurator Callback for configure the inner element builder
173
     *
174
     * @return ArrayElementBuilder<string>
175
     */
176 1
    public function string(?callable $configurator = null): ArrayElementBuilder
177
    {
178 1
        return $this->element(StringElement::class, $configurator);
179
    }
180
181
    /**
182
     * Define as array of integer
183
     *
184
     * <code>
185
     * $builder->array('ids')->integer(function (IntegerElementBuilder $builder) {
186
     *     $builder->min(1)->max(9999);
187
     * });
188
     * </code>
189
     *
190
     * @param callable|null $configurator Callback for configure the inner element builder
191
     *
192
     * @return ArrayElementBuilder<int>
193
     */
194 2
    public function integer(?callable $configurator = null): ArrayElementBuilder
195
    {
196 2
        return $this->element(IntegerElement::class, $configurator);
197
    }
198
199
    /**
200
     * Define as array of float
201
     *
202
     * <code>
203
     * $builder->array('prices')->float(function (FloatElementBuilder $builder) {
204
     *     $builder->min(0.01)->scale(2);
205
     * });
206
     * </code>
207
     *
208
     * @param callable|null $configurator Callback for configure the inner element builder
209
     *
210
     * @return ArrayElementBuilder<float>
211
     */
212 2
    public function float(?callable $configurator = null): ArrayElementBuilder
213
    {
214 2
        return $this->element(FloatElement::class, $configurator);
215
    }
216
217
    /**
218
     * Define as array of boolean
219
     *
220
     * <code>
221
     * $builder->array('flags')->boolean();
222
     * </code>
223
     *
224
     * @param callable|null $configurator Callback for configure the inner element builder
225
     *
226
     * @return ArrayElementBuilder<bool>
227
     */
228 1
    public function boolean(?callable $configurator = null): ArrayElementBuilder
229
    {
230 1
        return $this->element(BooleanElement::class, $configurator);
231
    }
232
233
    /**
234
     * Define as array of date time
235
     *
236
     * <code>
237
     * $builder->array('dates')->dateTime(function (DateTimeElementBuilder $builder) {
238
     *     $builder->after(new DateTime());
239
     * });
240
     * </code>
241
     *
242
     * @param callable|null $configurator Callback for configure the inner element builder
243
     *
244
     * @return ArrayElementBuilder<\DateTimeInterface>
245
     */
246 2
    public function dateTime(?callable $configurator = null): ArrayElementBuilder
247
    {
248 2
        return $this->element(DateTimeElement::class, $configurator);
249
    }
250
251
    /**
252
     * Define as array of phone number
253
     *
254
     * <code>
255
     * $builder->array('phones')->phone(function (PhoneElementBuilder $builder) {
256
     *     $builder->regionInput('../../address/country');
257
     * });
258
     * </code>
259
     *
260
     * @param callable|null $configurator Callback for configure the inner element builder
261
     *
262
     * @return ArrayElementBuilder<\libphonenumber\PhoneNumber>
263
     */
264 1
    public function phone(?callable $configurator = null): ArrayElementBuilder
265
    {
266 1
        return $this->element(PhoneElement::class, $configurator);
267
    }
268
269
    /**
270
     * Define as array of embedded forms
271
     *
272
     * <code>
273
     * $builder->array('addresses')->form(function (FormBuilder $builder) {
274
     *     $builder->string('address');
275
     *     $builder->string('city');
276
     *     $builder->string('zipcode');
277
     *     $builder->string('country');
278
     * });
279
     * </code>
280
     *
281
     * @param callable|null $configurator Configure the embedded form
282
     *
283
     * @return ArrayElementBuilder<mixed>
284
     */
285 2
    public function form(?callable $configurator = null): ArrayElementBuilder
286
    {
287 2
        return $this->element(Form::class, $configurator);
288
    }
289
290
    /**
291
     * Add a count constraint on the array
292
     *
293
     * Ex: `$builder->count(['min' => 3, 'max' => 5])`
294
     *
295
     * @param array $options Constraint options. Keys are "min", "max"
296
     *
297
     * @return $this
298
     *
299
     * @see Count For the list of options
300
     */
301 3
    public function count(array $options): ArrayElementBuilder
302
    {
303 3
        return $this->arrayConstraint(new Count($options));
304
    }
305
306
    /**
307
     * {@inheritdoc}
308
     *
309
     * @return $this
310
     */
311 6
    final public function required($options = null)
312
    {
313 6
        if (!$options instanceof Constraint) {
314 5
            if (is_string($options)) {
315 1
                $options = ['message' => $options];
316
            }
317
318 5
            $options = new NotBlank($options);
319
        }
320
321 6
        return $this->arrayConstraint($options);
322
    }
323
324
    /**
325
     * {@inheritdoc}
326
     */
327 3
    final public function choices($choices, $options = null): self
328
    {
329
        // @fixme c/c choice from trait
330 3
        if (!$choices instanceof ChoiceInterface) {
331 3
            $choices = is_array($choices) ? new ArrayChoice($choices) : new LazzyChoice($choices);
332
        }
333
334 3
        if (is_string($options)) {
335
            $options = ['message' => $options, 'multipleMessage' => $options];
336
        }
337
338 3
        $options['callback'] = [$choices, 'values'];
339 3
        $options['multiple'] = true;
340
341 3
        $this->choices = $choices;
342
343 3
        return $this->arrayConstraint(new ChoiceConstraint($options));
344
    }
345
346
    /**
347
     * {@inheritdoc}
348
     *
349
     * @return ArrayElement<T>
350
     */
351 38
    public function buildElement(): ElementInterface
352
    {
353 38
        $element = new ArrayElement(
354 38
            $this->getElementBuilder()->buildElement(),
355 38
            $this->buildTransformer(),
356 38
            $this->buildValidator(),
357 38
            $this->getChoices()
358
        );
359
360 38
        if ($this->value) {
361 1
            $element->import($this->value);
362
        }
363
364 38
        return $element;
365
    }
366
367
    /**
368
     * {@inheritdoc}
369
     */
370 38
    protected function registry(): RegistryInterface
371
    {
372 38
        return $this->registry;
373
    }
374
}
375