Passed
Push — master ( 0a4514...70449d )
by Vincent
04:28
created

LeafElement::submit()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 11
c 1
b 0
f 0
dl 0
loc 19
ccs 11
cts 11
cp 1
rs 9.9
cc 3
nc 4
nop 1
crap 3
1
<?php
2
3
namespace Bdf\Form\Leaf;
4
5
use Bdf\Form\Child\Http\HttpFieldPath;
6
use Bdf\Form\Choice\Choiceable;
7
use Bdf\Form\Choice\ChoiceInterface;
8
use Bdf\Form\Choice\ChoiceView;
9
use Bdf\Form\ElementInterface;
10
use Bdf\Form\Error\FormError;
11
use Bdf\Form\Leaf\View\SimpleElementView;
12
use Bdf\Form\RootElementInterface;
13
use Bdf\Form\Transformer\NullTransformer;
14
use Bdf\Form\Transformer\TransformerInterface;
15
use Bdf\Form\Util\ContainerTrait;
16
use Bdf\Form\Validator\ConstraintValueValidator;
17
use Bdf\Form\Validator\ValueValidatorInterface;
18
use Bdf\Form\View\ConstraintsNormalizer;
19
use Bdf\Form\View\ElementViewInterface;
20
use Bdf\Form\View\FieldViewInterface;
21
use Exception;
22
use Symfony\Component\Validator\Constraints\NotBlank;
23
24
/**
25
 * Form element containing a single value
26
 *
27
 * @template T
28
 *
29
 * @implements ElementInterface<T>
30
 * @implements Choiceable<T>
31
 */
32
abstract class LeafElement implements ElementInterface, Choiceable
33
{
34
    use ContainerTrait;
35
36
    /**
37
     * @var ValueValidatorInterface<T>
38
     */
39
    private $validator;
40
41
    /**
42
     * Transformer to view value
43
     *
44
     * @var TransformerInterface
45
     */
46
    private $transformer;
47
48
    /**
49
     * @var ChoiceInterface<T>|null
50
     */
51
    private $choices;
52
53
    /**
54
     * @var T|null
0 ignored issues
show
Bug introduced by
The type Bdf\Form\Leaf\T 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...
55
     */
56
    private $value = null;
57
58
    /**
59
     * @var FormError
60
     */
61
    private $error;
62
63
    /**
64
     * @var bool
65
     */
66
    private $submitted = false;
67
68
69
    /**
70
     * LeafElement constructor.
71
     *
72
     * @param ValueValidatorInterface<T>|null $validator
73
     * @param TransformerInterface|null $transformer
74
     * @param ChoiceInterface<T>|null $choices
75
     */
76 696
    public function __construct(?ValueValidatorInterface $validator = null, ?TransformerInterface $transformer = null, ?ChoiceInterface $choices = null)
77
    {
78 696
        $this->validator = $validator ?: ConstraintValueValidator::empty();
79 696
        $this->transformer = $transformer ?: NullTransformer::instance();
80 696
        $this->error = FormError::null();
81 696
        $this->choices = $choices;
82 696
    }
83
84
    /**
85
     * {@inheritdoc}
86
     */
87 374
    final public function submit($data): ElementInterface
88
    {
89 374
        $shouldBeValidated = true;
90
91
        try {
92 374
            $this->submitted = true;
93 374
            $this->value = $this->toPhp($this->transformer->transformFromHttp($this->sanitize($data), $this));
94 28
        } catch (Exception $e) {
95 28
            $this->error = $this->validator->onTransformerException($e, $data, $this);
96 28
            $this->value = $data; // @todo null ? keep original ?
97 28
            $shouldBeValidated = $this->error->empty();
98
        }
99
100
        // Only validate on successfully transformation or if the transformation error is ignored
101 374
        if ($shouldBeValidated) {
102 359
            $this->error = $this->validator->validate($this->value, $this);
103
        }
104
105 374
        return $this;
106
    }
107
108
    /**
109
     * {@inheritdoc}
110
     */
111 30
    final public function patch($data): ElementInterface
112
    {
113
        // A data is provided : simply submit the data
114 30
        if ($data !== null) {
115 14
            return $this->submit($data);
116
        }
117
118 23
        $this->submitted = true;
119
        // Revalidate the element
120 23
        $this->error = $this->validator->validate($this->value, $this);
121
122 23
        return $this;
123
    }
124
125
    /**
126
     * {@inheritdoc}
127
     */
128 313
    final public function valid(): bool
129
    {
130 313
        return $this->submitted && $this->error->empty();
131
    }
132
133
    /**
134
     * {@inheritdoc}
135
     */
136 176
    final public function error(?HttpFieldPath $field = null): FormError
137
    {
138 176
        return $field ? $this->error->withField($field) : $this->error;
139
    }
140
141
    /**
142
     * {@inheritdoc}
143
     */
144 189
    final public function import($entity): ElementInterface
145
    {
146 189
        $this->value = $this->tryCast($entity);
147
148 150
        return $this;
149
    }
150
151
    /**
152
     * {@inheritdoc}
153
     */
154 352
    final public function value()
155
    {
156 352
        return $this->value;
157
    }
158
159
    /**
160
     * {@inheritdoc}
161
     */
162 95
    final public function httpValue()
163
    {
164
        try {
165 95
            return $this->transformer->transformToHttp($this->toHttp($this->value), $this);
166
        } catch (Exception $e) {
167
            return $this->value;
168
        }
169
    }
170
171
    /**
172
     * {@inheritdoc}
173
     */
174 234
    final public function root(): RootElementInterface
175
    {
176
        // @todo save the root ?
177 234
        if (!$this->container) {
178 159
            return new LeafRootElement($this);
179
        }
180
181 75
        return $this->container->parent()->root();
182
    }
183
184
    /**
185
     * {@inheritdoc}
186
     *
187
     * @return FieldViewInterface
188
     */
189 42
    public function view(?HttpFieldPath $field = null): ElementViewInterface
190
    {
191 42
        $normalizedConstraints = ConstraintsNormalizer::normalize($this->validator);
192
193 42
        return new SimpleElementView(
194 42
            static::class,
195 42
            (string) $field,
196 42
            $this->httpValue(),
197 42
            $this->error->global(),
198 42
            isset($normalizedConstraints[NotBlank::class]),
199 42
            $normalizedConstraints,
200 42
            $this->choiceView()
201
        );
202
    }
203
204
    /**
205
     * {@inheritdoc}
206
     */
207 4
    final public function choices(): ?ChoiceInterface
208
    {
209 4
        return $this->choices;
210
    }
211
212
    /**
213
     * Cast the HTTP value to the PHP value
214
     *
215
     * @param mixed $httpValue
216
     *
217
     * @return T|null
218
     */
219
    abstract protected function toPhp($httpValue);
220
221
    /**
222
     * Transform the PHP value to the HTTP representation
223
     *
224
     * @param T|null $phpValue
225
     *
226
     * @return mixed
227
     */
228
    abstract protected function toHttp($phpValue);
229
230
    /**
231
     * Try to convert the value into the element type
232
     *
233
     * @param mixed $value Value to cast
234
     * @psalm-assert T|null $value
235
     *
236
     * @return T|null
237
     *
238
     * @throws \TypeError If the value type is not supported by the element
239
     *
240
     * @see LeafElement::import()
241
     */
242 6
    protected function tryCast($value)
243
    {
244 6
        return $value;
245
    }
246
247
    /**
248
     * Sanitize the raw HTTP value
249
     *
250
     * @param mixed $rawValue The raw HTTP value
251
     *
252
     * @return string|null
253
     */
254 352
    protected function sanitize($rawValue)
255
    {
256 352
        if (is_scalar($rawValue)) {
257 310
            return (string) $rawValue;
258
        }
259
260
        // Leaf element supports only scalar values
261 51
        return null;
262
    }
263
264
    /**
265
     * Get the choice view and apply value transformation and selected value
266
     *
267
     * @return array|null
268
     * @see ChoiceInterface::view()
269
     */
270 41
    protected function choiceView(): ?array
271
    {
272 41
        if ($this->choices === null) {
273 34
            return null;
274
        }
275
276
        return $this->choices->view(function (ChoiceView $view) {
277 7
            $view->setSelected($view->value() == $this->value());
278 7
            $view->setValue($this->transformer->transformToHttp($this->toHttp($view->value()), $this));
279 7
        });
280
    }
281
}
282