• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ICanBoogie / Errors / 4189431805

pending completion
4189431805

push

github

Olivier Laviale
Add Code of Conduct

44 of 49 relevant lines covered (89.8%)

5.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.49
/lib/ErrorCollection.php
1
<?php
2

3
/*
4
 * This file is part of the ICanBoogie package.
5
 *
6
 * (c) Olivier Laviale <olivier.laviale@gmail.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11

12
namespace ICanBoogie;
13

14
use ArrayAccess;
15
use Countable;
16
use InvalidArgumentException;
17
use IteratorAggregate;
18
use JsonSerializable;
19
use Throwable;
20

21
use function get_debug_type;
22

23
/**
24
 * An error collection.
25
 *
26
 * @implements ArrayAccess<string, Error[]>
27
 * @implements IteratorAggregate<string, Error>
28
 */
29
class ErrorCollection implements ArrayAccess, IteratorAggregate, Countable, JsonSerializable, ToArray
30
{
31
    /**
32
     * Special identifier used when an error is not associated with a specific attribute.
33
     */
34
    public const GENERIC = '__generic__';
35

36
    /**
37
     * @var array<string, Error[]>
38
     */
39
    private array $collection = [];
40

41
    /**
42
     * Add an error associated with an attribute.
43
     *
44
     * @param string $attribute Attribute name.
45
     * @param Throwable|bool|string|Error $error_or_format_or_true A {@link Error} instance or
46
     * a format to create that instance, or `true`.
47
     * @param array<int|string, mixed> $args Only used if `$error_or_format_or_true` is not a {@link Error}
48
     * instance or `true`.
49
     *
50
     * @return $this
51
     */
52
    public function add(
53
        string $attribute,
54
        Throwable|bool|string|Error $error_or_format_or_true = true,
55
        array $args = []
56
    ): static {
57
        $this->assert_valid_error($error_or_format_or_true);
14✔
58

59
        $this->collection[$attribute][] = $this
14✔
60
            ->ensure_error_instance($error_or_format_or_true, $args);
14✔
61

62
        return $this;
14✔
63
    }
64

65
    /**
66
     * Add an error not associated with any attribute.
67
     *
68
     * @param Throwable|bool|string|Error $error_or_format_or_true A {@link Error} instance or
69
     * a format to create that instance, or `true`.
70
     * @param array<int|string, mixed> $args Only used if `$error_or_format_or_true` is not a {@link Error}
71
     * instance or `true`.
72
     *
73
     * @return $this
74
     */
75
    public function add_generic(
76
        Throwable|bool|string|Error $error_or_format_or_true = true,
77
        array $args = []
78
    ): static {
79
        return $this->add(self::GENERIC, $error_or_format_or_true, $args);
5✔
80
    }
81

82
    /**
83
     * Asserts that the error type is valid.
84
     *
85
     * @param mixed $error_or_format_or_true
86
     */
87
    private function assert_valid_error(mixed $error_or_format_or_true): void
88
    {
89
        if (
90
            $error_or_format_or_true === true
14✔
91
            || is_string($error_or_format_or_true)
12✔
92
            || $error_or_format_or_true instanceof Error
8✔
93
            || $error_or_format_or_true instanceof Throwable
14✔
94
        ) {
95
            return;
14✔
96
        }
97

98
        throw new InvalidArgumentException(sprintf(
×
99
            "\$error_or_format_or_true must be a an instance of `%s`, a string, or true. Given: `%s`",
×
100
            Error::class,
×
101
            get_debug_type($error_or_format_or_true)
×
102
        ));
×
103
    }
104

105
    /**
106
     * Ensures a {@link Error} instance.
107
     *
108
     * @param Throwable|bool|string|Error $error_or_format_or_true
109
     * @param array<int|string, mixed> $args
110
     */
111
    private function ensure_error_instance(
112
        Throwable|bool|string|Error $error_or_format_or_true,
113
        array $args = []
114
    ): Error {
115
        $error = $error_or_format_or_true;
14✔
116

117
        if (!$error instanceof Error) {
14✔
118
            $error = new Error($error === true ? "" : (string) $error, $args);
8✔
119
        }
120

121
        return $error;
14✔
122
    }
123

124
    /**
125
     * Adds an error.
126
     *
127
     * @param string|null $offset An attribute name or `null` for _generic_.
128
     * @param Throwable|Error|string|bool $value An error.
129
     *
130
     * @see add()
131
     *
132
     * @phpstan-ignore-next-line
133
     */
134
    public function offsetSet($offset, $value): void
135
    {
136
        $this->add($offset ?? self::GENERIC, $value);
3✔
137
    }
138

139
    /**
140
     * Clears the errors of an attribute.
141
     *
142
     * @param string|null $offset An attribute name or `null` for _generic_.
143
     */
144
    public function offsetUnset($offset): void
145
    {
146
        unset($this->collection[$offset ?? self::GENERIC]);
1✔
147
    }
148

149
    /**
150
     * Checks if an error is defined for an attribute.
151
     *
152
     * ```php
153
     * <?php
154
     *
155
     * use ICanBoogie\ErrorCollection
156
     *
157
     * $errors = new ErrorCollection;
158
     * isset($errors['username']);
159
     * // false
160
     * $errors->add('username');
161
     * isset($errors['username']);
162
     * // true
163
     * ```
164
     *
165
     * @param string|null $offset An attribute name or `null` for _generic_.
166
     */
167
    public function offsetExists($offset): bool
168
    {
169
        return isset($this->collection[$offset ?? self::GENERIC]);
7✔
170
    }
171

172
    /**
173
     * Returns errors associated with an attribute.
174
     *
175
     * ```php
176
     * <?php
177
     *
178
     * use ICanBoogie\ErrorCollection;
179
     *
180
     * $errors = new ErrorCollection;
181
     * $errors['password']
182
     * // []
183
     * $errors->add('password')
184
     * // [ Message ]
185
     * ```
186
     *
187
     * @param string|null $offset An attribute name or `null` for _generic_.
188
     *
189
     * @return Error[]
190
     */
191
    public function offsetGet($offset): array
192
    {
193
        if (!$this->offsetExists($offset)) {
7✔
194
            return [];
1✔
195
        }
196

197
        return $this->collection[$offset ?? self::GENERIC];
7✔
198
    }
199

200
    /**
201
     * Clears errors.
202
     *
203
     * @return $this
204
     */
205
    public function clear(): static
206
    {
207
        $this->collection = [];
1✔
208

209
        return $this;
1✔
210
    }
211

212
    /**
213
     * Merges with another error collection.
214
     */
215
    public function merge(ErrorCollection $collection): void
216
    {
217
        foreach ($collection as $attribute => $error) {
1✔
218
            $this->add($attribute, $error);
1✔
219
        }
220
    }
221

222
    /**
223
     * @return iterable<string, Error>
224
     */
225
    public function getIterator(): iterable
226
    {
227
        foreach ($this->to_array() as $attribute => $errors) {
5✔
228
            foreach ($errors as $error) {
5✔
229
                yield $attribute => $error;
5✔
230
            }
231
        }
232
    }
233

234
    /**
235
     * Iterates through errors using a callback.
236
     *
237
     * ```php
238
     * <?php
239
     *
240
     * use ICanBoogie\ErrorCollection;
241
     *
242
     * $errors = new ErrorCollection;
243
     * $errors->add('username', "Funny user name");
244
     * $errors->add('password', "Weak password");
245
     *
246
     * $errors->each(function ($error, $attribute, $errors) {
247
     *
248
     *     echo "$attribute => $error\n";
249
     *
250
     * });
251
     * ```
252
     *
253
     * @param callable(Error, string $attribute, ErrorCollection): void $callback
254
     *     Function to execute for each element, taking three arguments:
255
     *
256
     *     - `Error $error`: The current error.
257
     *     - `string $attribute`: The attribute or {@link self::GENERIC}.
258
     *     - `ErrorCollection $collection`: This instance.
259
     */
260
    public function each(callable $callback): void
261
    {
262
        foreach ($this as $attribute => $error) {
1✔
263
            $callback($error, $attribute, $this);
1✔
264
        }
265
    }
266

267
    /**
268
     * Returns the total number of errors.
269
     *
270
     * @inheritDoc
271
     */
272
    public function count(): int
273
    {
274
        return count($this->collection, COUNT_RECURSIVE) - count($this->collection);
6✔
275
    }
276

277
    /**
278
     * @inheritdoc
279
     */
280
    public function jsonSerialize()
281
    {
282
        return $this->to_array();
1✔
283
    }
284

285
    /**
286
     * Converts the object into an array.
287
     *
288
     * @return array<string, Error[]>
289
     */
290
    public function to_array(): array
291
    {
292
        return array_filter(array_merge([ self::GENERIC => [] ], $this->collection));
6✔
293
    }
294
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc