|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
/** |
|
4
|
|
|
* @author Jared King <[email protected]> |
|
5
|
|
|
* |
|
6
|
|
|
* @see http://jaredtking.com |
|
7
|
|
|
* |
|
8
|
|
|
* @copyright 2015 Jared King |
|
9
|
|
|
* @license MIT |
|
10
|
|
|
*/ |
|
11
|
|
|
|
|
12
|
|
|
namespace Pulsar; |
|
13
|
|
|
|
|
14
|
|
|
use ArrayAccess; |
|
15
|
|
|
use ArrayIterator; |
|
16
|
|
|
use Countable; |
|
17
|
|
|
use Infuse\Locale; |
|
18
|
|
|
use IteratorAggregate; |
|
19
|
|
|
|
|
20
|
|
|
/** |
|
21
|
|
|
* Holds error messages generated by models, like validation errors. |
|
22
|
|
|
*/ |
|
23
|
|
|
class Errors implements IteratorAggregate, Countable, ArrayAccess |
|
24
|
|
|
{ |
|
25
|
|
|
/** |
|
26
|
|
|
* @var array |
|
27
|
|
|
*/ |
|
28
|
|
|
private static $messages = [ |
|
29
|
|
|
'pulsar.validation.alpha' => '{{field_name}} only allows letters', |
|
30
|
|
|
'pulsar.validation.alpha_numeric' => '{{field_name}} only allows letters and numbers', |
|
31
|
|
|
'pulsar.validation.alpha_dash' => '{{field_name}} only allows letters and dashes', |
|
32
|
|
|
'pulsar.validation.boolean' => '{{field_name}} must be yes or no', |
|
33
|
|
|
'pulsar.validation.custom' => '{{field_name}} validation failed', |
|
34
|
|
|
'pulsar.validation.email' => '{{field_name}} must be a valid email address', |
|
35
|
|
|
'pulsar.validation.enum' => '{{field_name}} must be one of the allowed values', |
|
36
|
|
|
'pulsar.validation.date' => '{{field_name}} must be a date', |
|
37
|
|
|
'pulsar.validation.failed' => '{{field_name}} is invalid', |
|
38
|
|
|
'pulsar.validation.ip' => '{{field_name}} only allows valid IP addresses', |
|
39
|
|
|
'pulsar.validation.matching' => '{{field_name}} must match', |
|
40
|
|
|
'pulsar.validation.numeric' => '{{field_name}} only allows numbers', |
|
41
|
|
|
'pulsar.validation.password' => '{{field_name}} must meet the password requirements', |
|
42
|
|
|
'pulsar.validation.password_php' => '{{field_name}} must meet the password requirements', |
|
43
|
|
|
'pulsar.validation.range' => '{{field_name}} must be within the allowed range', |
|
44
|
|
|
'pulsar.validation.required' => '{{field_name}} is missing', |
|
45
|
|
|
'pulsar.validation.string' => '{{field_name}} must be a string of the proper length', |
|
46
|
|
|
'pulsar.validation.time_zone' => '{{field_name}} only allows valid time zones', |
|
47
|
|
|
'pulsar.validation.timestamp' => '{{field_name}} only allows timestamps', |
|
48
|
|
|
'pulsar.validation.unique' => 'The {{field_name}} you chose has already been taken. Please try a different {{field_name}}.', |
|
49
|
|
|
'pulsar.validation.url' => '{{field_name}} only allows valid URLs', |
|
50
|
|
|
]; |
|
51
|
|
|
|
|
52
|
|
|
/** |
|
53
|
|
|
* @var Locale |
|
54
|
|
|
*/ |
|
55
|
|
|
private static $globalLocale; |
|
56
|
|
|
|
|
57
|
|
|
/** |
|
58
|
|
|
* @var array |
|
59
|
|
|
*/ |
|
60
|
|
|
private $stack = []; |
|
61
|
|
|
|
|
62
|
|
|
/** |
|
63
|
|
|
* @var Locale |
|
64
|
|
|
*/ |
|
65
|
|
|
private $locale; |
|
66
|
|
|
|
|
67
|
|
|
/** |
|
68
|
|
|
* Sets the global locale instance. |
|
69
|
|
|
* |
|
70
|
|
|
* @param Locale $locale |
|
71
|
|
|
*/ |
|
72
|
|
|
public static function setGlobalLocale(Locale $locale) |
|
73
|
|
|
{ |
|
74
|
|
|
self::$globalLocale = $locale; |
|
75
|
|
|
} |
|
76
|
|
|
|
|
77
|
|
|
/** |
|
78
|
|
|
* Sets the locale service. |
|
79
|
|
|
* |
|
80
|
|
|
* @param Locale $locale |
|
81
|
|
|
* |
|
82
|
|
|
* @return $this |
|
83
|
|
|
*/ |
|
84
|
|
|
public function setLocale(Locale $locale) |
|
85
|
|
|
{ |
|
86
|
|
|
$this->locale = $locale; |
|
87
|
|
|
|
|
88
|
|
|
return $this; |
|
89
|
|
|
} |
|
90
|
|
|
|
|
91
|
|
|
/** |
|
92
|
|
|
* Gets the locale service. |
|
93
|
|
|
* |
|
94
|
|
|
* @return Locale |
|
95
|
|
|
*/ |
|
96
|
|
|
public function getLocale() |
|
97
|
|
|
{ |
|
98
|
|
|
if (!$this->locale) { |
|
99
|
|
|
if (self::$globalLocale) { |
|
100
|
|
|
return self::$globalLocale; |
|
101
|
|
|
} |
|
102
|
|
|
|
|
103
|
|
|
$this->locale = new Locale(); |
|
104
|
|
|
} |
|
105
|
|
|
|
|
106
|
|
|
return $this->locale; |
|
107
|
|
|
} |
|
108
|
|
|
|
|
109
|
|
|
/** |
|
110
|
|
|
* Adds an error message to the stack. |
|
111
|
|
|
* |
|
112
|
|
|
* @param $error |
|
113
|
|
|
* @param array $parameters |
|
114
|
|
|
* |
|
115
|
|
|
* @return $this |
|
116
|
|
|
*/ |
|
117
|
|
|
public function add($error, array $parameters = []) |
|
118
|
|
|
{ |
|
119
|
|
|
$this->stack[] = [ |
|
120
|
|
|
'error' => $error, |
|
121
|
|
|
'params' => $parameters, |
|
122
|
|
|
]; |
|
123
|
|
|
|
|
124
|
|
|
return $this; |
|
125
|
|
|
} |
|
126
|
|
|
|
|
127
|
|
|
/** |
|
128
|
|
|
* Gets all of the errors on the stack and also attempts |
|
129
|
|
|
* translation using the Locale class. |
|
130
|
|
|
* |
|
131
|
|
|
* @param string|bool $locale optional locale |
|
132
|
|
|
* |
|
133
|
|
|
* @return array error messages |
|
134
|
|
|
*/ |
|
135
|
|
|
public function all($locale = false) |
|
136
|
|
|
{ |
|
137
|
|
|
$messages = []; |
|
138
|
|
|
foreach ($this->stack as $error) { |
|
139
|
|
|
$messages[] = $this->parse($error['error'], $locale, $error['params']); |
|
|
|
|
|
|
140
|
|
|
} |
|
141
|
|
|
|
|
142
|
|
|
return $messages; |
|
143
|
|
|
} |
|
144
|
|
|
|
|
145
|
|
|
/** |
|
146
|
|
|
* Gets an error for a specific parameter on the stack. |
|
147
|
|
|
* |
|
148
|
|
|
* @param string $value value we are searching for |
|
149
|
|
|
* @param string $param parameter name |
|
150
|
|
|
* |
|
151
|
|
|
* @return array|null |
|
152
|
|
|
*/ |
|
153
|
|
|
public function find($value, $param = 'field'): ?array |
|
154
|
|
|
{ |
|
155
|
|
|
foreach ($this->stack as $error) { |
|
156
|
|
|
if (array_value($error['params'], $param) !== $value) { |
|
157
|
|
|
continue; |
|
158
|
|
|
} |
|
159
|
|
|
|
|
160
|
|
|
if (!isset($error['message'])) { |
|
161
|
|
|
$error['message'] = $this->parse($error['error'], false, $error['params']); |
|
162
|
|
|
} |
|
163
|
|
|
|
|
164
|
|
|
return $error; |
|
165
|
|
|
} |
|
166
|
|
|
|
|
167
|
|
|
return null; |
|
168
|
|
|
} |
|
169
|
|
|
|
|
170
|
|
|
/** |
|
171
|
|
|
* Checks if an error exists with a specific parameter on the stack. |
|
172
|
|
|
* |
|
173
|
|
|
* @param string $value value we are searching for |
|
174
|
|
|
* @param string $param parameter name |
|
175
|
|
|
* |
|
176
|
|
|
* @return bool |
|
177
|
|
|
*/ |
|
178
|
|
|
public function has($value, $param = 'field') |
|
179
|
|
|
{ |
|
180
|
|
|
return null !== $this->find($value, $param); |
|
181
|
|
|
} |
|
182
|
|
|
|
|
183
|
|
|
/** |
|
184
|
|
|
* Clears the error stack. |
|
185
|
|
|
* |
|
186
|
|
|
* @return $this |
|
187
|
|
|
*/ |
|
188
|
|
|
public function clear() |
|
189
|
|
|
{ |
|
190
|
|
|
$this->stack = []; |
|
191
|
|
|
|
|
192
|
|
|
return $this; |
|
193
|
|
|
} |
|
194
|
|
|
|
|
195
|
|
|
/** |
|
196
|
|
|
* Formats an incoming error message. |
|
197
|
|
|
* |
|
198
|
|
|
* @param array|string $error |
|
199
|
|
|
* |
|
200
|
|
|
* @return array |
|
201
|
|
|
*/ |
|
202
|
|
|
private function sanitize($error) |
|
203
|
|
|
{ |
|
204
|
|
|
if (!is_array($error)) { |
|
205
|
|
|
$error = ['error' => $error]; |
|
206
|
|
|
} |
|
207
|
|
|
|
|
208
|
|
|
if (!isset($error['params'])) { |
|
209
|
|
|
$error['params'] = []; |
|
210
|
|
|
} |
|
211
|
|
|
|
|
212
|
|
|
return $error; |
|
213
|
|
|
} |
|
214
|
|
|
|
|
215
|
|
|
/** |
|
216
|
|
|
* Parses an error message before displaying it. |
|
217
|
|
|
* |
|
218
|
|
|
* @param string $error |
|
219
|
|
|
* @param string|false $locale |
|
220
|
|
|
* @param array $parameters |
|
221
|
|
|
* |
|
222
|
|
|
* @return string |
|
223
|
|
|
*/ |
|
224
|
|
|
private function parse($error, $locale, array $parameters) |
|
225
|
|
|
{ |
|
226
|
|
|
// try to supply a fallback message in case |
|
227
|
|
|
// the user does not have one specified |
|
228
|
|
|
$fallback = array_value(self::$messages, $error); |
|
229
|
|
|
|
|
230
|
|
|
return $this->getLocale()->t($error, $parameters, $locale, $fallback); |
|
231
|
|
|
} |
|
232
|
|
|
|
|
233
|
|
|
////////////////////////// |
|
234
|
|
|
// IteratorAggregate Interface |
|
235
|
|
|
////////////////////////// |
|
236
|
|
|
|
|
237
|
|
|
public function getIterator() |
|
238
|
|
|
{ |
|
239
|
|
|
return new ArrayIterator($this->stack); |
|
240
|
|
|
} |
|
241
|
|
|
|
|
242
|
|
|
////////////////////////// |
|
243
|
|
|
// Countable Interface |
|
244
|
|
|
////////////////////////// |
|
245
|
|
|
|
|
246
|
|
|
/** |
|
247
|
|
|
* Get total number of models matching query. |
|
248
|
|
|
* |
|
249
|
|
|
* @return int |
|
250
|
|
|
*/ |
|
251
|
|
|
public function count() |
|
252
|
|
|
{ |
|
253
|
|
|
return count($this->stack); |
|
254
|
|
|
} |
|
255
|
|
|
|
|
256
|
|
|
///////////////////////////// |
|
257
|
|
|
// ArrayAccess Interface |
|
258
|
|
|
///////////////////////////// |
|
259
|
|
|
|
|
260
|
|
|
public function offsetExists($offset) |
|
261
|
|
|
{ |
|
262
|
|
|
return isset($this->stack[$offset]); |
|
263
|
|
|
} |
|
264
|
|
|
|
|
265
|
|
|
public function offsetGet($offset) |
|
266
|
|
|
{ |
|
267
|
|
|
if (!$this->offsetExists($offset)) { |
|
268
|
|
|
throw new \OutOfBoundsException("$offset does not exist on this ErrorStack"); |
|
269
|
|
|
} |
|
270
|
|
|
|
|
271
|
|
|
$error = $this->stack[$offset]; |
|
272
|
|
|
if (!isset($error['message'])) { |
|
273
|
|
|
$error['message'] = $this->parse($error['error'], false, $error['params']); |
|
274
|
|
|
} |
|
275
|
|
|
|
|
276
|
|
|
return $error; |
|
277
|
|
|
} |
|
278
|
|
|
|
|
279
|
|
|
public function offsetSet($offset, $error) |
|
280
|
|
|
{ |
|
281
|
|
|
if (!is_numeric($offset)) { |
|
282
|
|
|
throw new \Exception('Can only perform set on numeric indices'); |
|
283
|
|
|
} |
|
284
|
|
|
|
|
285
|
|
|
$this->stack[$offset] = $this->sanitize($error); |
|
286
|
|
|
} |
|
287
|
|
|
|
|
288
|
|
|
public function offsetUnset($offset) |
|
289
|
|
|
{ |
|
290
|
|
|
unset($this->stack[$offset]); |
|
291
|
|
|
} |
|
292
|
|
|
} |
|
293
|
|
|
|
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.