|
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 IteratorAggregate; |
|
18
|
|
|
use Pulsar\Interfaces\TranslatorInterface; |
|
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 TranslatorInterface|null |
|
54
|
|
|
*/ |
|
55
|
|
|
private static $translator; |
|
56
|
|
|
|
|
57
|
|
|
/** |
|
58
|
|
|
* @var array |
|
59
|
|
|
*/ |
|
60
|
|
|
private $stack = []; |
|
61
|
|
|
|
|
62
|
|
|
/** |
|
63
|
|
|
* Sets the global translator instance. |
|
64
|
|
|
*/ |
|
65
|
|
|
public static function setTranslator(TranslatorInterface $translator) |
|
66
|
|
|
{ |
|
67
|
|
|
self::$translator = $translator; |
|
68
|
|
|
} |
|
69
|
|
|
|
|
70
|
|
|
/** |
|
71
|
|
|
* Clears the global translator instance. |
|
72
|
|
|
*/ |
|
73
|
|
|
public static function clearTranslator(): void |
|
74
|
|
|
{ |
|
75
|
|
|
self::$translator = null; |
|
76
|
|
|
} |
|
77
|
|
|
|
|
78
|
|
|
/** |
|
79
|
|
|
* Gets the translator. |
|
80
|
|
|
*/ |
|
81
|
|
|
public function getTranslator(): ?TranslatorInterface |
|
82
|
|
|
{ |
|
83
|
|
|
return self::$translator; |
|
84
|
|
|
} |
|
85
|
|
|
|
|
86
|
|
|
/** |
|
87
|
|
|
* Adds an error message to the stack. |
|
88
|
|
|
* |
|
89
|
|
|
* @param $error |
|
90
|
|
|
* |
|
91
|
|
|
* @return $this |
|
92
|
|
|
*/ |
|
93
|
|
|
public function add($error, array $parameters = []) |
|
94
|
|
|
{ |
|
95
|
|
|
$this->stack[] = [ |
|
96
|
|
|
'error' => $error, |
|
97
|
|
|
'params' => $parameters, |
|
98
|
|
|
]; |
|
99
|
|
|
|
|
100
|
|
|
return $this; |
|
101
|
|
|
} |
|
102
|
|
|
|
|
103
|
|
|
/** |
|
104
|
|
|
* Gets all of the errors on the stack and also performs translation if enabled. |
|
105
|
|
|
* |
|
106
|
|
|
* @param string|bool $locale optional locale |
|
107
|
|
|
* |
|
108
|
|
|
* @return array error messages |
|
109
|
|
|
*/ |
|
110
|
|
|
public function all($locale = false): array |
|
111
|
|
|
{ |
|
112
|
|
|
$messages = []; |
|
113
|
|
|
foreach ($this->stack as $error) { |
|
114
|
|
|
$messages[] = $this->parse($error['error'], $locale, $error['params']); |
|
|
|
|
|
|
115
|
|
|
} |
|
116
|
|
|
|
|
117
|
|
|
return $messages; |
|
118
|
|
|
} |
|
119
|
|
|
|
|
120
|
|
|
/** |
|
121
|
|
|
* Gets an error for a specific parameter on the stack. |
|
122
|
|
|
* |
|
123
|
|
|
* @param string $value value we are searching for |
|
124
|
|
|
* @param string $param parameter name |
|
125
|
|
|
*/ |
|
126
|
|
|
public function find($value, $param = 'field'): ?array |
|
127
|
|
|
{ |
|
128
|
|
|
foreach ($this->stack as $error) { |
|
129
|
|
|
$stackValue = $error['params'][$param] ?? null; |
|
130
|
|
|
if ($stackValue !== $value) { |
|
131
|
|
|
continue; |
|
132
|
|
|
} |
|
133
|
|
|
|
|
134
|
|
|
if (!isset($error['message'])) { |
|
135
|
|
|
$error['message'] = $this->parse($error['error'], false, $error['params']); |
|
136
|
|
|
} |
|
137
|
|
|
|
|
138
|
|
|
return $error; |
|
139
|
|
|
} |
|
140
|
|
|
|
|
141
|
|
|
return null; |
|
142
|
|
|
} |
|
143
|
|
|
|
|
144
|
|
|
/** |
|
145
|
|
|
* Checks if an error exists with a specific parameter on the stack. |
|
146
|
|
|
* |
|
147
|
|
|
* @param string $value value we are searching for |
|
148
|
|
|
* @param string $param parameter name |
|
149
|
|
|
*/ |
|
150
|
|
|
public function has($value, $param = 'field'): bool |
|
151
|
|
|
{ |
|
152
|
|
|
return null !== $this->find($value, $param); |
|
153
|
|
|
} |
|
154
|
|
|
|
|
155
|
|
|
/** |
|
156
|
|
|
* Clears the error stack. |
|
157
|
|
|
* |
|
158
|
|
|
* @return $this |
|
159
|
|
|
*/ |
|
160
|
|
|
public function clear() |
|
161
|
|
|
{ |
|
162
|
|
|
$this->stack = []; |
|
163
|
|
|
|
|
164
|
|
|
return $this; |
|
165
|
|
|
} |
|
166
|
|
|
|
|
167
|
|
|
/** |
|
168
|
|
|
* Formats an incoming error message. |
|
169
|
|
|
* |
|
170
|
|
|
* @param array|string $error |
|
171
|
|
|
*/ |
|
172
|
|
|
private function sanitize($error): array |
|
173
|
|
|
{ |
|
174
|
|
|
if (!is_array($error)) { |
|
175
|
|
|
$error = ['error' => $error]; |
|
176
|
|
|
} |
|
177
|
|
|
|
|
178
|
|
|
if (!isset($error['params'])) { |
|
179
|
|
|
$error['params'] = []; |
|
180
|
|
|
} |
|
181
|
|
|
|
|
182
|
|
|
return $error; |
|
183
|
|
|
} |
|
184
|
|
|
|
|
185
|
|
|
/** |
|
186
|
|
|
* Parses an error message before displaying it. |
|
187
|
|
|
* |
|
188
|
|
|
* @param string $error |
|
189
|
|
|
* @param string|false $locale |
|
190
|
|
|
*/ |
|
191
|
|
|
private function parse($error, $locale, array $parameters): string |
|
192
|
|
|
{ |
|
193
|
|
|
// try to supply a fallback message in case |
|
194
|
|
|
// the user does not have one specified |
|
195
|
|
|
$fallback = self::$messages[$error] ?? null; |
|
196
|
|
|
|
|
197
|
|
|
if (!self::$translator) { |
|
198
|
|
|
return $error; |
|
199
|
|
|
} |
|
200
|
|
|
|
|
201
|
|
|
return $this->getTranslator()->translate($error, $parameters, $locale, $fallback); |
|
|
|
|
|
|
202
|
|
|
} |
|
203
|
|
|
|
|
204
|
|
|
////////////////////////// |
|
205
|
|
|
// IteratorAggregate Interface |
|
206
|
|
|
////////////////////////// |
|
207
|
|
|
|
|
208
|
|
|
public function getIterator() |
|
209
|
|
|
{ |
|
210
|
|
|
return new ArrayIterator($this->stack); |
|
211
|
|
|
} |
|
212
|
|
|
|
|
213
|
|
|
////////////////////////// |
|
214
|
|
|
// Countable Interface |
|
215
|
|
|
////////////////////////// |
|
216
|
|
|
|
|
217
|
|
|
/** |
|
218
|
|
|
* Get total number of models matching query. |
|
219
|
|
|
* |
|
220
|
|
|
* @return int |
|
221
|
|
|
*/ |
|
222
|
|
|
public function count() |
|
223
|
|
|
{ |
|
224
|
|
|
return count($this->stack); |
|
225
|
|
|
} |
|
226
|
|
|
|
|
227
|
|
|
///////////////////////////// |
|
228
|
|
|
// ArrayAccess Interface |
|
229
|
|
|
///////////////////////////// |
|
230
|
|
|
|
|
231
|
|
|
public function offsetExists($offset) |
|
232
|
|
|
{ |
|
233
|
|
|
return isset($this->stack[$offset]); |
|
234
|
|
|
} |
|
235
|
|
|
|
|
236
|
|
|
public function offsetGet($offset) |
|
237
|
|
|
{ |
|
238
|
|
|
if (!$this->offsetExists($offset)) { |
|
239
|
|
|
throw new \OutOfBoundsException("$offset does not exist on this ErrorStack"); |
|
240
|
|
|
} |
|
241
|
|
|
|
|
242
|
|
|
$error = $this->stack[$offset]; |
|
243
|
|
|
if (!isset($error['message'])) { |
|
244
|
|
|
$error['message'] = $this->parse($error['error'], false, $error['params']); |
|
245
|
|
|
} |
|
246
|
|
|
|
|
247
|
|
|
return $error; |
|
248
|
|
|
} |
|
249
|
|
|
|
|
250
|
|
|
public function offsetSet($offset, $error) |
|
251
|
|
|
{ |
|
252
|
|
|
if (!is_numeric($offset)) { |
|
253
|
|
|
throw new \Exception('Can only perform set on numeric indices'); |
|
254
|
|
|
} |
|
255
|
|
|
|
|
256
|
|
|
$this->stack[$offset] = $this->sanitize($error); |
|
257
|
|
|
} |
|
258
|
|
|
|
|
259
|
|
|
public function offsetUnset($offset) |
|
260
|
|
|
{ |
|
261
|
|
|
unset($this->stack[$offset]); |
|
262
|
|
|
} |
|
263
|
|
|
} |
|
264
|
|
|
|
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.