Test Failed
Push — master ( ef12af...52cb59 )
by Vincent
06:49
created

FormError   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 268
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 32
eloc 60
c 1
b 0
f 0
dl 0
loc 268
ccs 47
cts 47
cp 1
rs 9.84

15 Methods

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