FormBuilder::string()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
ccs 2
cts 2
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\AbstractElementBuilder;
6
use Bdf\Form\Aggregate\Collection\ChildrenCollection;
7
use Bdf\Form\Aggregate\Value\ValueGenerator;
8
use Bdf\Form\Aggregate\Value\ValueGeneratorInterface;
9
use Bdf\Form\Button\ButtonBuilderInterface;
10
use Bdf\Form\Child\ChildBuilder;
11
use Bdf\Form\Child\ChildBuilderInterface;
12
use Bdf\Form\Csrf\CsrfElement;
13
use Bdf\Form\Csrf\CsrfElementBuilder;
14
use Bdf\Form\Custom\CustomForm;
15
use Bdf\Form\ElementBuilderInterface;
16
use Bdf\Form\ElementInterface;
17
use Bdf\Form\Leaf\BooleanElement;
18
use Bdf\Form\Leaf\BooleanElementBuilder;
19
use Bdf\Form\Leaf\Date\DateTimeChildBuilder;
20
use Bdf\Form\Leaf\Date\DateTimeElement;
21
use Bdf\Form\Leaf\Date\DateTimeElementBuilder;
22
use Bdf\Form\Leaf\FloatElement;
23
use Bdf\Form\Leaf\FloatElementBuilder;
24
use Bdf\Form\Leaf\Helper\EmailElement;
25
use Bdf\Form\Leaf\Helper\UrlElement;
26
use Bdf\Form\Leaf\IntegerElement;
27
use Bdf\Form\Leaf\IntegerElementBuilder;
28
use Bdf\Form\Leaf\StringElement;
29
use Bdf\Form\Leaf\StringElementBuilder;
30
use Bdf\Form\Phone\PhoneChildBuilder;
31
use Bdf\Form\Phone\PhoneElement;
32
use Bdf\Form\Phone\PhoneElementBuilder;
33
use Bdf\Form\Registry\RegistryInterface;
34
use Bdf\Form\RootElementInterface;
35
use Bdf\Form\Transformer\TransformerInterface;
36
use Bdf\Form\Validator\ValueValidatorInterface;
37
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
38
use Symfony\Component\Validator\Validator\ValidatorInterface;
39
40
/**
41
 * Builder for a form
42
 *
43
 * <code>
44
 * // Get the builder
45
 * $builder = $registry->elementBuilder(Form::class);
46
 * $builder->generates(MyEntity::class); // Define the generated entity
47
 *
48
 * // Declare fields
49
 * $builder->string('foo')->required()->setter();
50
 * $builder->integer('bar')->min(11)->required()->setter();
51
 *
52
 * // Build the form
53
 * $form = $builder->buildElement();
54
 *
55
 * // Submit and validate http data
56
 * if (!$form->submit($request->post())->valid()) {
57
 *     throw new FormError();
58
 * }
59
 *
60
 * // Get the generated entity, and save it
61
 * $repository->save($form->value());
62
 * </code>
63
 *
64
 * @see Form
65
 * @see CustomForm::configure()
66
 */
67
class FormBuilder extends AbstractElementBuilder implements FormBuilderInterface
68
{
69
    /**
70
     * @var array<non-empty-string, ChildBuilder>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<non-empty-string, ChildBuilder> at position 2 could not be parsed: Unknown type name 'non-empty-string' at position 2 in array<non-empty-string, ChildBuilder>.
Loading history...
71
     */
72
    private $children = [];
73
74
    /**
75
     * @var array<non-empty-string, ButtonBuilderInterface>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<non-empty-string, ButtonBuilderInterface> at position 2 could not be parsed: Unknown type name 'non-empty-string' at position 2 in array<non-empty-string, ButtonBuilderInterface>.
Loading history...
76
     */
77
    private $buttons = [];
78
79
    /**
80
     * @var PropertyAccessorInterface|null
81
     */
82
    private $propertyAccessor;
83
84
    /**
85
     * @var ValidatorInterface|null
86
     */
87
    private $validator;
88
89
    /**
90
     * @var ValueGeneratorInterface|null
91
     */
92
    private $generator;
93
94
95
    /**
96
     * FormBuilder constructor.
97
     *
98
     * @param RegistryInterface|null $registry
99
     */
100 91
    public function __construct(?RegistryInterface $registry = null)
101
    {
102 91
        parent::__construct($registry);
103 91
    }
104
105
    /**
106
     * {@inheritdoc}
107
     *
108
     * @psalm-param non-empty-string $name
109
     * @psalm-param class-string<E> $element
110
     *
111
     * @template E as ElementInterface
112
     *
113
     * @psalm-return ChildBuilder<ElementBuilderInterface<E>>
114
     *
115
     * @psalm-suppress MoreSpecificReturnType
116
     * @psalm-suppress LessSpecificReturnStatement
117
     * @psalm-suppress PropertyTypeCoercion
118
     */
119 84
    public function add(string $name, string $element): ChildBuilderInterface
120
    {
121 84
        return $this->children[$name] = $this->registry()->childBuilder($element, $name);
122
    }
123
124
    /**
125
     * {@inheritdoc}
126
     *
127
     * @psalm-param non-empty-string $name
128
     * @psalm-return ChildBuilder<StringElementBuilder>
129
     *
130
     * @psalm-suppress MoreSpecificReturnType
131
     * @psalm-suppress LessSpecificReturnStatement
132
     */
133 53
    public function string(string $name, ?string $default = null): ChildBuilderInterface
134
    {
135 53
        return $this->add($name, StringElement::class)->default($default);
136
    }
137
138
    /**
139
     * {@inheritdoc}
140
     *
141
     * @psalm-param non-empty-string $name
142
     * @psalm-return ChildBuilder<IntegerElementBuilder>
143
     *
144
     * @psalm-suppress MoreSpecificReturnType
145
     * @psalm-suppress LessSpecificReturnStatement
146
     */
147 36
    public function integer(string $name, ?int $default = null): ChildBuilderInterface
148
    {
149 36
        return $this->add($name, IntegerElement::class)->default($default);
150
    }
151
152
    /**
153
     * {@inheritdoc}
154
     *
155
     * @psalm-param non-empty-string $name
156
     * @psalm-return ChildBuilder<FloatElementBuilder>
157
     *
158
     * @psalm-suppress MoreSpecificReturnType
159
     * @psalm-suppress LessSpecificReturnStatement
160
     */
161 1
    public function float(string $name, ?float $default = null): ChildBuilderInterface
162
    {
163 1
        return $this->add($name, FloatElement::class)->default($default);
164
    }
165
166
    /**
167
     * {@inheritdoc}
168
     *
169
     * @psalm-param non-empty-string $name
170
     * @psalm-return ChildBuilder<BooleanElementBuilder>
171
     *
172
     * @psalm-suppress MoreSpecificReturnType
173
     * @psalm-suppress LessSpecificReturnStatement
174
     */
175 1
    public function boolean(string $name): ChildBuilderInterface
176
    {
177 1
        return $this->add($name, BooleanElement::class);
178
    }
179
180
    /**
181
     * {@inheritdoc}
182
     *
183
     * @psalm-param non-empty-string $name
184
     * @psalm-return DateTimeChildBuilder&ChildBuilder<DateTimeElementBuilder>
185
     *
186
     * @psalm-suppress MoreSpecificReturnType
187
     * @psalm-suppress LessSpecificReturnStatement
188
     */
189 4
    public function dateTime(string $name): ChildBuilderInterface
190
    {
191 4
        return $this->add($name, DateTimeElement::class);
192
    }
193
194
    /**
195
     * {@inheritdoc}
196
     *
197
     * @psalm-param non-empty-string $name
198
     * @psalm-return PhoneChildBuilder&ChildBuilder<PhoneElementBuilder>
199
     *
200
     * @psalm-suppress MoreSpecificReturnType
201
     * @psalm-suppress LessSpecificReturnStatement
202
     */
203 8
    public function phone(string $name): ChildBuilderInterface
204
    {
205 8
        return $this->add($name, PhoneElement::class);
206
    }
207
208
    /**
209
     * {@inheritdoc}
210
     *
211
     * @psalm-param non-empty-string $name
212
     * @psalm-return ChildBuilder<CsrfElementBuilder>
213
     *
214
     * @psalm-suppress MoreSpecificReturnType
215
     * @psalm-suppress LessSpecificReturnStatement
216
     */
217 1
    public function csrf(string $name = '_token'): ChildBuilderInterface
218
    {
219 1
        return $this->add($name, CsrfElement::class);
220
    }
221
222
    /**
223
     * Add a new email element
224
     * Note: The email element is a simple string but with the email constraint
225
     *
226
     * <code>
227
     * $builder->email('contact')
228
     *     ->message('Invalid contact email')
229
     * ;
230
     * </code>
231
     *
232
     * @param non-empty-string $name The name of the input
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string.
Loading history...
233
     *
234
     * @return ChildBuilder|\Bdf\Form\Leaf\Helper\EmailElementBuilder
235
     * @psalm-return ChildBuilder<\Bdf\Form\Leaf\Helper\EmailElementBuilder>
236
     *
237
     * @psalm-suppress MoreSpecificReturnType
238
     * @psalm-suppress LessSpecificReturnStatement
239
     */
240 1
    public function email(string $name): ChildBuilderInterface
241
    {
242 1
        return $this->add($name, EmailElement::class);
243
    }
244
245
    /**
246
     * Add a new url element
247
     * Note: The url element is a simple string but with the url constraint
248
     *
249
     * <code>
250
     * $builder->url('home')->protocols('https');
251
     * </code>
252
     *
253
     * @param non-empty-string $name The name of the input
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string.
Loading history...
254
     *
255
     * @return ChildBuilder|\Bdf\Form\Leaf\Helper\UrlElementBuilder
256
     * @psalm-return ChildBuilder<\Bdf\Form\Leaf\Helper\UrlElementBuilder>
257
     *
258
     * @psalm-suppress MoreSpecificReturnType
259
     * @psalm-suppress LessSpecificReturnStatement
260
     */
261 1
    public function url(string $name): ChildBuilderInterface
262
    {
263 1
        return $this->add($name, UrlElement::class);
264
    }
265
266
    /**
267
     * {@inheritdoc}
268
     *
269
     * @psalm-param non-empty-string $name
270
     * @psalm-param callable|null $configurator
271
     * @psalm-return ChildBuilder<FormBuilder>
272
     *
273
     * @psalm-suppress MoreSpecificReturnType
274
     * @psalm-suppress LessSpecificReturnStatement
275
     */
276 12
    public function embedded(string $name, ?callable $configurator = null): ChildBuilderInterface
277
    {
278 12
        $builder = $this->add($name, Form::class);
279
280 12
        if ($configurator) {
281 11
            $configurator($builder);
282
        }
283
284 12
        return $builder;
285
    }
286
287
    /**
288
     * {@inheritdoc}
289
     *
290
     * @psalm-param non-empty-string $name
291
     * @psalm-param class-string<ElementInterface>|null $elementType
292
     * @psalm-param callable|null $elementConfigurator
293
     * @psalm-return ArrayChildBuilder&ChildBuilderInterface<ArrayElementBuilder>
294
     *
295
     * @psalm-suppress MoreSpecificReturnType
296
     * @psalm-suppress LessSpecificReturnStatement
297
     */
298 4
    public function array(string $name, ?string $elementType = null, ?callable $elementConfigurator = null): ChildBuilderInterface
299
    {
300
        /** @var ChildBuilderInterface<ArrayElementBuilder>&ArrayChildBuilder $builder */
301 4
        $builder = $this->add($name, ArrayElement::class);
302
303 4
        if ($elementType) {
304 1
            $builder->element($elementType, $elementConfigurator);
0 ignored issues
show
Bug introduced by
The method element() does not exist on Bdf\Form\Child\ChildBuilderInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Bdf\Form\Child\ChildBuilderInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

304
            $builder->/** @scrutinizer ignore-call */ 
305
                      element($elementType, $elementConfigurator);
Loading history...
305
        }
306
307 4
        return $builder;
308
    }
309
310
    /**
311
     * {@inheritdoc}
312
     */
313 3
    public function submit(string $name): ButtonBuilderInterface
314
    {
315 3
        return $this->buttons[$name] = $this->registry()->buttonBuilder($name);
316
    }
317
318
    /**
319
     * {@inheritdoc}
320
     */
321 1
    public function propertyAccessor(PropertyAccessorInterface $propertyAccessor): FormBuilderInterface
322
    {
323 1
        $this->propertyAccessor = $propertyAccessor;
324
325 1
        return $this;
326
    }
327
328
    /**
329
     * {@inheritdoc}
330
     */
331 1
    public function validator(ValidatorInterface $validator): FormBuilderInterface
332
    {
333 1
        $this->validator = $validator;
334
335 1
        return $this;
336
    }
337
338
    /**
339
     * {@inheritdoc}
340
     */
341 15
    public function generator(ValueGeneratorInterface $generator): FormBuilderInterface
342
    {
343 15
        $this->generator = $generator;
344
345 15
        return $this;
346
    }
347
348
    /**
349
     * {@inheritdoc}
350
     */
351 15
    public function generates($entity): FormBuilderInterface
352
    {
353 15
        return $this->generator(new ValueGenerator($entity));
354
    }
355
356
    /**
357
     * {@inheritdoc}
358
     */
359 90
    final protected function createElement(ValueValidatorInterface $validator, TransformerInterface $transformer): ElementInterface
360
    {
361 90
        $children = new ChildrenCollection();
362
363 90
        foreach ($this->children as $child) {
364 84
            $children->add($child->buildChild());
365
        }
366
367 90
        $form = new Form($children, $validator, $transformer, $this->generator);
368
369
        // The root form is configured by the builder : set into the form
370 90
        if ($this->hasRootFormConfiguration()) {
371 5
            $form->setRoot($this->buildRootForm($form));
372
        }
373
374 90
        return $form;
375
    }
376
377
    /**
378
     * Check if there is at least one attribute of the root form that is configured by the builder
379
     *
380
     * @return bool
381
     */
382 90
    private function hasRootFormConfiguration(): bool
383
    {
384 90
        return $this->buttons || $this->validator || $this->propertyAccessor;
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->buttons of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
385
    }
386
387
    /**
388
     * Build the root form
389
     *
390
     * @param Form $form
391
     *
392
     * @return RootElementInterface
393
     */
394 5
    private function buildRootForm(Form $form): RootElementInterface
395
    {
396 5
        $buttons = [];
397
398 5
        foreach ($this->buttons as $name => $button) {
399 3
            $buttons[$name] = $button->buildButton();
400
        }
401
402 5
        return new RootForm($form, $buttons, $this->propertyAccessor, $this->validator);
403
    }
404
}
405