Completed
Push — 2.0 ( 4df6fa...9a9bd1 )
by Olivier
02:03
created

ErrorCollection::merge()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 7
rs 9.4285
cc 2
eloc 3
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|null $attribute Attribute name or `null` for _generic_.
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->collection[$attribute ?: self::GENERIC][] = $this
43
			->ensure_error_instance($error_or_format_or_true, $args);
44
45
		return $this;
46
	}
47
48
	/**
49
	 * Add an error not associated with any attribute.
50
	 *
51
	 * @param Error|string|bool $error_or_format_or_true A {@link Error} instance or
52
	 * a format to create that instance, or `true`.
53
	 * @param array $args Only used if `$error_or_format_or_true` is not a {@link Error}
54
	 * instance or `true`.
55
	 *
56
	 * @return $this
57
	 */
58
	public function add_generic($error_or_format_or_true = true, array $args = [])
59
	{
60
		return $this->add(null, $error_or_format_or_true, $args);
61
	}
62
63
	/**
64
	 * Ensures a {@link Error} instance.
65
	 *
66
	 * @param Error|string|bool $error_or_format_or_true
67
	 * @param array $args
68
	 *
69
	 * @return Error
70
	 */
71
	protected function ensure_error_instance($error_or_format_or_true, array $args = [])
72
	{
73
		$error = $error_or_format_or_true;
74
75
		if (!$error instanceof Error)
76
		{
77
			$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...
78
		}
79
80
		return $error;
81
	}
82
83
	/**
84
	 * Adds an error.
85
	 *
86
	 * @param string $attribute
87
	 * @param Error|string|true $error
88
	 *
89
	 * @see add()
90
	 */
91
	public function offsetSet($attribute, $error)
92
	{
93
		$this->add($attribute, $error);
94
	}
95
96
	/**
97
	 * Clears the errors of an attribute.
98
	 *
99
	 * @param string|null $attribute Attribute name or `null` for _generic_.
100
	 */
101
	public function offsetUnset($attribute)
102
	{
103
		unset($this->collection[$attribute ?: self::GENERIC]);
104
	}
105
106
	/**
107
	 * Checks if an error is defined for an attribute.
108
	 *
109
	 * ```php
110
	 * <?php
111
	 *
112
	 * use ICanBoogie\ErrorCollection
113
	 *
114
	 * $errors = new ErrorCollection;
115
	 * isset($errors['username']);
116
	 * // false
117
	 * $errors->add('username');
118
	 * isset($errors['username']);
119
	 * // true
120
	 * ```
121
	 *
122
	 * @param string|null $attribute Attribute name or `null` for _generic_.
123
	 *
124
	 * @return boolean true if an error is defined for the specified attribute, false otherwise.
125
	 */
126
	public function offsetExists($attribute)
127
	{
128
		return isset($this->collection[$attribute ?: self::GENERIC]);
129
	}
130
131
	/**
132
	 * Returns errors associated with an attribute.
133
	 *
134
	 * ```php
135
	 * <?php
136
	 *
137
	 * use ICanBoogie\ErrorCollection;
138
	 *
139
	 * $errors = new ErrorCollection;
140
	 * $errors['password']
141
	 * // []
142
	 * $errors->add('password')
143
	 * // [ Message ]
144
	 * ```
145
	 *
146
	 * @param string|null $attribute Attribute name or `null` for _generic_.
147
	 *
148
	 * @return Error[]
149
	 */
150
	public function offsetGet($attribute)
151
	{
152
		if (!$this->offsetExists($attribute))
153
		{
154
			return [];
155
		}
156
157
		return $this->collection[$attribute ?: self::GENERIC];
158
	}
159
160
	/**
161
	 * Clears errors.
162
	 *
163
	 * @return $this
164
	 */
165
	public function clear()
166
	{
167
		$this->collection = [];
168
169
		return $this;
170
	}
171
172
	/**
173
	 * Merges with another error collection.
174
	 *
175
	 * @param ErrorCollection $collection
176
	 */
177
	public function merge(ErrorCollection $collection)
178
	{
179
		foreach ($collection as $attribute => $error)
180
		{
181
			$this->add($attribute, $error);
182
		}
183
	}
184
185
	/**
186
	 * @inheritdoc
187
	 */
188
	public function getIterator()
189
	{
190
		foreach ($this->to_array() as $attribute => $errors)
191
		{
192
			foreach ($errors as $error)
193
			{
194
				yield $attribute => $error;
195
			}
196
		}
197
	}
198
199
	/**
200
	 * Iterates through errors using a callback.
201
	 *
202
	 * ```php
203
	 * <?php
204
	 *
205
	 * use ICanBoogie\ErrorCollection;
206
	 *
207
	 * $errors = new ErrorCollection;
208
	 * $errors->add('username', "Funny user name");
209
	 * $errors->add('password', "Weak password");
210
	 *
211
	 * $errors->each(function ($error, $attribute, $errors) {
212
	 *
213
	 *     echo "$attribute => $error<br />";
214
	 *
215
	 * });
216
	 * </pre>
217
	 *
218
	 * @param callable $callback Function to execute for each element, taking three arguments:
219
	 *
220
	 * - `Error $error`: The current error.
221
	 * - `string $attribute`: The attribute or {@link self::GENERIC}.
222
	 * - `ErrorCollection $collection`: This instance.
223
	 */
224
	public function each(callable $callback)
225
	{
226
		foreach ($this as $attribute => $error)
227
		{
228
			$callback($error, $attribute, $this);
229
		}
230
	}
231
232
	/**
233
	 * Returns the total number of errors.
234
	 *
235
	 * @inheritdoc
236
	 */
237
	public function count()
238
	{
239
		$n = 0;
240
241
		foreach ($this->collection as $errors)
242
		{
243
			$n += count($errors);
244
		}
245
246
		return $n;
247
	}
248
249
	/**
250
	 * @inheritdoc
251
	 */
252
	public function jsonSerialize()
253
	{
254
		return $this->to_array();
255
	}
256
257
	/**
258
	 * Converts the object into an array.
259
	 *
260
	 * @return Error[][]
261
	 */
262
	public function to_array()
263
	{
264
		return array_filter(array_merge([ self::GENERIC => [] ], $this->collection));
265
	}
266
}
267