Passed
Pull Request — master (#3)
by Vincent
04:40
created

RootForm::offsetExists()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
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 4
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
namespace Bdf\Form\Aggregate;
4
5
use BadMethodCallException;
6
use Bdf\Form\Button\ButtonInterface;
7
use Bdf\Form\Child\ChildInterface;
8
use Bdf\Form\Child\Http\HttpFieldPath;
9
use Bdf\Form\ElementInterface;
10
use Bdf\Form\Error\FormError;
11
use Bdf\Form\RootElementInterface;
12
use Bdf\Form\View\ElementViewInterface;
13
use Iterator;
14
use OutOfBoundsException;
15
use Symfony\Component\PropertyAccess\PropertyAccess;
16
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
17
use Symfony\Component\Validator\Constraint;
18
use Symfony\Component\Validator\Validator\ValidatorInterface;
19
use Symfony\Component\Validator\ValidatorBuilder;
20
use WeakReference;
21
22
/**
23
 * Adapt a form element as root element
24
 * The root form handle constraint group, validator and property accessor instances, and submit button
25
 *
26
 * The root form should be used instead of the form element for `submit()`
27
 *
28
 * <code>
29
 * $form = new MyForm();
30
 *
31
 * $root = $form->root();
32
 * if (!$root->submit($request->post())->valid()) {
33
 *     throw new MyError();
34
 * }
35
 *
36
 * $entity = $root->value();
37
 *
38
 * switch ($btn = $root->submitButton() ? $btn->name() : null) {
39
 *     case 'save':
40
 *         return $this->save($entity);
41
 *
42
 *     case 'delete':
43
 *         return $this->delete($entity);
44
 *
45
 *     default:
46
 *         throw new InvalidAction();
47
 * }
48
 * </code>
49
 *
50
 * @todo delegation trait
51
 */
52
final class RootForm implements RootElementInterface, ChildAggregateInterface
53
{
54
    /**
55
     * @var WeakReference<Form>
56
     */
57
    private $form;
58
59
    /**
60
     * @var array<non-empty-string, ButtonInterface>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<non-empty-string, ButtonInterface> at position 2 could not be parsed: Unknown type name 'non-empty-string' at position 2 in array<non-empty-string, ButtonInterface>.
Loading history...
61
     */
62
    private $buttons;
63
64
    /**
65
     * @var ButtonInterface|null
66
     */
67
    private $submitButton;
68
69
    /**
70
     * @var PropertyAccessorInterface|null
71
     */
72
    private $propertyAccessor;
73
74
    /**
75
     * @var ValidatorInterface|null
76
     */
77
    private $validator;
78
79
80
    /**
81
     * RootForm constructor.
82
     *
83
     * @param Form $form
84
     * @param array<non-empty-string, ButtonInterface> $buttons Buttons, indexed by there name
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<non-empty-string, ButtonInterface> at position 2 could not be parsed: Unknown type name 'non-empty-string' at position 2 in array<non-empty-string, ButtonInterface>.
Loading history...
85
     * @param PropertyAccessorInterface|null $propertyAccessor
86
     * @param ValidatorInterface|null $validator
87
     */
88 149
    public function __construct(Form $form, array $buttons = [], ?PropertyAccessorInterface $propertyAccessor = null, ?ValidatorInterface $validator = null)
89
    {
90 149
        $this->form = WeakReference::create($form);
91 149
        $this->buttons = $buttons;
92 149
        $this->propertyAccessor = $propertyAccessor;
93 149
        $this->validator = $validator;
94 149
    }
95
96
    /**
97
     * {@inheritdoc}
98
     */
99 27
    public function submit($data): ElementInterface
100
    {
101 27
        $this->submitToButtons($data);
102
        /** @psalm-suppress PossiblyNullReference */
103 27
        $this->form->get()->submit($data);
104
105 27
        return $this;
106
    }
107
108
    /**
109
     * {@inheritdoc}
110
     */
111 3
    public function patch($data): ElementInterface
112
    {
113 3
        $this->submitToButtons($data);
114
        /** @psalm-suppress PossiblyNullReference */
115 3
        $this->form->get()->patch($data);
116
117 3
        return $this;
118
    }
119
120
    /**
121
     * {@inheritdoc}
122
     */
123 2
    public function import($entity): ElementInterface
124
    {
125
        /** @psalm-suppress PossiblyNullReference */
126 2
        $this->form->get()->import($entity);
127
128 2
        return $this;
129
    }
130
131
    /**
132
     * {@inheritdoc}
133
     */
134 1
    public function value()
135
    {
136
        /** @psalm-suppress PossiblyNullReference */
137 1
        return $this->form->get()->value();
138
    }
139
140
    /**
141
     * {@inheritdoc}
142
     */
143 2
    public function httpValue()
144
    {
145
        /** @psalm-suppress PossiblyNullReference */
146 2
        $httpValue = $this->form->get()->httpValue();
147
148 2
        if (empty($this->buttons)) {
149 1
            return $httpValue;
150
        }
151
152 1
        $httpValue = (array) $httpValue;
153
154 1
        foreach ($this->buttons as $btn) {
155 1
            $httpValue += $btn->toHttp();
156
        }
157
158 1
        return $httpValue;
159
    }
160
161
    /**
162
     * {@inheritdoc}
163
     */
164 1
    public function valid(): bool
165
    {
166
        /** @psalm-suppress PossiblyNullReference */
167 1
        return $this->form->get()->valid();
168
    }
169
170
    /**
171
     * {@inheritdoc}
172
     */
173 1
    public function error(?HttpFieldPath $field = null): FormError
174
    {
175
        /** @psalm-suppress PossiblyNullReference */
176 1
        return $this->form->get()->error($field);
177
    }
178
179
    /**
180
     * {@inheritdoc}
181
     */
182 2
    public function container(): ?ChildInterface
183
    {
184 2
        return null; // root cannot have a container
185
    }
186
187
    /**
188
     * {@inheritdoc}
189
     */
190 1
    public function setContainer(ChildInterface $container): ElementInterface
191
    {
192 1
        throw new BadMethodCallException('Cannot wrap a root element into a container');
193
    }
194
195
    /**
196
     * {@inheritdoc}
197
     */
198 1
    public function root(): RootElementInterface
199
    {
200 1
        return $this;
201
    }
202
203
    /**
204
     * {@inheritdoc}
205
     */
206 5
    public function view(?HttpFieldPath $field = null): ElementViewInterface
207
    {
208 5
        $buttons = [];
209
210 5
        foreach ($this->buttons as $button) {
211 2
            $buttons[$button->name()] = $button->view($field);
212
        }
213
214
        /** @psalm-suppress PossiblyNullReference */
215 5
        $view = $this->form->get()->view($field);
216 5
        $view->setButtons($buttons);
217
218 5
        return $view;
219
    }
220
221
    /**
222
     * {@inheritdoc}
223
     */
224 4
    public function submitButton(): ?ButtonInterface
225
    {
226 4
        return $this->submitButton;
227
    }
228
229
    /**
230
     * {@inheritdoc}
231
     */
232 2
    public function button(string $name): ButtonInterface
233
    {
234 2
        if ($btn = $this->buttons[$name] ?? null) {
235 1
            return $btn;
236
        }
237
238 1
        throw new OutOfBoundsException("The button '{$name}' is not found");
239
    }
240
241
    /**
242
     * {@inheritdoc}
243
     */
244 68
    public function getValidator(): ValidatorInterface
245
    {
246 68
        if ($this->validator === null) {
247 67
            $this->validator = (new ValidatorBuilder())->getValidator();
248
        }
249
250 68
        return $this->validator;
251
    }
252
253
    /**
254
     * {@inheritdoc}
255
     */
256 79
    public function getPropertyAccessor(): PropertyAccessorInterface
257
    {
258 79
        if ($this->propertyAccessor === null) {
259 78
            $this->propertyAccessor = PropertyAccess::createPropertyAccessor();
260
        }
261
262 79
        return $this->propertyAccessor;
263
    }
264
265
    /**
266
     * {@inheritdoc}
267
     */
268 66
    public function constraintGroups(): array
269
    {
270 66
        if (!$button = $this->submitButton) {
271 65
            return [Constraint::DEFAULT_GROUP];
272
        }
273
274 2
        return $button->constraintGroups() ?: [Constraint::DEFAULT_GROUP];
275
    }
276
277
    /**
278
     * {@inheritdoc}
279
     *
280
     * @psalm-suppress InvalidNullableReturnType
281
     * @psalm-suppress PossiblyNullReference
282
     * @psalm-suppress PossiblyNullArrayAccess
283
     * @psalm-suppress NullableReturnStatement
284
     */
285 6
    public function offsetGet($offset): ChildInterface
286
    {
287 6
        return $this->form->get()[$offset];
288
    }
289
290
    /**
291
     * {@inheritdoc}
292
     */
293 4
    public function offsetExists($offset): bool
294
    {
295
        /** @psalm-suppress PossiblyNullReference */
296 4
        return isset($this->form->get()[$offset]);
297
    }
298
299
    /**
300
     * {@inheritdoc}
301
     */
302
    public function offsetSet($offset, $value): void
303
    {
304
        /** @psalm-suppress PossiblyNullReference */
305
        $this->form->get()[$offset] = $value;
306
    }
307
308
    /**
309
     * {@inheritdoc}
310
     *
311
     * @psalm-suppress PossiblyNullReference
312
     * @psalm-suppress PossiblyNullArrayAccess
313
     */
314
    public function offsetUnset($offset): void
315
    {
316
        unset($this->form->get()[$offset]);
317
    }
318
319
    /**
320
     * {@inheritdoc}
321
     */
322
    public function getIterator(): Iterator
323
    {
324
        /** @psalm-suppress PossiblyNullReference */
325
        return $this->form->get()->getIterator();
326
    }
327
328
    /**
329
     * Submit HTTP fields to buttons
330
     *
331
     * @param mixed $data The HTTP value
332
     */
333 28
    private function submitToButtons($data): void
334
    {
335 28
        $this->submitButton = null;
336
337 28
        foreach ($this->buttons as $button) {
338 4
            if ($button->submit($data) && $this->submitButton === null) {
339 4
                $this->submitButton = $button;
340
            }
341
        }
342 28
    }
343
}
344