Passed
Push — master ( 9d312b...da6698 )
by Vincent
04:40
created

FormBuilder::array()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 5
c 1
b 0
f 0
dl 0
loc 10
ccs 5
cts 5
cp 1
rs 10
cc 2
nc 2
nop 3
crap 2
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\ChildBuilderInterface;
11
use Bdf\Form\Csrf\CsrfElement;
12
use Bdf\Form\Custom\CustomForm;
13
use Bdf\Form\ElementInterface;
14
use Bdf\Form\Leaf\BooleanElement;
15
use Bdf\Form\Leaf\Date\DateTimeElement;
16
use Bdf\Form\Leaf\FloatElement;
17
use Bdf\Form\Leaf\Helper\EmailElement;
18
use Bdf\Form\Leaf\Helper\UrlElement;
19
use Bdf\Form\Leaf\IntegerElement;
20
use Bdf\Form\Leaf\StringElement;
21
use Bdf\Form\Phone\PhoneElement;
22
use Bdf\Form\Registry\RegistryInterface;
23
use Bdf\Form\RootElementInterface;
24
use Bdf\Form\Transformer\TransformerInterface;
25
use Bdf\Form\Validator\ValueValidatorInterface;
26
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
27
use Symfony\Component\Validator\Validator\ValidatorInterface;
28
29
/**
30
 * Builder for a form
31
 *
32
 * <code>
33
 * // Get the builder
34
 * $builder = $registry->elementBuilder(Form::class);
35
 * $builder->generates(MyEntity::class); // Define the generated entity
36
 *
37
 * // Declare fields
38
 * $builder->string('foo')->required()->setter();
39
 * $builder->integer('bar')->min(11)->required()->setter();
40
 *
41
 * // Build the form
42
 * $form = $builder->buildElement();
43
 *
44
 * // Submit and validate http data
45
 * if (!$form->submit($request->post())->valid()) {
46
 *     throw new FormError();
47
 * }
48
 *
49
 * // Get the generated entity, and save it
50
 * $repository->save($form->value());
51
 * </code>
52
 *
53
 * @see Form
54
 * @see CustomForm::configure()
55
 */
56
class FormBuilder extends AbstractElementBuilder implements FormBuilderInterface
57
{
58
    /**
59
     * @var ChildBuilderInterface[]
60
     */
61
    private $children = [];
62
63
    /**
64
     * @var ButtonBuilderInterface[]
65
     */
66
    private $buttons = [];
67
68
    /**
69
     * @var PropertyAccessorInterface|null
70
     */
71
    private $propertyAccessor;
72
73
    /**
74
     * @var ValidatorInterface|null
75
     */
76
    private $validator;
77
78
    /**
79
     * @var ValueGeneratorInterface|null
80
     */
81
    private $generator;
82
83
84
    /**
85
     * FormBuilder constructor.
86
     *
87
     * @param RegistryInterface|null $registry
88
     */
89 81
    public function __construct(?RegistryInterface $registry = null)
90
    {
91 81
        parent::__construct($registry);
92 81
    }
93
94
    /**
95
     * {@inheritdoc}
96
     */
97 74
    public function add(string $name, string $element): ChildBuilderInterface
98
    {
99 74
        return $this->children[$name] = $this->registry()->childBuilder($element, $name);
100
    }
101
102
    /**
103
     * {@inheritdoc}
104
     *
105
     * @psalm-suppress MoreSpecificReturnType
106
     * @psalm-suppress LessSpecificReturnStatement
107
     */
108 48
    public function string(string $name, ?string $default = null): ChildBuilderInterface
109
    {
110 48
        return $this->add($name, StringElement::class)->default($default);
111
    }
112
113
    /**
114
     * {@inheritdoc}
115
     *
116
     * @psalm-suppress MoreSpecificReturnType
117
     * @psalm-suppress LessSpecificReturnStatement
118
     */
119 34
    public function integer(string $name, ?int $default = null): ChildBuilderInterface
120
    {
121 34
        return $this->add($name, IntegerElement::class)->default($default);
122
    }
123
124
    /**
125
     * {@inheritdoc}
126
     *
127
     * @psalm-suppress MoreSpecificReturnType
128
     * @psalm-suppress LessSpecificReturnStatement
129
     */
130 1
    public function float(string $name, ?float $default = null): ChildBuilderInterface
131
    {
132 1
        return $this->add($name, FloatElement::class)->default($default);
133
    }
134
135
    /**
136
     * {@inheritdoc}
137
     *
138
     * @psalm-suppress MoreSpecificReturnType
139
     * @psalm-suppress LessSpecificReturnStatement
140
     */
141 1
    public function boolean(string $name): ChildBuilderInterface
142
    {
143 1
        return $this->add($name, BooleanElement::class);
144
    }
145
146
    /**
147
     * {@inheritdoc}
148
     *
149
     * @psalm-suppress MoreSpecificReturnType
150
     * @psalm-suppress LessSpecificReturnStatement
151
     */
152 4
    public function dateTime(string $name): ChildBuilderInterface
153
    {
154 4
        return $this->add($name, DateTimeElement::class);
155
    }
156
157
    /**
158
     * {@inheritdoc}
159
     *
160
     * @psalm-suppress MoreSpecificReturnType
161
     * @psalm-suppress LessSpecificReturnStatement
162
     */
163 3
    public function phone(string $name): ChildBuilderInterface
164
    {
165 3
        return $this->add($name, PhoneElement::class);
166
    }
167
168
    /**
169
     * {@inheritdoc}
170
     *
171
     * @psalm-suppress MoreSpecificReturnType
172
     * @psalm-suppress LessSpecificReturnStatement
173
     */
174 1
    public function csrf(string $name = '_token'): ChildBuilderInterface
175
    {
176 1
        return $this->add($name, CsrfElement::class);
177
    }
178
179
    /**
180
     * Add a new email element
181
     * Note: The email element is a simple string but with the email constraint
182
     *
183
     * <code>
184
     * $builder->email('contact')
185
     *     ->message('Invalid contact email')
186
     * ;
187
     * </code>
188
     *
189
     * @param string $name The name of the input
190
     *
191
     * @return ChildBuilderInterface|\Bdf\Form\Leaf\Helper\EmailElementBuilder
192
     * @psalm-return ChildBuilderInterface<\Bdf\Form\Leaf\Helper\EmailElementBuilder>
193
     *
194
     * @psalm-suppress MoreSpecificReturnType
195
     * @psalm-suppress LessSpecificReturnStatement
196
     */
197 1
    public function email(string $name): ChildBuilderInterface
198
    {
199 1
        return $this->add($name, EmailElement::class);
200
    }
201
202
    /**
203
     * Add a new url element
204
     * Note: The url element is a simple string but with the url constraint
205
     *
206
     * <code>
207
     * $builder->url('home')->protocols('https');
208
     * </code>
209
     *
210
     * @param string $name The name of the input
211
     *
212
     * @return ChildBuilderInterface|\Bdf\Form\Leaf\Helper\UrlElementBuilder
213
     * @psalm-return ChildBuilderInterface<\Bdf\Form\Leaf\Helper\UrlElementBuilder>
214
     *
215
     * @psalm-suppress MoreSpecificReturnType
216
     * @psalm-suppress LessSpecificReturnStatement
217
     */
218 1
    public function url(string $name): ChildBuilderInterface
219
    {
220 1
        return $this->add($name, UrlElement::class);
221
    }
222
223
    /**
224
     * {@inheritdoc}
225
     *
226
     * @psalm-suppress MoreSpecificReturnType
227
     * @psalm-suppress LessSpecificReturnStatement
228
     */
229 12
    public function embedded(string $name, ?callable $configurator = null): ChildBuilderInterface
230
    {
231 12
        $builder = $this->add($name, Form::class);
232
233 12
        if ($configurator) {
234 11
            $configurator($builder);
235
        }
236
237 12
        return $builder;
238
    }
239
240
    /**
241
     * {@inheritdoc}
242
     *
243
     * @psalm-suppress MoreSpecificReturnType
244
     * @psalm-suppress LessSpecificReturnStatement
245
     */
246 3
    public function array(string $name, ?string $elementType = null, ?callable $elementConfigurator = null): ChildBuilderInterface
247
    {
248
        /** @var ChildBuilderInterface<ArrayElementBuilder> $builder */
249 3
        $builder = $this->add($name, ArrayElement::class);
250
251 3
        if ($elementType) {
252 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

252
            $builder->/** @scrutinizer ignore-call */ 
253
                      element($elementType, $elementConfigurator);
Loading history...
253
        }
254
255 3
        return $builder;
256
    }
257
258
    /**
259
     * {@inheritdoc}
260
     */
261 3
    public function submit(string $name): ButtonBuilderInterface
262
    {
263 3
        return $this->buttons[$name] = $this->registry()->buttonBuilder($name);
264
    }
265
266
    /**
267
     * {@inheritdoc}
268
     */
269 1
    public function propertyAccessor(PropertyAccessorInterface $propertyAccessor): FormBuilderInterface
270
    {
271 1
        $this->propertyAccessor = $propertyAccessor;
272
273 1
        return $this;
274
    }
275
276
    /**
277
     * {@inheritdoc}
278
     */
279 1
    public function validator(ValidatorInterface $validator): FormBuilderInterface
280
    {
281 1
        $this->validator = $validator;
282
283 1
        return $this;
284
    }
285
286
    /**
287
     * {@inheritdoc}
288
     */
289 15
    public function generator(ValueGeneratorInterface $generator): FormBuilderInterface
290
    {
291 15
        $this->generator = $generator;
292
293 15
        return $this;
294
    }
295
296
    /**
297
     * {@inheritdoc}
298
     */
299 15
    public function generates($entity): FormBuilderInterface
300
    {
301 15
        return $this->generator(new ValueGenerator($entity));
302
    }
303
304
    /**
305
     * {@inheritdoc}
306
     */
307 80
    final protected function createElement(ValueValidatorInterface $validator, TransformerInterface $transformer): ElementInterface
308
    {
309 80
        $children = new ChildrenCollection();
310
311 80
        foreach ($this->children as $child) {
312 74
            $children->add($child->buildChild());
313
        }
314
315 80
        $form = new Form($children, $validator, $transformer, $this->generator);
316
317
        // The root form is configured by the builder : set into the form
318 80
        if ($this->hasRootFormConfiguration()) {
319 5
            $form->setRoot($this->buildRootForm($form));
320
        }
321
322 80
        return $form;
323
    }
324
325
    /**
326
     * Check if there is at least one attribute of the root form that is configured by the builder
327
     *
328
     * @return bool
329
     */
330 80
    private function hasRootFormConfiguration(): bool
331
    {
332 80
        return $this->buttons || $this->validator || $this->propertyAccessor;
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->buttons of type Bdf\Form\Button\ButtonBuilderInterface[] 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...
333
    }
334
335
    /**
336
     * Build the root form
337
     *
338
     * @param Form $form
339
     *
340
     * @return RootElementInterface
341
     */
342 5
    private function buildRootForm(Form $form): RootElementInterface
343
    {
344 5
        $buttons = [];
345
346 5
        foreach ($this->buttons as $button) {
347 3
            $buttons[] = $button->buildButton();
348
        }
349
350 5
        return new RootForm($form, $buttons, $this->propertyAccessor, $this->validator);
351
    }
352
}
353