Completed
Push — 2.0 ( 9a9bd1...98f64c )
by Olivier
02:55
created

ErrorCollection::assert_valid_error()   B

Complexity

Conditions 5
Paths 2

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 16
rs 8.8571
cc 5
eloc 11
nc 2
nop 1
1
<?php
2
3
/*
4
 * This file is part of the ICanBoogie package.
5
 *
6
 * (c) Olivier Laviale <[email protected]>
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
/**
15
 * An error collection.
16
 */
17
class ErrorCollection implements \ArrayAccess, \IteratorAggregate, \Countable, \JsonSerializable, ToArray
18
{
19
	/**
20
	 * Special identifier used when an error is not associated with a specific attribute.
21
	 */
22
	const GENERIC = '__generic__';
23
24
	/**
25
	 * @var Error[][]
26
	 */
27
	private $collection = [];
28
29
	/**
30
	 * Add an error associated with an attribute.
31
	 *
32
	 * @param string $attribute Attribute name.
33
	 * @param Error|string|bool $error_or_format_or_true A {@link Error} instance or
34
	 * a format to create that instance, or `true`.
35
	 * @param array $args Only used if `$error_or_format_or_true` is not a {@link Error}
36
	 * instance or `true`.
37
	 *
38
	 * @return $this
39
	 */
40
	public function add($attribute, $error_or_format_or_true = true, array $args = [])
41
	{
42
		$this->assert_valid_attribute($attribute);
43
		$this->assert_valid_error($error_or_format_or_true);
44
45
		$this->collection[$attribute][] = $this
46
			->ensure_error_instance($error_or_format_or_true, $args);
47
48
		return $this;
49
	}
50
51
	/**
52
	 * Add an error not associated with any attribute.
53
	 *
54
	 * @param Error|string|bool $error_or_format_or_true A {@link Error} instance or
55
	 * a format to create that instance, or `true`.
56
	 * @param array $args Only used if `$error_or_format_or_true` is not a {@link Error}
57
	 * instance or `true`.
58
	 *
59
	 * @return $this
60
	 */
61
	public function add_generic($error_or_format_or_true = true, array $args = [])
62
	{
63
		return $this->add(self::GENERIC, $error_or_format_or_true, $args);
64
	}
65
66
	/**
67
	 * Asserts that an attribute is valid.
68
	 *
69
	 * @param string $attribute
70
	 */
71
	protected function assert_valid_attribute($attribute)
72
	{
73
		if (is_string($attribute)) {
74
			return;
75
		}
76
77
		throw new \InvalidArgumentException(sprintf(
78
			"\$attribute must a string. Given: `%s`",
79
			Error::class,
80
			is_object($attribute)
81
				? get_class($attribute)
82
				: gettype($attribute)
83
		));
84
	}
85
86
	/**
87
	 * Asserts that the error type is valid.
88
	 *
89
	 * @param mixed $error_or_format_or_true
90
	 */
91
	protected function assert_valid_error($error_or_format_or_true)
92
	{
93
		if ($error_or_format_or_true === true
94
		|| is_string($error_or_format_or_true)
95
		|| $error_or_format_or_true instanceof Error) {
96
			return;
97
		}
98
99
		throw new \InvalidArgumentException(sprintf(
100
			"\$error_or_format_or_true must be a an instance of `%s`, a string, or true. Given: `%s`",
101
			Error::class,
102
			is_object($error_or_format_or_true)
103
				? get_class($error_or_format_or_true)
104
				: gettype($error_or_format_or_true)
105
		));
106
	}
107
108
	/**
109
	 * Ensures a {@link Error} instance.
110
	 *
111
	 * @param Error|string|bool $error_or_format_or_true
112
	 * @param array $args
113
	 *
114
	 * @return Error
115
	 */
116
	protected function ensure_error_instance($error_or_format_or_true, array $args = [])
117
	{
118
		$error = $error_or_format_or_true;
119
120
		if (!$error instanceof Error)
121
		{
122
			$error = new Error($error === true ? "" : $error, $args);
0 ignored issues
show
Bug introduced by
It seems like $error === true ? '' : $error can also be of type boolean; however, ICanBoogie\Error::__construct() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
123
		}
124
125
		return $error;
126
	}
127
128
	/**
129
	 * Adds an error.
130
	 *
131
	 * @param string $attribute
132
	 * @param Error|string|true $error
133
	 *
134
	 * @see add()
135
	 */
136
	public function offsetSet($attribute, $error)
137
	{
138
		$this->add($attribute === null ? self::GENERIC : $attribute, $error);
139
	}
140
141
	/**
142
	 * Clears the errors of an attribute.
143
	 *
144
	 * @param string|null $attribute Attribute name or `null` for _generic_.
145
	 */
146
	public function offsetUnset($attribute)
147
	{
148
		unset($this->collection[$attribute === null ? self::GENERIC : $attribute]);
149
	}
150
151
	/**
152
	 * Checks if an error is defined for an attribute.
153
	 *
154
	 * ```php
155
	 * <?php
156
	 *
157
	 * use ICanBoogie\ErrorCollection
158
	 *
159
	 * $errors = new ErrorCollection;
160
	 * isset($errors['username']);
161
	 * // false
162
	 * $errors->add('username');
163
	 * isset($errors['username']);
164
	 * // true
165
	 * ```
166
	 *
167
	 * @param string|null $attribute Attribute name or `null` for _generic_.
168
	 *
169
	 * @return boolean true if an error is defined for the specified attribute, false otherwise.
170
	 */
171
	public function offsetExists($attribute)
172
	{
173
		return isset($this->collection[$attribute === null ? self::GENERIC : $attribute]);
174
	}
175
176
	/**
177
	 * Returns errors associated with an attribute.
178
	 *
179
	 * ```php
180
	 * <?php
181
	 *
182
	 * use ICanBoogie\ErrorCollection;
183
	 *
184
	 * $errors = new ErrorCollection;
185
	 * $errors['password']
186
	 * // []
187
	 * $errors->add('password')
188
	 * // [ Message ]
189
	 * ```
190
	 *
191
	 * @param string|null $attribute Attribute name or `null` for _generic_.
192
	 *
193
	 * @return Error[]
194
	 */
195
	public function offsetGet($attribute)
196
	{
197
		if (!$this->offsetExists($attribute))
198
		{
199
			return [];
200
		}
201
202
		return $this->collection[$attribute === null ? self::GENERIC : $attribute];
203
	}
204
205
	/**
206
	 * Clears errors.
207
	 *
208
	 * @return $this
209
	 */
210
	public function clear()
211
	{
212
		$this->collection = [];
213
214
		return $this;
215
	}
216
217
	/**
218
	 * Merges with another error collection.
219
	 *
220
	 * @param ErrorCollection $collection
221
	 */
222
	public function merge(ErrorCollection $collection)
223
	{
224
		foreach ($collection as $attribute => $error)
225
		{
226
			$this->add($attribute, $error);
227
		}
228
	}
229
230
	/**
231
	 * @inheritdoc
232
	 */
233
	public function getIterator()
234
	{
235
		foreach ($this->to_array() as $attribute => $errors)
236
		{
237
			foreach ($errors as $error)
238
			{
239
				yield $attribute => $error;
240
			}
241
		}
242
	}
243
244
	/**
245
	 * Iterates through errors using a callback.
246
	 *
247
	 * ```php
248
	 * <?php
249
	 *
250
	 * use ICanBoogie\ErrorCollection;
251
	 *
252
	 * $errors = new ErrorCollection;
253
	 * $errors->add('username', "Funny user name");
254
	 * $errors->add('password', "Weak password");
255
	 *
256
	 * $errors->each(function ($error, $attribute, $errors) {
257
	 *
258
	 *     echo "$attribute => $error<br />";
259
	 *
260
	 * });
261
	 * </pre>
262
	 *
263
	 * @param callable $callback Function to execute for each element, taking three arguments:
264
	 *
265
	 * - `Error $error`: The current error.
266
	 * - `string $attribute`: The attribute or {@link self::GENERIC}.
267
	 * - `ErrorCollection $collection`: This instance.
268
	 */
269
	public function each(callable $callback)
270
	{
271
		foreach ($this as $attribute => $error)
272
		{
273
			$callback($error, $attribute, $this);
274
		}
275
	}
276
277
	/**
278
	 * Returns the total number of errors.
279
	 *
280
	 * @inheritdoc
281
	 */
282
	public function count()
283
	{
284
		$n = 0;
285
286
		foreach ($this->collection as $errors)
287
		{
288
			$n += count($errors);
289
		}
290
291
		return $n;
292
	}
293
294
	/**
295
	 * @inheritdoc
296
	 */
297
	public function jsonSerialize()
298
	{
299
		return $this->to_array();
300
	}
301
302
	/**
303
	 * Converts the object into an array.
304
	 *
305
	 * @return Error[][]
306
	 */
307
	public function to_array()
308
	{
309
		return array_filter(array_merge([ self::GENERIC => [] ], $this->collection));
310
	}
311
}
312