Completed
Push — master ( b31e17...a9e5d9 )
by Jared
02:30
created

Validator::unique()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 11
rs 9.4286
cc 3
eloc 5
nc 2
nop 3
1
<?php
2
3
/**
4
 * @author Jared King <[email protected]>
5
 *
6
 * @link http://jaredtking.com
7
 *
8
 * @copyright 2015 Jared King
9
 * @license MIT
10
 */
11
namespace Pulsar;
12
13
use Infuse\Utility;
14
15
class Validator
16
{
17
    /**
18
     * @staticvar array
19
     */
20
    private static $config = [
21
        'salt' => '',
22
    ];
23
24
    /**
25
     * @var array
26
     */
27
    private $rules;
28
29
    /**
30
     * @var Errors
31
     */
32
    private $errors;
33
34
    /**
35
     * @var bool
36
     */
37
    private $skipRemaining;
38
39
    /**
40
     * These are rules that always run even if a value is not present.
41
     *
42
     * @staticvar array
43
     */
44
    private static $runsWhenNotPresent = [
45
        'required',
46
    ];
47
48
    /**
49
     * Changes settings for the validator.
50
     *
51
     * @param array $config
52
     */
53
    public static function configure($config)
54
    {
55
        self::$config = array_replace(self::$config, (array) $config);
56
    }
57
58
    /**
59
     * @var array
60
     * @var Errors
61
     */
62
    public function __construct(array $rules, Errors $errors = null)
63
    {
64
        // parse rule strings if used
65
        foreach ($rules as &$rules2) {
66
            if (!is_array($rules2)) {
67
                $rules2 = $this->buildRulesFromStr($rules2);
68
            }
69
        }
70
71
        $this->rules = $rules;
72
        $this->errors = $errors;
73
    }
74
75
    /**
76
     * Validates whether an input passes the validator's rules.
77
     *
78
     * @param array $data
79
     *
80
     * @return bool
81
     */
82
    public function validate(array &$data)
83
    {
84
        $validated = true;
85
        foreach ($this->rules as $name => $rules) {
86
            // if a value is not present then skip any validations
87
            if ((!array_key_exists($name, $data) || !$this->required($data[$name])) && !$this->runsWhenNotPresent($rules)) {
88
                continue;
89
            }
90
91
            if (!isset($data[$name])) {
92
                $data[$name] = null;
93
            }
94
95
            $this->skipRemaining = false;
96
97
            foreach ($rules as $rule) {
98
                list($method, $parameters) = $rule;
99
100
                $valid = self::$method($data[$name], $parameters, $name);
101
                $validated = $validated && $valid;
102
103
                if (!$valid && $this->errors) {
104
                    $this->errors->add($name, "pulsar.validation.$method");
105
                }
106
107
                if ($this->skipRemaining) {
108
                    break;
109
                }
110
            }
111
        }
112
113
        return $validated;
114
    }
115
116
    /**
117
     * Parses a string into a list of rules.
118
     * Rule strings have the form "numeric|range:10,30" where
119
     * '|' separates rules and ':' allows a comma-separated list
120
     * of parameters to be specified. This example would generate 
121
     * [['numeric', []], ['range', [10, 30]]].
122
     *
123
     * @param string $rules
0 ignored issues
show
Bug introduced by
There is no parameter named $rules. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
124
     *
125
     * @return array
126
     */
127
    private function buildRulesFromStr($str)
128
    {
129
        $rules = [];
130
131
        // explodes the string into a a list of strings
132
        // containing rules and parameters
133
        $pieces = explode('|', $str);
134
        foreach ($pieces as $piece) {
135
            $exp = explode(':', $piece);
136
            // [0] = rule method
137
            $method = $exp[0];
0 ignored issues
show
Unused Code introduced by
$method is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
138
            // [1] = optional method parameters
139
            $parameters = isset($exp[1]) ? explode(',', $exp[1]) : [];
140
141
            $rules[] = [$exp[0], $parameters];
142
        }
143
144
        return $rules;
145
    }
146
147
    /**
148
     * Checks if the rules should be ran when a value is empty or
149
     * not present.
150
     *
151
     * @param array $rules
152
     *
153
     * @return bool
154
     */
155
    private function runsWhenNotPresent(array $rules)
156
    {
157
        foreach ($rules as $rule) {
158
            if (in_array($rule[0], self::$runsWhenNotPresent)) {
159
                return true;
160
            }
161
        }
162
163
        return false;
164
    }
165
166
    /**
167
     * Skips remaining rules.
168
     *
169
     * @return self
170
     */
171
    private function skipRemaining()
172
    {
173
        $this->skipRemaining = true;
174
175
        return $this;
176
    }
177
178
    ////////////////////////////////
179
    // Rules
180
    ////////////////////////////////
181
182
    /**
183
     * Validates an alpha string.
184
     * OPTIONAL alpha:5 can specify minimum length.
185
     *
186
     * @param mixed $value
187
     * @param array $parameters
188
     *
189
     * @return bool
190
     */
191
    private function alpha($value, array $parameters)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
192
    {
193
        return preg_match('/^[A-Za-z]*$/', $value) && strlen($value) >= array_value($parameters, 0);
194
    }
195
196
    /**
197
     * Validates an alpha-numeric string
198
     * OPTIONAL alpha_numeric:6 can specify minimum length.
199
     *
200
     * @param mixed $value
201
     * @param array $parameters
202
     *
203
     * @return bool
204
     */
205
    private function alpha_numeric($value, array $parameters)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
206
    {
207
        return preg_match('/^[A-Za-z0-9]*$/', $value) && strlen($value) >= array_value($parameters, 0);
208
    }
209
210
    /**
211
     * Validates an alpha-numeric string with dashes and underscores
212
     * OPTIONAL alpha_dash:7 can specify minimum length.
213
     *
214
     * @param mixed $value
215
     * @param array $parameters
216
     *
217
     * @return bool
218
     */
219
    private function alpha_dash($value, array $parameters)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
220
    {
221
        return preg_match('/^[A-Za-z0-9_-]*$/', $value) && strlen($value) >= array_value($parameters, 0);
222
    }
223
224
    /**
225
     * Validates a boolean value.
226
     *
227
     * @param mixed $value
228
     *
229
     * @return bool
230
     */
231
    private function boolean(&$value)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
232
    {
233
        $value = filter_var($value, FILTER_VALIDATE_BOOLEAN);
234
235
        return true;
236
    }
237
238
    /**
239
     * Validates by calling a given function.
240
     *
241
     * @param mixed $value
242
     * @param array $parameters
243
     *
244
     * @return bool
245
     */
246
    private function custom(&$value, array $parameters)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
247
    {
248
        $method = $parameters[0];
249
        $parameters2 = (array) array_slice($parameters, 1);
250
251
        return $method($value, $parameters2);
252
    }
253
254
    /**
255
     * Validates an e-mail address.
256
     *
257
     * @param string $email      e-mail address
0 ignored issues
show
Bug introduced by
There is no parameter named $email. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
258
     * @param array  $parameters parameters for validation
259
     *
260
     * @return bool success
261
     */
262
    private function email(&$value, array $parameters)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
Unused Code introduced by
The parameter $parameters is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
263
    {
264
        $value = trim(strtolower($value));
265
266
        return filter_var($value, FILTER_VALIDATE_EMAIL);
267
    }
268
269
    /**
270
     * Validates a value exists in an array. i.e. enum:blue,red,green,yellow.
271
     *
272
     * @param mixed $value
273
     * @param array $parameters
274
     *
275
     * @return bool
276
     */
277
    private function enum($value, array $parameters)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
278
    {
279
        return in_array($value, $parameters);
280
    }
281
282
    /**
283
     * Validates a date string.
284
     *
285
     * @param mixed $value
286
     *
287
     * @return bool
288
     */
289
    private function date($value)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
290
    {
291
        return strtotime($value);
292
    }
293
294
    /**
295
     * Validates an IP address.
296
     *
297
     * @param mixed $value
298
     *
299
     * @return bool
300
     */
301
    private function ip($value)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
302
    {
303
        return filter_var($value, FILTER_VALIDATE_IP);
304
    }
305
306
    /**
307
     * Validates that an array of values matches. The array will
308
     * be flattened to a single value if it matches.
309
     *
310
     * @param mixed $value
311
     *
312
     * @return bool
313
     */
314
    private function matching(&$value)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
315
    {
316
        if (!is_array($value)) {
317
            return true;
318
        }
319
320
        $matches = true;
321
        $cur = reset($value);
322
        foreach ($value as $v) {
323
            $matches = ($v == $cur) && $matches;
324
            $cur = $v;
325
        }
326
327
        if ($matches) {
328
            $value = $cur;
329
        }
330
331
        return $matches;
332
    }
333
334
    /**
335
     * Validates a number.
336
     * OPTIONAL numeric:int specifies a type.
337
     *
338
     * @param mixed $value
339
     * @param array $parameters
340
     *
341
     * @return bool
342
     */
343
    private function numeric($value, array $parameters)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
344
    {
345
        $check = 'is_'.array_value($parameters, 0);
346
347
        return (!isset($parameters[0])) ? is_numeric($value) : $check($value);
348
    }
349
350
    /**
351
     * Validates a password and hashes the value.
352
     * OPTIONAL password:10 sets the minimum length.
353
     *
354
     * @param mixed $value
355
     * @param array $parameters
356
     *
357
     * @return bool
358
     */
359
    private function password(&$value, array $parameters)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
360
    {
361
        $minimumPasswordLength = (isset($parameters[0])) ? $parameters[0] : 8;
362
363
        if (strlen($value) < $minimumPasswordLength) {
364
            return false;
365
        }
366
367
        $value = Utility::encryptPassword($value, self::$config['salt']);
368
369
        return true;
370
    }
371
372
    /**
373
     * Validates that a number falls within a range.
374
     *
375
     * @param mixed $value
376
     * @param array $parameters
377
     *
378
     * @return bool
379
     */
380
    private function range($value, array $parameters)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
381
    {
382
        // check min
383
        if (isset($parameters[0]) && $value < $parameters[0]) {
384
            return false;
385
        }
386
387
        // check max
388
        if (isset($parameters[1]) && $value > $parameters[1]) {
389
            return false;
390
        }
391
392
        return true;
393
    }
394
395
    /**
396
     * Makes sure that a variable is not empty.
397
     *
398
     * @param mixed $value
399
     *
400
     * @return bool
401
     */
402
    private function required($value)
403
    {
404
        if ($value === null || $value === '') {
405
            return false;
406
        }
407
408
        if (is_array($value) && count($value) === 0) {
409
            return false;
410
        }
411
412
        return true;
413
    }
414
415
    /**
416
     * Skips any remaining rules for a field if the
417
     * value is empty.
418
     *
419
     * @param mixed $value
420
     *
421
     * @return bool
422
     */
423
    public function skip_empty(&$value)
424
    {
425
        if (empty($value)) {
426
            $value = null;
427
            $this->skipRemaining();
428
        }
429
430
        return true;
431
    }
432
433
    /**
434
     * Validates a string.
435
     * OPTIONAL string:5 supplies a minimum length
436
     *          string:1:5 supplies a minimum and maximum length.
437
     *
438
     * @param mixed $value
439
     * @param array $parameters
440
     *
441
     * @return bool
442
     */
443
    private function string($value, array $parameters)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
444
    {
445
        if (!is_string($value)) {
446
            return false;
447
        }
448
449
        $len = strlen($value);
450
        $min = array_value($parameters, 0);
451
        $max = array_value($parameters, 1);
452
453
        return $len >= $min && (!$max || $len <= $max);
454
    }
455
456
    /**
457
     * Validates a PHP time zone identifier.
458
     *
459
     * @param mixed $value
460
     *
461
     * @return bool
462
     */
463
    private function time_zone($value)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
464
    {
465
        // thanks to http://stackoverflow.com/questions/5816960/how-to-check-is-timezone-identifier-valid-from-code
466
        $valid = [];
467
        $tza = timezone_abbreviations_list();
468
        foreach ($tza as $zone) {
469
            foreach ($zone as $item) {
470
                $valid[$item['timezone_id']] = true;
471
            }
472
        }
473
        unset($valid['']);
474
475
        return !!array_value($valid, $value);
476
    }
477
478
    /**
479
     * Validates a Unix timestamp. If the value is not a timestamp it will be
480
     * converted to one with strtotime().
481
     *
482
     * @param mixed $value
483
     *
484
     * @return bool
485
     */
486
    private function timestamp(&$value)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
487
    {
488
        if (ctype_digit((string) $value)) {
489
            return true;
490
        }
491
492
        $value = strtotime($value);
493
494
        return !!$value;
495
    }
496
497
    /**
498
     * Converts a Unix timestamp into a format compatible with database
499
     * timestamp types.
500
     *
501
     * @param mixed $value
502
     *
503
     * @return bool
504
     */
505
    private function db_timestamp(&$value)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
506
    {
507
        if (is_integer($value)) {
508
            $value = Utility::unixToDb($value);
509
510
            return true;
511
        }
512
513
        return false;
514
    }
515
516
    /**
517
     * Checks if a value is unique for a model.
518
     *
519
     * @param mixed  $value
520
     * @param array  $parameters
521
     * @param string $name
522
     * @param Model  $model
0 ignored issues
show
Bug introduced by
There is no parameter named $model. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
523
     *
524
     * @return bool
525
     */
526
    private function unique($value, array $parameters, $name)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
Unused Code introduced by
The parameter $parameters is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
527
    {
528
        $model = $this->errors->getModel();
529
        // if the model has already been saved and its value has not
530
        // changed then there is no need to check for uniqueness
531
        if ($model->persisted() && $value == $model->ignoreUnsaved()->$name) {
532
            return true;
533
        }
534
535
        return $model::totalRecords([$name => $value]) == 0;
536
    }
537
538
    /**
539
     * Validates a URL.
540
     *
541
     * @param mixed $value
542
     *
543
     * @return bool
544
     */
545
    private function url($value)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
546
    {
547
        return filter_var($value, FILTER_VALIDATE_URL);
548
    }
549
}
550