Validator::getValue()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 2
nc 2
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace GeminiLabs\Castor\Services;
4
5
use BadMethodCallException;
6
use InvalidArgumentException;
7
8
class Validator
9
{
10
    /**
11
     * @var array
12
     */
13
    public $errors = [];
14
15
    /**
16
     * The data under validation.
17
     *
18
     * @var array
19
     */
20
    protected $data = [];
21
22
    /**
23
     * The failed validation rules.
24
     *
25
     * @var array
26
     */
27
    protected $failedRules = [];
28
29
    /**
30
     * The rules to be applied to the data.
31
     *
32
     * @var array
33
     */
34
    protected $rules = [];
35
36
    /**
37
     * The size related validation rules.
38
     *
39
     * @var array
40
     */
41
    protected $sizeRules = [
42
        'Between',
43
        'Max',
44
        'Min',
45
    ];
46
47
    /**
48
     * The validation rules that imply the field is required.
49
     *
50
     * @var array
51
     */
52
    protected $implicitRules = [
53
        'Required',
54
    ];
55
56
    /**
57
     * The numeric related validation rules.
58
     *
59
     * @var array
60
     */
61
    protected $numericRules = [
62
        'Numeric',
63
    ];
64
65
    /**
66
     * Run the validator's rules against its data.
67
     *
68
     * @param mixed $data
69
     *
70
     * @return array
71
     */
72
    public function validate($data, array $rules = [])
73
    {
74
        $this->normalizeData($data);
75
        $this->setRules($rules);
76
77
        foreach ($this->rules as $attribute => $rules) {
78
            foreach ($rules as $rule) {
79
                $this->validateAttribute($rule, $attribute);
80
81
                if ($this->shouldStopValidating($attribute)) {
82
                    break;
83
                }
84
            }
85
        }
86
87
        return $this->errors;
88
    }
89
90
    /**
91
     * Add an error message to the validator's collection of errors.
92
     *
93
     * @param string $attribute
94
     * @param string $rule
95
     *
96
     * @return void
97
     */
98
    protected function addError($attribute, $rule, array $parameters)
99
    {
100
        $message = $this->getMessage($attribute, $rule, $parameters);
101
102
        $this->errors[$attribute]['errors'][] = $message;
103
104
        if (!isset($this->errors[$attribute]['value'])) {
105
            $this->errors[$attribute]['value'] = $this->getValue($attribute);
106
        }
107
    }
108
109
    /**
110
     * Add a failed rule and error message to the collection.
111
     *
112
     * @param string $attribute
113
     * @param string $rule
114
     *
115
     * @return void
116
     */
117
    protected function addFailure($attribute, $rule, array $parameters)
118
    {
119
        $this->addError($attribute, $rule, $parameters);
120
121
        $this->failedRules[$attribute][$rule] = $parameters;
122
    }
123
124
    /**
125
     * Get the data type of the given attribute.
126
     *
127
     * @param  string  $attribute
128
     * @return string
129
     */
130
    protected function getAttributeType($attribute)
131
    {
132
        return $this->hasRule($attribute, $this->numericRules)
133
            ? 'numeric'
134
            : 'string';
135
    }
136
137
    /**
138
     * Get the validation message for an attribute and rule.
139
     *
140
     * @param string $attribute
141
     * @param string $rule
142
     *
143
     * @return string|null
144
     */
145
    protected function getMessage($attribute, $rule, array $parameters)
146
    {
147
        if (in_array($rule, $this->sizeRules)) {
148
            return $this->getSizeMessage($attribute, $rule, $parameters);
149
        }
150
151
        $lowerRule = $this->snakeCase($rule);
152
153
        return $this->translator($lowerRule, $rule, $attribute, $parameters);
154
    }
155
156
    /**
157
     * Get a rule and its parameters for a given attribute.
158
     *
159
     * @param string       $attribute
160
     * @param string|array $rules
161
     *
162
     * @return array|null
163
     */
164
    protected function getRule($attribute, $rules)
165
    {
166
        if (!array_key_exists($attribute, $this->rules)) {
167
            return;
168
        }
169
170
        $rules = (array) $rules;
171
172
        foreach ($this->rules[$attribute] as $rule) {
173
            list($rule, $parameters) = $this->parseRule($rule);
174
175
            if (in_array($rule, $rules)) {
176
                return [$rule, $parameters];
177
            }
178
        }
179
    }
180
181
    /**
182
     * Get the size of an attribute.
183
     *
184
     * @param string $attribute
185
     * @param mixed  $value
186
     *
187
     * @return mixed
188
     */
189
    protected function getSize($attribute, $value)
190
    {
191
        $hasNumeric = $this->hasRule($attribute, $this->numericRules);
192
193
        if (is_numeric($value) && $hasNumeric) {
194
            return $value;
195
        } elseif (is_array($value)) {
196
            return count($value);
197
        }
198
199
        return mb_strlen($value);
200
    }
201
202
    /**
203
     * Get the proper error message for an attribute and size rule.
204
     *
205
     * @param string $attribute
206
     * @param string $rule
207
     *
208
     * @return string|null
209
     */
210
    protected function getSizeMessage($attribute, $rule, array $parameters)
211
    {
212
        $lowerRule = $this->snakeCase($rule);
213
        $type = $this->getAttributeType($attribute);
214
215
        $lowerRule .= ".{$type}";
216
217
        return $this->translator($lowerRule, $rule, $attribute, $parameters);
218
    }
219
220
    /**
221
     * Get the value of a given attribute.
222
     *
223
     * @param string $attribute
224
     *
225
     * @return mixed
226
     */
227
    protected function getValue($attribute)
228
    {
229
        if (isset($this->data[$attribute])) {
230
            return $this->data[$attribute];
231
        }
232
    }
233
234
    /**
235
     * Determine if the given attribute has a rule in the given set.
236
     *
237
     * @param string       $attribute
238
     * @param string|array $rules
239
     *
240
     * @return bool
241
     */
242
    protected function hasRule($attribute, $rules)
243
    {
244
        return !is_null($this->getRule($attribute, $rules));
245
    }
246
247
    /**
248
     * Normalize the provided data to an array.
249
     *
250
     * @param mixed $data
251
     *
252
     * @return $this
253
     */
254
    protected function normalizeData($data)
255
    {
256
        // If an object was provided, get its public properties
257
        if (is_object($data)) {
258
            $this->data = get_object_vars($data);
259
        } else {
260
            $this->data = $data;
261
        }
262
263
        return $this;
264
    }
265
266
    /**
267
     * Parse a parameter list.
268
     *
269
     * @param string $rule
270
     * @param string $parameter
271
     *
272
     * @return array
273
     */
274
    protected function parseParameters($rule, $parameter)
275
    {
276
        if ('regex' == strtolower($rule)) {
277
            return [$parameter];
278
        }
279
280
        return str_getcsv($parameter);
281
    }
282
283
    /**
284
     * Extract the rule name and parameters from a rule.
285
     *
286
     * @param string $rule
287
     *
288
     * @return array
289
     */
290
    protected function parseRule($rule)
291
    {
292
        $parameters = [];
293
294
        // example: {rule}:{parameters}
295
        if (false !== strpos($rule, ':')) {
296
            list($rule, $parameter) = explode(':', $rule, 2);
297
298
            // example: {parameter1,parameter2,...}
299
            $parameters = $this->parseParameters($rule, $parameter);
300
        }
301
302
        $rule = ucwords(str_replace(['-', '_'], ' ', trim($rule)));
303
        $rule = str_replace(' ', '', $rule);
304
305
        return [$rule, $parameters];
306
    }
307
308
    /**
309
     * Replace all placeholders for the between rule.
310
     *
311
     * @param string $message
312
     *
313
     * @return string
314
     */
315
    protected function replaceBetween($message, array $parameters)
316
    {
317
        return str_replace([':min', ':max'], $parameters, $message);
318
    }
319
320
    /**
321
     * Replace all placeholders for the max rule.
322
     *
323
     * @param string $message
324
     *
325
     * @return string
326
     */
327
    protected function replaceMax($message, array $parameters)
328
    {
329
        return str_replace(':max', $parameters[0], $message);
330
    }
331
332
    /**
333
     * Replace all placeholders for the min rule.
334
     *
335
     * @param string $message
336
     *
337
     * @return string
338
     */
339
    protected function replaceMin($message, array $parameters)
340
    {
341
        return str_replace(':min', $parameters[0], $message);
342
    }
343
344
    /**
345
     * Require a certain number of parameters to be present.
346
     *
347
     * @param int    $count
348
     * @param string $rule
349
     *
350
     * @return void
351
     * @throws InvalidArgumentException
352
     */
353
    protected function requireParameterCount($count, array $parameters, $rule)
354
    {
355
        if (count($parameters) < $count) {
356
            throw new InvalidArgumentException("Validation rule $rule requires at least $count parameters.");
357
        }
358
    }
359
360
    /**
361
     * Set the validation rules.
362
     *
363
     * @return $this
364
     */
365
    protected function setRules(array $rules)
366
    {
367
        foreach ($rules as $key => $rule) {
368
            $rules[$key] = is_string($rule) ? explode('|', $rule) : $rule;
369
        }
370
371
        $this->rules = $rules;
372
373
        return $this;
374
    }
375
376
    /**
377
     * Check if we should stop further validations on a given attribute.
378
     *
379
     * @param string $attribute
380
     *
381
     * @return bool
382
     */
383
    protected function shouldStopValidating($attribute)
384
    {
385
        return $this->hasRule($attribute, $this->implicitRules)
386
            && isset($this->failedRules[$attribute])
387
            && array_intersect(array_keys($this->failedRules[$attribute]), $this->implicitRules);
388
    }
389
390
    /**
391
     * Convert a string to snake case.
392
     *
393
     * @param string $string
394
     *
395
     * @return string
396
     */
397
    protected function snakeCase($string)
398
    {
399
        if (!ctype_lower($string)) {
400
            $string = preg_replace('/\s+/u', '', $string);
401
            $string = preg_replace('/(.)(?=[A-Z])/u', '$1_', $string);
402
            $string = mb_strtolower($string, 'UTF-8');
403
        }
404
405
        return $string;
406
    }
407
408
    /**
409
     * Returns a translated message for the attribute.
410
     *
411
     * @param string $key
412
     * @param string $rule
413
     * @param string $attribute
414
     *
415
     * @return string|null
416
     */
417
    protected function translator($key, $rule, $attribute, array $parameters)
418
    {
419
        $strings = []; //glsr_resolve( 'Strings' )->validation();
420
421
        $message = isset($strings[$key])
422
            ? $strings[$key]
423
            : false;
424
425
        if (!$message) {
426
            return;
427
        }
428
429
        $message = str_replace(':attribute', $attribute, $message);
430
431
        if (method_exists($this, $replacer = "replace{$rule}")) {
432
            $message = $this->$replacer($message, $parameters);
433
        }
434
435
        return $message;
436
    }
437
438
    // Rules Validation
439
    // ---------------------------------------------------------------------------------------------
440
441
    /**
442
     * Validate that an attribute was "accepted".
443
     *
444
     * This validation rule implies the attribute is "required".
445
     *
446
     * @param mixed  $value
447
     *
448
     * @return bool
449
     */
450
    protected function validateAccepted($value)
451
    {
452
        $acceptable = ['yes', 'on', '1', 1, true, 'true'];
453
454
        return $this->validateRequired($value) && in_array($value, $acceptable, true);
455
    }
456
457
    /**
458
     * Validate a given attribute against a rule.
459
     *
460
     * @param string $rule
461
     * @param string $attribute
462
     *
463
     * @return void
464
     * @throws BadMethodCallException
465
     */
466
    protected function validateAttribute($rule, $attribute)
467
    {
468
        list($rule, $parameters) = $this->parseRule($rule);
469
470
        if ('' == $rule) {
471
            return;
472
        }
473
474
        $method = "validate{$rule}";
475
476
        if (!method_exists($this, $method)) {
477
            throw new BadMethodCallException("Method [$method] does not exist.");
478
        }
479
480
        if (!$this->$method($this->getValue($attribute), $attribute, $parameters)) {
481
            $this->addFailure($attribute, $rule, $parameters);
482
        }
483
    }
484
485
    /**
486
     * Validate the size of an attribute is between a set of values.
487
     *
488
     * @param mixed  $value
489
     * @param string $attribute
490
     *
491
     * @return bool
492
     */
493
    protected function validateBetween($value, $attribute, array $parameters)
494
    {
495
        $this->requireParameterCount(2, $parameters, 'between');
496
497
        $size = $this->getSize($attribute, $value);
498
499
        return $size >= $parameters[0] && $size <= $parameters[1];
500
    }
501
502
    /**
503
     * Validate that an attribute is a valid e-mail address.
504
     *
505
     * @param mixed $value
506
     *
507
     * @return bool
508
     */
509
    protected function validateEmail($value)
510
    {
511
        return false !== filter_var($value, FILTER_VALIDATE_EMAIL);
512
    }
513
514
    /**
515
     * Validate the size of an attribute is less than a maximum value.
516
     *
517
     * @param mixed  $value
518
     * @param string $attribute
519
     *
520
     * @return bool
521
     */
522
    protected function validateMax($value, $attribute, array $parameters)
523
    {
524
        $this->requireParameterCount(1, $parameters, 'max');
525
526
        return $this->getSize($attribute, $value) <= $parameters[0];
527
    }
528
529
    /**
530
     * Validate the size of an attribute is greater than a minimum value.
531
     *
532
     * @param mixed  $value
533
     * @param string $attribute
534
     *
535
     * @return bool
536
     */
537
    protected function validateMin($value, $attribute, array $parameters)
538
    {
539
        $this->requireParameterCount(1, $parameters, 'min');
540
541
        return $this->getSize($attribute, $value) >= $parameters[0];
542
    }
543
544
    /**
545
     * Validate that an attribute is numeric.
546
     *
547
     * @param mixed $value
548
     *
549
     * @return bool
550
     */
551
    protected function validateNumeric($value)
552
    {
553
        return is_numeric($value);
554
    }
555
556
    /**
557
     * Validate that a required attribute exists.
558
     *
559
     * @param mixed $value
560
     *
561
     * @return bool
562
     */
563
    protected function validateRequired($value)
564
    {
565
        if (is_string($value)) {
566
            $value = trim($value);
567
        }
568
        return is_null($value) || empty($value)
569
            ? false
570
            : true;
571
    }
572
}
573