FormError::null()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 3
c 1
b 0
f 0
dl 0
loc 7
ccs 4
cts 4
cp 1
rs 10
cc 2
nc 2
nop 0
crap 2
1
<?php
2
3
namespace Bdf\Form\Error;
4
5
use Bdf\Form\Child\ChildInterface;
6
use Bdf\Form\Child\Http\HttpFieldPath;
7
use Bdf\Form\ElementInterface;
8
use InvalidArgumentException;
9
use Stringable;
10
use Symfony\Component\Validator\ConstraintViolation;
11
use Symfony\Component\Validator\ConstraintViolationInterface;
12
13
/**
14
 * Store errors of a form element
15
 *
16
 * @see ElementInterface::error()
17
 * @see ChildInterface::error()
18
 */
19
final class FormError implements Stringable
20
{
21
    /**
22
     * @var FormError|null
23
     */
24
    private static $null;
25
26
    /**
27
     * @var HttpFieldPath|null
28
     */
29
    private $field;
30
31
    /**
32
     * @var string|null
33
     */
34
    private $global;
35
36
    /**
37
     * @var string|null
38
     */
39
    private $code;
40
41
    /**
42
     * @var FormError[]
43
     */
44
    private $children;
45
46
47
    /**
48
     * FormError constructor.
49
     * Prefer use static methods to instantiate the FormError
50
     *
51
     * @param string|null $global
52
     * @param string|null $code
53
     * @param FormError[] $children
54
     */
55 233
    public function __construct(?string $global, ?string $code, array $children)
56
    {
57 233
        $this->global = $global;
58 233
        $this->code = $code;
59 233
        $this->children = $children;
60 233
    }
61
62
    /**
63
     * Get the HTTP field name of the current element
64
     *
65
     * @return HttpFieldPath|null The field path, or null is it's on the root element
66
     */
67 12
    public function field(): ?HttpFieldPath
68
    {
69 12
        return $this->field;
70
    }
71
72
    /**
73
     * Get the error of the current element, or the global error on an aggregate element
74
     *
75
     * @return string|null The error message, or null if there is no errors
76
     */
77 199
    public function global(): ?string
78
    {
79 199
        return $this->global;
80
    }
81
82
    /**
83
     * The error code of the current element
84
     *
85
     * @return string|null The error code, or null if not provided
86
     */
87 41
    public function code(): ?string
88
    {
89 41
        return $this->code;
90
    }
91
92
    /**
93
     * Get the children's errors
94
     * The errors are indexed by the child's name
95
     * Contains only non-empty errors
96
     *
97
     * @return FormError[]
98
     */
99 16
    public function children(): array
100
    {
101 16
        return $this->children;
102
    }
103
104
    /**
105
     * Check if the error object is an empty one
106
     *
107
     * If true, there is no errors, neither on current elements, nor on children
108
     * An element returning an empty error is a valid one
109
     *
110
     * @return bool
111
     */
112 362
    public function empty(): bool
113
    {
114 362
        return empty($this->global) && empty($this->code) && empty($this->children);
115
    }
116
117
    /**
118
     * Export the errors into an array
119
     *
120
     * - The errors are indexed by the children's name
121
     * - If a child contains a "global" error, the error value will be the global error
122
     * - Else, call toArray() on the child's error
123
     * - If the current element contains a global error, return it at the int(0) index
124
     *
125
     * @return array
126
     */
127 14
    public function toArray(): array
128
    {
129 14
        $errors = [];
130
131 14
        if ($this->global) {
132 3
            $errors[0] = $this->global;
133
        }
134
135 14
        foreach ($this->children as $name => $child) {
136 12
            if ($child->global) {
137 12
                $errors[$name] = $child->global;
138
            } else {
139 2
                $errors[$name] = $child->toArray();
140
            }
141
        }
142
143 14
        return $errors;
144
    }
145
146
    /**
147
     * Format the error using the given printer
148
     *
149
     * @param FormErrorPrinterInterface $printer
150
     *
151
     * @return mixed The printer result
152
     */
153 20
    public function print(FormErrorPrinterInterface $printer)
154
    {
155 20
        if ($this->field) {
156 12
            $printer->field($this->field);
157
        }
158
159 20
        if ($this->global) {
160 18
            $printer->global($this->global);
161
        }
162
163 20
        if ($this->code) {
164 15
            $printer->code($this->code);
165
        }
166
167 20
        foreach ($this->children as $name => $child) {
168 14
            $printer->child($name, $child);
169
        }
170
171 20
        return $printer->print();
172
    }
173
174
    /**
175
     * Format errors as string
176
     *
177
     * @return string
178
     */
179 4
    public function __toString(): string
180
    {
181 4
        return $this->print(new StringErrorPrinter());
182
    }
183
184
    /**
185
     * Add the HTTP field on the form error
186
     * The field will be applied to all children as prefix
187
     *
188
     * @param HttpFieldPath $field The current element field
189
     *
190
     * @return FormError The new FormError instance
191
     */
192 54
    public function withField(HttpFieldPath $field): FormError
193
    {
194 54
        $error = clone $this;
195
196 54
        $error->field = $field;
197 54
        $error->children = [];
198
199 54
        foreach ($this->children as $name => $child) {
200 14
            $error->children[$name] = $child->withPrefixField($field);
201
        }
202
203 54
        return $error;
204
    }
205
206
    /**
207
     * Get an empty error instance
208
     *
209
     * @return FormError
210
     */
211 743
    public static function null(): FormError
212
    {
213 743
        if (self::$null) {
214 743
            return self::$null;
215
        }
216
217 1
        return self::$null = new FormError(null, null, []);
218
    }
219
220
    /**
221
     * Creates an error containing only the global message
222
     *
223
     * @param string $message The error message
224
     * @param string|null $code The error code
225
     *
226
     * @return FormError
227
     */
228 232
    public static function message(string $message, ?string $code = null): FormError
229
    {
230 232
        return new FormError($message, $code, []);
231
    }
232
233
    /**
234
     * Creates an aggregation of children errors
235
     *
236
     * @param FormError[] $errors The children errors, indexed by the child name
237
     *
238
     * @return FormError
239
     */
240 44
    public static function aggregate(array $errors): FormError
241
    {
242 44
        return new FormError(null, null, $errors);
243
    }
244
245
    /**
246
     * Create a form error from a symfony violation
247
     *
248
     * @param ConstraintViolationInterface $violation The violation instance
249
     *
250
     * @return FormError
251
     */
252 219
    public static function violation(ConstraintViolationInterface $violation): FormError
253
    {
254 219
        $message = (string) $violation->getMessage();
255 219
        $code = $violation->getCode();
256
257 219
        if ($code !== null && $violation instanceof ConstraintViolation && ($constraint = $violation->getConstraint()) !== null) {
258
            try {
259 219
                $code = $constraint->getErrorName($code);
260 40
            } catch (InvalidArgumentException $e) {
261
                // Ignore error
262
            }
263
        }
264
265 219
        return self::message($message, $code);
266
    }
267
268
    /**
269
     * Add recursively a prefix field on children
270
     *
271
     * @param HttpFieldPath $prefix Prefix to add
272
     *
273
     * @return FormError
274
     */
275 14
    private function withPrefixField(HttpFieldPath $prefix)
276
    {
277 14
        $error = clone $this;
278
279 14
        $error->field = $this->field ? $prefix->concat($this->field) : $prefix;
280 14
        $error->children = [];
281
282 14
        foreach ($this->children as $name => $child) {
283 3
            $error->children[$name] = $child->withPrefixField($prefix);
284
        }
285
286 14
        return $error;
287
    }
288
}
289