Passed
Pull Request — master (#6)
by Vincent
16:57 queued 12:15
created

CustomForm::error()   A

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 1
crap 1
1
<?php
2
3
namespace Bdf\Form\Custom;
4
5
use Bdf\Form\Aggregate\FormBuilder;
6
use Bdf\Form\Aggregate\FormBuilderInterface;
7
use Bdf\Form\Aggregate\FormInterface;
8
use Bdf\Form\Aggregate\View\FormView;
9
use Bdf\Form\Child\ChildInterface;
10
use Bdf\Form\Child\Http\HttpFieldPath;
11
use Bdf\Form\ElementInterface;
12
use Bdf\Form\Error\FormError;
13
use Bdf\Form\RootElementInterface;
14
use Bdf\Form\View\ElementViewInterface;
15
use Iterator;
16
use WeakReference;
17
18
/**
19
 * Utility class for simply create a custom form element
20
 *
21
 * <code>
22
 * // Declaration
23
 * class MyForm extends CustomForm
24
 * {
25
 *     public function configure(FormBuilderInterface $builder)
26
 *     {
27
 *         $builder->generates(MyEntity::class);
28
 *         $builder->string('foo')->setter();
29
 *     }
30
 * }
31
 *
32
 * // Usage
33
 * $form = new MyForm(); // Directly instantiate the form
34
 * $form = $this->registry->elementBuilder(MyForm::class)->buildElement(); // Use registry and builder
35
 *
36
 * if (!$form->submit($request->post())->valid()) {
37
 *     return new JsonResponse($form->error()->print(new FormErrorFormat()), 400);
38
 * }
39
 *
40
 * $entity = $form->value();
41
 * $this->service->handle($entity);
42
 *
43
 * return new Response('OK', 200);
44
 * </code>
45
 *
46
 * @todo implements root form interface ?
47
 * @template T
48
 * @implements FormInterface<T>
49
 */
50
abstract class CustomForm implements FormInterface
51
{
52
    /**
53
     * @var FormBuilderInterface
54
     */
55
    private $builder;
56
57
    /**
58
     * The inner form
59
     *
60
     * @var FormInterface<T>|null
61
     */
62
    private $form;
63
64
    /**
65
     * @var WeakReference<ChildInterface>|null
66
     */
67
    private $container;
68
69
    /**
70
     * @var list<callable(static, FormBuilderInterface): void>
0 ignored issues
show
Bug introduced by
The type Bdf\Form\Custom\list was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
71
     */
72
    private $preConfigureHooks = [];
73
74
    /**
75
     * @var list<callable(static, FormInterface<T>): void>
76
     */
77
    private $postConfigureHooks = [];
78
79
    /**
80
     * CustomForm constructor.
81
     *
82
     * @param FormBuilderInterface|null $builder
83
     */
84 44
    public function __construct(?FormBuilderInterface $builder = null)
85
    {
86 44
        $this->builder = $builder ?? new FormBuilder();
87 44
    }
88
89
    /**
90
     * {@inheritdoc}
91
     */
92 19
    public function offsetGet($offset): ChildInterface
93
    {
94 19
        return $this->form()[$offset];
95
    }
96
97
    /**
98
     * {@inheritdoc}
99
     */
100 11
    public function offsetExists($offset): bool
101
    {
102 11
        return isset($this->form()[$offset]);
103
    }
104
105
    /**
106
     * {@inheritdoc}
107
     */
108
    public function offsetSet($offset, $value): void
109
    {
110
        $this->form()[$offset] = $value;
111
    }
112
113
    /**
114
     * {@inheritdoc}
115
     */
116
    public function offsetUnset($offset): void
117
    {
118
        unset($this->form()[$offset]);
119
    }
120
121
    /**
122
     * {@inheritdoc}
123
     */
124 1
    public function getIterator(): Iterator
125
    {
126 1
        return $this->form()->getIterator();
127
    }
128
129
    /**
130
     * {@inheritdoc}
131
     */
132 27
    public function submit($data): ElementInterface
133
    {
134 27
        $this->submitTarget()->submit($data);
135
136 27
        return $this;
137
    }
138
139
    /**
140
     * {@inheritdoc}
141
     */
142 2
    public function patch($data): ElementInterface
143
    {
144 2
        $this->submitTarget()->patch($data);
145
146 2
        return $this;
147
    }
148
149
    /**
150
     * {@inheritdoc}
151
     */
152 2
    public function import($entity): ElementInterface
153
    {
154 2
        $this->form()->import($entity);
155
156 2
        return $this;
157
    }
158
159
    /**
160
     * {@inheritdoc}
161
     */
162 13
    public function value()
163
    {
164 13
        return $this->form()->value();
165
    }
166
167
    /**
168
     * {@inheritdoc}
169
     */
170 1
    public function httpValue()
171
    {
172 1
        return $this->form()->httpValue();
173
    }
174
175
    /**
176
     * {@inheritdoc}
177
     */
178 19
    public function valid(): bool
179
    {
180 19
        return $this->form()->valid();
181
    }
182
183
    /**
184
     * {@inheritdoc}
185
     */
186 5
    public function error(?HttpFieldPath $field = null): FormError
187
    {
188 5
        return $this->form()->error($field);
189
    }
190
191
    /**
192
     * {@inheritdoc}
193
     */
194 3
    public function container(): ?ChildInterface
195
    {
196 3
        return $this->container ? $this->container->get() : null;
197
    }
198
199
    /**
200
     * {@inheritdoc}
201
     */
202 8
    public function setContainer(ChildInterface $container): ElementInterface
203
    {
204 8
        $form = clone $this;
205 8
        $form->container = WeakReference::create($container);
206 8
        $form->form = null; // Reset the form to ensure that $this references will be regenerated
207
208 8
        return $form;
209
    }
210
211
    /**
212
     * {@inheritdoc}
213
     */
214 3
    public function root(): RootElementInterface
215
    {
216
        // @todo bad root form ?
217 3
        return $this->form()->root();
218
    }
219
220
    /**
221
     * {@inheritdoc}
222
     */
223 1
    public function attach($entity): FormInterface
224
    {
225 1
        $this->form()->attach($entity);
226
227 1
        return $this;
228
    }
229
230
    /**
231
     * {@inheritdoc}
232
     */
233 4
    public function view(?HttpFieldPath $field = null): ElementViewInterface
234
    {
235 4
        $form = $this->form();
236
        /** @var FormView $view */
237 4
        $view = $form->container() === null
238 4
            ? $form->root()->view($field)
239 4
            : $form->view($field)
240
        ;
241
242 4
        $view->setType(static::class);
243
244 4
        return $view;
245
    }
246
247
    /**
248
     * Configure the form using the builder
249
     *
250
     * @param FormBuilder $builder
251
     */
252
    abstract protected function configure(FormBuilderInterface $builder): void;
253
254
    /**
255
     * Override this method to hook the inner form build
256
     *
257
     * <code>
258
     * class MyForm extends CustomForm
259
     * {
260
     *     public $foo;
261
     *     public function configure(FormBuilderInterface $builder): void
262
     *     {
263
     *         $builder->string('foo');
264
     *     }
265
     *
266
     *     public function postConfigure(FormInterface $form): void
267
     *     {
268
     *         // Get the "foo" children
269
     *         $this->foo = $form['foo'];
270
     *     }
271
     * }
272
     * </code>
273
     *
274
     * @param FormInterface $form The inner form built instance
275
     */
276 41
    public function postConfigure(FormInterface $form): void
0 ignored issues
show
Unused Code introduced by
The parameter $form is not used and could be removed. ( Ignorable by Annotation )

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

276
    public function postConfigure(/** @scrutinizer ignore-unused */ FormInterface $form): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
277
    {
278
        // to overrides
279 41
    }
280
281
    /**
282
     * Define hooks called before the form is built
283
     * @param list<callable(static, FormBuilderInterface):void> $hooks
284
     * @return void
285
     * @internal This method should be called by the {@see CustomFormBuilder}
286
     */
287 16
    final public function setPreConfigureHooks(array $hooks): void
288
    {
289 16
        $this->preConfigureHooks = $hooks;
0 ignored issues
show
Documentation Bug introduced by
It seems like $hooks of type array is incompatible with the declared type Bdf\Form\Custom\list of property $preConfigureHooks.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
290 16
    }
291
292
    /**
293
     * Define hooks hook called after the form is built
294
     * @param list<callable(static, FormInterface<T>):void> $hooks
295
     * @return void
296
     * @internal This method should be called by the {@see CustomFormBuilder}
297
     */
298 16
    final public function setPostConfigureHooks(array $hooks): void
299
    {
300 16
        $this->postConfigureHooks = $hooks;
0 ignored issues
show
Documentation Bug introduced by
It seems like $hooks of type array is incompatible with the declared type Bdf\Form\Custom\list of property $postConfigureHooks.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
301 16
    }
302
303
    /**
304
     * Get (or build) the inner form
305
     *
306
     * @return FormInterface<T>
307
     */
308 42
    final protected function form(): FormInterface
309
    {
310 42
        if ($this->form) {
311 38
            return $this->form;
312
        }
313
314
        // Form can be rebuilt, so we need to clone the builder to avoid side effects
315 42
        $builder = clone $this->builder;
316
317
        /** @var static $this Psalm cannot infer this type */
318
319 42
        foreach ($this->preConfigureHooks as $hook) {
320 1
            $hook($this, $builder);
321
        }
322
323
        /** @psalm-suppress ArgumentTypeCoercion */
324 42
        $this->configure($builder);
325
326 42
        $form = $builder->buildElement();
327
328 42
        if ($this->container && $container = $this->container->get()) {
329 7
            $form = $form->setContainer($container);
330
        }
331
332 42
        $this->form = $form;
333
334 42
        $this->postConfigure($form);
335
336 42
        foreach ($this->postConfigureHooks as $hook) {
337 1
            $hook($this, $form);
338
        }
339
340 42
        return $form;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $form returns the type Bdf\Form\ElementInterface which includes types incompatible with the type-hinted return Bdf\Form\Aggregate\FormInterface.
Loading history...
341
    }
342
343
    /**
344
     * Get the submit target element
345
     * This element must be used for all submit or patch call
346
     * Handle submit button if the current form is the root element
347
     *
348
     * @return ElementInterface
349
     */
350 29
    final protected function submitTarget(): ElementInterface
351
    {
352 29
        $form = $this->form();
353
354
        // The form is the root form
355 29
        if ($form->container() === null) {
356 27
            return $form->root();
357
        }
358
359 6
        return $form;
360
    }
361
}
362