Test Failed
Push — master ( 0db9fe...ef12af )
by Vincent
13:16
created

FormBuilder::url()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 0
cts 0
cp 0
rs 10
cc 1
nc 1
nop 1
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 71
     * @param RegistryInterface|null $registry
88
     */
89 71
    public function __construct(?RegistryInterface $registry = null)
90 71
    {
91
        parent::__construct($registry);
92
    }
93
94
    /**
95 64
     * {@inheritdoc}
96
     */
97 64
    public function add(string $name, string $element): ChildBuilderInterface
98
    {
99
        return $this->children[$name] = $this->registry()->childBuilder($element, $name);
100
    }
101
102
    /**
103
     * {@inheritdoc}
104
     *
105
     * @psalm-suppress MoreSpecificReturnType
106 45
     * @psalm-suppress LessSpecificReturnStatement
107
     */
108 45
    public function string(string $name, ?string $default = null): ChildBuilderInterface
109
    {
110
        return $this->add($name, StringElement::class)->default($default);
111
    }
112
113
    /**
114
     * {@inheritdoc}
115
     *
116
     * @psalm-suppress MoreSpecificReturnType
117 34
     * @psalm-suppress LessSpecificReturnStatement
118
     */
119 34
    public function integer(string $name, ?int $default = null): ChildBuilderInterface
120
    {
121
        return $this->add($name, IntegerElement::class)->default($default);
122
    }
123
124
    /**
125
     * {@inheritdoc}
126
     *
127
     * @psalm-suppress MoreSpecificReturnType
128 1
     * @psalm-suppress LessSpecificReturnStatement
129
     */
130 1
    public function float(string $name, ?float $default = null): ChildBuilderInterface
131
    {
132
        return $this->add($name, FloatElement::class)->default($default);
133
    }
134
135
    /**
136
     * {@inheritdoc}
137
     *
138
     * @psalm-suppress MoreSpecificReturnType
139 1
     * @psalm-suppress LessSpecificReturnStatement
140
     */
141 1
    public function boolean(string $name): ChildBuilderInterface
142
    {
143
        return $this->add($name, BooleanElement::class);
144
    }
145
146
    /**
147
     * {@inheritdoc}
148
     *
149
     * @psalm-suppress MoreSpecificReturnType
150 1
     * @psalm-suppress LessSpecificReturnStatement
151
     */
152 1
    public function dateTime(string $name): ChildBuilderInterface
153
    {
154
        return $this->add($name, DateTimeElement::class);
155
    }
156
157
    /**
158
     * {@inheritdoc}
159
     *
160
     * @psalm-suppress MoreSpecificReturnType
161 1
     * @psalm-suppress LessSpecificReturnStatement
162
     */
163 1
    public function phone(string $name): ChildBuilderInterface
164
    {
165
        return $this->add($name, PhoneElement::class);
166
    }
167
168
    /**
169
     * {@inheritdoc}
170
     *
171
     * @psalm-suppress MoreSpecificReturnType
172 1
     * @psalm-suppress LessSpecificReturnStatement
173
     */
174 1
    public function csrf(string $name = '_token'): ChildBuilderInterface
175
    {
176
        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 11
     * <code>
184
     * $builder->email('contact')
185 11
     *     ->message('Invalid contact email')
186
     * ;
187 11
     * </code>
188 10
     *
189
     * @param string $name The name of the input
190
     *
191 11
     * @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
    public function email(string $name): ChildBuilderInterface
198
    {
199
        return $this->add($name, EmailElement::class);
200 2
    }
201
202
    /**
203 2
     * Add a new url element
204
     * Note: The url element is a simple string but with the url constraint
205 2
     *
206 1
     * <code>
207
     * $builder->url('home')->protocols('https');
208
     * </code>
209 2
     *
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 3
     * @psalm-suppress MoreSpecificReturnType
216
     * @psalm-suppress LessSpecificReturnStatement
217 3
     */
218
    public function url(string $name): ChildBuilderInterface
219
    {
220
        return $this->add($name, UrlElement::class);
221
    }
222
223 1
    /**
224
     * {@inheritdoc}
225 1
     *
226
     * @psalm-suppress MoreSpecificReturnType
227 1
     * @psalm-suppress LessSpecificReturnStatement
228
     */
229
    public function embedded(string $name, ?callable $configurator = null): ChildBuilderInterface
230
    {
231
        $builder = $this->add($name, Form::class);
232
233 1
        if ($configurator) {
234
            $configurator($builder);
235 1
        }
236
237 1
        return $builder;
238
    }
239
240
    /**
241
     * {@inheritdoc}
242
     *
243 15
     * @psalm-suppress MoreSpecificReturnType
244
     * @psalm-suppress LessSpecificReturnStatement
245 15
     */
246
    public function array(string $name, ?string $elementType = null, ?callable $elementConfigurator = null): ChildBuilderInterface
247 15
    {
248
        /** @var ChildBuilderInterface<ArrayElementBuilder> $builder */
249
        $builder = $this->add($name, ArrayElement::class);
250
251
        if ($elementType) {
252
            $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 15
        }
254
255 15
        return $builder;
256
    }
257
258
    /**
259
     * {@inheritdoc}
260
     */
261 70
    public function submit(string $name): ButtonBuilderInterface
262
    {
263 70
        return $this->buttons[$name] = $this->registry()->buttonBuilder($name);
264
    }
265 70
266 64
    /**
267
     * {@inheritdoc}
268
     */
269 70
    public function propertyAccessor(PropertyAccessorInterface $propertyAccessor): FormBuilderInterface
270
    {
271
        $this->propertyAccessor = $propertyAccessor;
272 70
273 5
        return $this;
274
    }
275
276 70
    /**
277
     * {@inheritdoc}
278
     */
279
    public function validator(ValidatorInterface $validator): FormBuilderInterface
280
    {
281
        $this->validator = $validator;
282
283
        return $this;
284 70
    }
285
286 70
    /**
287
     * {@inheritdoc}
288
     */
289
    public function generator(ValueGeneratorInterface $generator): FormBuilderInterface
290
    {
291
        $this->generator = $generator;
292
293
        return $this;
294
    }
295
296 5
    /**
297
     * {@inheritdoc}
298 5
     */
299
    public function generates($entity): FormBuilderInterface
300 5
    {
301 3
        return $this->generator(new ValueGenerator($entity));
302
    }
303
304 5
    /**
305
     * {@inheritdoc}
306
     */
307
    final protected function createElement(ValueValidatorInterface $validator, TransformerInterface $transformer): ElementInterface
308
    {
309
        $children = new ChildrenCollection();
310
311
        foreach ($this->children as $child) {
312
            $children->add($child->buildChild());
313
        }
314
315
        $form = new Form($children, $validator, $transformer, $this->generator);
316
317
        // The root form is configured by the builder : set into the form
318
        if ($this->hasRootFormConfiguration()) {
319
            $form->setRoot($this->buildRootForm($form));
320
        }
321
322
        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
    private function hasRootFormConfiguration(): bool
331
    {
332
        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
    private function buildRootForm(Form $form): RootElementInterface
343
    {
344
        $buttons = [];
345
346
        foreach ($this->buttons as $button) {
347
            $buttons[] = $button->buildButton();
348
        }
349
350
        return new RootForm($form, $buttons, $this->propertyAccessor, $this->validator);
351
    }
352
}
353