Passed
Pull Request — master (#2)
by Vincent
19:00
created

RootForm::patch()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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