1
|
|
|
<?php |
2
|
|
|
declare(strict_types=1); |
3
|
|
|
|
4
|
|
|
/** |
5
|
|
|
* |
6
|
|
|
* This file is part of Aura for PHP. |
7
|
|
|
* |
8
|
|
|
* @license http://opensource.org/licenses/mit-license.php MIT |
9
|
|
|
* |
10
|
|
|
*/ |
11
|
|
|
namespace Aura\Filter; |
12
|
|
|
|
13
|
|
|
use Aura\Filter\Exception\FilterFailed; |
14
|
|
|
use Aura\Filter\Exception; |
15
|
|
|
use Aura\Filter\Failure\Failure; |
16
|
|
|
use Aura\Filter\Failure\FailureCollection; |
17
|
|
|
use Aura\Filter\Spec\SanitizeSpec; |
18
|
|
|
use Aura\Filter\Spec\Spec; |
19
|
|
|
use Aura\Filter\Spec\ValidateSpec; |
20
|
|
|
use Aura\Filter\Spec\SubSpecFactory; |
21
|
|
|
use Aura\Filter\Spec\SubSpec; |
22
|
|
|
use InvalidArgumentException; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* |
26
|
|
|
* A filter for an entire "subject" (i.e., an array or object). |
27
|
|
|
* |
28
|
|
|
* @package Aura.Filter |
29
|
|
|
* |
30
|
|
|
*/ |
31
|
|
|
class SubjectFilter |
32
|
|
|
{ |
33
|
|
|
/** |
34
|
|
|
* |
35
|
|
|
* An array of specifications for the filter subject. |
36
|
|
|
* |
37
|
|
|
* @var array |
38
|
|
|
* |
39
|
|
|
*/ |
40
|
|
|
protected $specs = array(); |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* |
44
|
|
|
* Skip these fields on the filter subject. |
45
|
|
|
* |
46
|
|
|
* @var array |
47
|
|
|
* |
48
|
|
|
*/ |
49
|
|
|
protected $skip = array(); |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* |
53
|
|
|
* A collection of failure objects. |
54
|
|
|
* |
55
|
|
|
* @var FailureCollection |
56
|
|
|
* |
57
|
|
|
*/ |
58
|
|
|
protected $failures; |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* |
62
|
|
|
* Use these field-specific messages when a subject field fails. |
63
|
|
|
* |
64
|
|
|
* @var array |
65
|
|
|
* |
66
|
|
|
*/ |
67
|
|
|
protected $field_messages = array(); |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* |
71
|
|
|
* A prototype ValidateSpec. |
72
|
|
|
* |
73
|
|
|
* @var ValidateSpec |
74
|
|
|
* |
75
|
|
|
*/ |
76
|
|
|
protected $validate_spec; |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* |
80
|
|
|
* A prototype SanitizeSpec. |
81
|
|
|
* |
82
|
|
|
* @var SanitizeSpec |
83
|
|
|
* |
84
|
|
|
*/ |
85
|
|
|
protected $sanitize_spec; |
86
|
|
|
|
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* Factory for Sub subject specifications |
90
|
|
|
* |
91
|
|
|
* @var SubSpecFactory |
92
|
|
|
* |
93
|
|
|
* @access protected |
94
|
|
|
*/ |
95
|
|
|
protected $sub_spec_factory; |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* |
99
|
|
|
* A prototype FailureCollection. |
100
|
|
|
* |
101
|
|
|
* @var FailureCollection |
102
|
|
|
* |
103
|
|
|
*/ |
104
|
|
|
protected $proto_failures; |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* |
108
|
|
|
* Constructor. |
109
|
|
|
* |
110
|
|
|
* @param ValidateSpec $validate_spec A prototype ValidateSpec. |
111
|
|
|
* |
112
|
|
|
* @param ValidateSpec $sanitize_spec A prototype SanitizeSpec. |
113
|
|
|
* |
114
|
|
|
* @param SubSpecFactory $sub_spec_factory A factory for SubSpec |
115
|
|
|
* |
116
|
|
|
* @param FailureCollection $failures A prototype FailureCollection. |
117
|
|
|
*/ |
118
|
|
|
public function __construct( |
119
|
|
|
ValidateSpec $validate_spec, |
120
|
|
|
SanitizeSpec $sanitize_spec, |
121
|
|
|
SubSpecFactory $sub_spec_factory, |
122
|
|
|
FailureCollection $failures |
123
|
|
|
) { |
124
|
|
|
$this->validate_spec = $validate_spec; |
125
|
|
|
$this->sanitize_spec = $sanitize_spec; |
126
|
|
|
$this->sub_spec_factory = $sub_spec_factory; |
127
|
|
|
$this->proto_failures = $failures; |
128
|
|
|
$this->init(); |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* |
133
|
|
|
* Initialization logic for this filter. |
134
|
|
|
* |
135
|
|
|
* @return null |
136
|
|
|
* |
137
|
|
|
*/ |
138
|
|
|
protected function init(): void |
139
|
|
|
{ |
140
|
|
|
// do nothing |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* |
145
|
|
|
* Asserts that the subject passes the filter. |
146
|
|
|
* |
147
|
|
|
* @param array|object $subject The subject to be filtered. |
148
|
|
|
* |
149
|
|
|
* @return null |
150
|
|
|
* |
151
|
|
|
* @throws Exception\FilterFailed when the assertion fails. |
152
|
|
|
* |
153
|
|
|
*/ |
154
|
|
|
public function __invoke(&$subject) |
155
|
|
|
{ |
156
|
|
|
return $this->assert($subject); |
|
|
|
|
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
/** |
160
|
|
|
* |
161
|
|
|
* Asserts that the subject passes the filter. |
162
|
|
|
* |
163
|
|
|
* @param array|object $subject The subject to be filtered. |
164
|
|
|
* |
165
|
|
|
* @return null |
166
|
|
|
* |
167
|
|
|
* @throws Exception\FilterFailed when the assertion fails. |
168
|
|
|
* |
169
|
|
|
*/ |
170
|
|
|
public function assert(&$subject): void |
171
|
|
|
{ |
172
|
|
|
if ($this->apply($subject)) { |
173
|
|
|
return; |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
$class = get_class($this); |
177
|
|
|
$message = PHP_EOL |
178
|
|
|
. " Filter: {$class}" . PHP_EOL |
179
|
|
|
. " Fields:" . PHP_EOL |
180
|
|
|
. $this->failures->getMessagesAsString(' '); |
181
|
|
|
|
182
|
|
|
$e = new FilterFailed($message); |
183
|
|
|
$e->setFilterClass($class); |
184
|
|
|
$e->setFailures($this->failures); |
185
|
|
|
$e->setSubject($subject); |
186
|
|
|
throw $e; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* |
191
|
|
|
* Adds a "validate" specification for a subject field. |
192
|
|
|
* |
193
|
|
|
* @param string $field The subject field name. |
194
|
|
|
* |
195
|
|
|
* |
196
|
|
|
*/ |
197
|
|
|
public function validate(string $field): Spec |
198
|
|
|
{ |
199
|
|
|
return $this->addSpec(clone $this->validate_spec, $field); |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
/** |
203
|
|
|
* |
204
|
|
|
* Adds a "sanitize" specification for a subject field. |
205
|
|
|
* |
206
|
|
|
* @param string $field The subject field name. |
207
|
|
|
* |
208
|
|
|
* |
209
|
|
|
*/ |
210
|
|
|
public function sanitize(string $field): Spec |
211
|
|
|
{ |
212
|
|
|
return $this->addSpec(clone $this->sanitize_spec, $field); |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
/** |
216
|
|
|
* |
217
|
|
|
* Adds a "subfilter" specification for a subject field. |
218
|
|
|
* |
219
|
|
|
* @param string $field The subject field name. |
220
|
|
|
* |
221
|
|
|
* |
222
|
|
|
*/ |
223
|
|
|
public function subFilter(string $field, $class = \Aura\Filter\SubjectFilter::class): Spec |
224
|
|
|
{ |
225
|
|
|
$spec = $this->sub_spec_factory->newSubSpec($class); |
226
|
|
|
return $this->addSpec($spec, $field); |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
/** |
230
|
|
|
* |
231
|
|
|
* Adds a specification for a subject field. |
232
|
|
|
* |
233
|
|
|
* @param Spec $spec The specification object. |
234
|
|
|
* |
235
|
|
|
* @param string $field The subject field name. |
236
|
|
|
* |
237
|
|
|
* |
238
|
|
|
*/ |
239
|
|
|
protected function addSpec(Spec $spec, string $field): Spec |
240
|
|
|
{ |
241
|
|
|
$this->specs[] = $spec; |
242
|
|
|
$spec->field($field); |
243
|
|
|
return $spec; |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
/** |
247
|
|
|
* |
248
|
|
|
* Specifies a custom message to use when a subject field fails. |
249
|
|
|
* |
250
|
|
|
* @param string $field The subject field name. |
251
|
|
|
* |
252
|
|
|
* @param string $message The failure message to use. |
253
|
|
|
* |
254
|
|
|
* @return null |
255
|
|
|
* |
256
|
|
|
*/ |
257
|
|
|
public function useFieldMessage(string $field, string $message): void |
258
|
|
|
{ |
259
|
|
|
$this->field_messages[$field] = $message; |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
/** |
263
|
|
|
* |
264
|
|
|
* Applies the filter to a subject. |
265
|
|
|
* |
266
|
|
|
* @param array|object $subject The subject to be filtered. |
267
|
|
|
* |
268
|
|
|
* @return bool True on success, false on failure. |
269
|
|
|
* |
270
|
|
|
*/ |
271
|
|
|
public function apply(&$subject): bool |
272
|
|
|
{ |
273
|
|
|
if (is_array($subject)) { |
274
|
|
|
return $this->applyToArray($subject); |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
if (! is_object($subject)) { |
278
|
|
|
$type = gettype($subject); |
279
|
|
|
$message = "Apply the filter to an array or object, not a {$type}."; |
280
|
|
|
throw new InvalidArgumentException($message); |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
return $this->applyToObject($subject); |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
/** |
287
|
|
|
* |
288
|
|
|
* Applies the rule specifications to an array. |
289
|
|
|
* |
290
|
|
|
* @param array $array The filter subject. |
291
|
|
|
* |
292
|
|
|
* @return bool True if all rules passed, false if not. |
293
|
|
|
* |
294
|
|
|
*/ |
295
|
|
|
protected function applyToArray(array &$array): bool |
296
|
|
|
{ |
297
|
|
|
$object = (object) $array; |
298
|
|
|
$result = $this->applyToObject($object); |
299
|
|
|
$array = (array) $object; |
300
|
|
|
return $result; |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
/** |
304
|
|
|
* |
305
|
|
|
* Applies the rule specifications to an object. |
306
|
|
|
* |
307
|
|
|
* |
308
|
|
|
* @return bool True if all rules passed, false if not. |
309
|
|
|
* |
310
|
|
|
*/ |
311
|
|
|
protected function applyToObject(object $object): bool |
312
|
|
|
{ |
313
|
|
|
$this->skip = array(); |
314
|
|
|
$this->failures = clone $this->proto_failures; |
315
|
|
|
foreach ($this->specs as $spec) { |
316
|
|
|
$continue = $this->applySpec($spec, $object); |
317
|
|
|
if (! $continue) { |
318
|
|
|
break; |
319
|
|
|
} |
320
|
|
|
} |
321
|
|
|
return $this->failures->isEmpty(); |
322
|
|
|
} |
323
|
|
|
|
324
|
|
|
/** |
325
|
|
|
* |
326
|
|
|
* Apply a rule specification to the subject. |
327
|
|
|
* |
328
|
|
|
* @param Spec $spec The rule specification. |
329
|
|
|
* |
330
|
|
|
* |
331
|
|
|
* @return bool True to continue, false to stop. |
332
|
|
|
* |
333
|
|
|
*/ |
334
|
|
|
protected function applySpec(Spec $spec, object $subject): bool |
335
|
|
|
{ |
336
|
|
|
if (isset($this->skip[$spec->getField()])) { |
337
|
|
|
|
338
|
|
|
// Issue 140 . Some rule already failed for the field. |
339
|
|
|
// Check the current one have a stop rule or not. |
340
|
|
|
if ($spec->isStopRule()) { |
341
|
|
|
return false; |
342
|
|
|
} |
343
|
|
|
|
344
|
|
|
return true; |
345
|
|
|
} |
346
|
|
|
|
347
|
|
|
if (call_user_func($spec, $subject)) { |
348
|
|
|
return true; |
349
|
|
|
} |
350
|
|
|
|
351
|
|
|
$this->failed($spec); |
352
|
|
|
|
353
|
|
|
if ($spec->isStopRule()) { |
354
|
|
|
return false; |
355
|
|
|
} |
356
|
|
|
|
357
|
|
|
return true; |
358
|
|
|
} |
359
|
|
|
|
360
|
|
|
/** |
361
|
|
|
* |
362
|
|
|
* Adds a failure. |
363
|
|
|
* |
364
|
|
|
* @param Spec $spec The failed rule specification. |
365
|
|
|
* |
366
|
|
|
* @return void |
367
|
|
|
*/ |
368
|
|
|
protected function failed(Spec $spec) |
369
|
|
|
{ |
370
|
|
|
$field = $spec->getField(); |
371
|
|
|
|
372
|
|
|
if ($spec->isHardRule()) { |
373
|
|
|
$this->skip[$field] = true; |
374
|
|
|
} |
375
|
|
|
|
376
|
|
|
if ($spec instanceof SubSpec) { |
377
|
|
|
$this->failures->addSubfieldFailures($field, $spec); |
378
|
|
|
|
379
|
|
|
return; |
380
|
|
|
} |
381
|
|
|
|
382
|
|
|
if (isset($this->field_messages[$field])) { |
383
|
|
|
$this->failures->set($field, $this->field_messages[$field]); |
384
|
|
|
|
385
|
|
|
return; |
386
|
|
|
} |
387
|
|
|
|
388
|
|
|
$this->failures->add($field, $spec->getMessage(), $spec->getArgs()); |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
/** |
392
|
|
|
* |
393
|
|
|
* Returns the failures. |
394
|
|
|
* |
395
|
|
|
* |
396
|
|
|
*/ |
397
|
|
|
public function getFailures(): FailureCollection |
398
|
|
|
{ |
399
|
|
|
return $this->failures; |
400
|
|
|
} |
401
|
|
|
} |
402
|
|
|
|
This check looks for function or method calls that always return null and whose return value is used.
The method
getObject()
can return nothing but null, so it makes no sense to use the return value.The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.