Completed
Push — master ( 58dc52...2147b5 )
by Jared
01:25
created

Validator::alpha()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 2
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 DateTimeZone;
15
use Exception;
16
17
/**
18
 * Validates one or more fields based upon certain filters.
19
 * Filters may be chained and will be executed in order:
20
 * i.e. new Validate('email') or new Validate('matching|password:8|required').
21
 *
22
 * NOTE: some filters may modify the data, which is passed in by reference
23
 */
24
class Validator
25
{
26
    /**
27
     * @var array
28
     */
29
    private static $config = [];
30
31
    /**
32
     * @var array|string
33
     */
34
    private $rules;
35
36
    /**
37
     * @var string|null
38
     */
39
    private $failingRule;
40
41
    /**
42
     * Changes settings for the validator.
43
     *
44
     * @param array $config
45
     */
46
    public static function configure($config)
47
    {
48
        self::$config = array_replace(self::$config, (array) $config);
49
    }
50
51
    /**
52
     * @param array|string $rules can be key-value array matching data or a string
53
     */
54
    public function __construct($rules)
55
    {
56
        $this->rules = $rules;
57
    }
58
59
    /**
60
     * Gets the rules.
61
     *
62
     * @return array|string
63
     */
64
    public function getRules()
65
    {
66
        return $this->rules;
67
    }
68
69
    /**
70
     * Validates the given data against the rules.
71
     *
72
     * @param array|mixed $data can be key-value array matching rules or a single value
73
     */
74
    public function validate(&$data): bool
75
    {
76
        if (is_array($this->rules)) {
77
            $validated = true;
78
79
            foreach ($this->rules as $key => $rule) {
80
                $result = self::validateRule($data[$key], $rule);
81
                $validated = $validated && $result;
82
            }
83
84
            return $validated;
85
        }
86
87
        return self::validateRule($data, $this->rules);
88
    }
89
90
    /**
91
     * Gets the failing rule.
92
     */
93
    public function getFailingRule(): ?string
94
    {
95
        return $this->failingRule;
96
    }
97
98
    /**
99
     * Validates a value according to a rule or set of rules.
100
     * This will short-circuit on the first failing rule.
101
     *
102
     * @param mixed  $value
103
     * @param string $rule  rule string
104
     */
105
    private function validateRule(&$value, $rule): bool
106
    {
107
        $filters = explode('|', $rule);
108
109
        foreach ($filters as $filterStr) {
110
            $exp = explode(':', $filterStr);
111
            $filter = $exp[0];
112
            $result = $this->$filter($value, array_slice($exp, 1));
113
114
            if (!$result) {
115
                $this->failingRule = $filter;
116
117
                return false;
118
            }
119
        }
120
121
        return true;
122
    }
123
124
    ////////////////////////////////
125
    // Validators
126
    ////////////////////////////////
127
128
    /**
129
     * Validates an alpha string.
130
     * OPTIONAL alpha:5 can specify minimum length.
131
     *
132
     * @param mixed $value
133
     */
134
    private function alpha(&$value, array $parameters): bool
135
    {
136
        $minLength = $parameters[0] ?? 0;
137
138
        return preg_match('/^[A-Za-z]*$/', $value) && strlen($value) >= $minLength;
139
    }
140
141
    /**
142
     * Validates an alpha-numeric string
143
     * OPTIONAL alpha_numeric:6 can specify minimum length.
144
     *
145
     * @param mixed $value
146
     */
147
    private function alpha_numeric(&$value, array $parameters): bool
148
    {
149
        $minLength = $parameters[0] ?? 0;
150
151
        return preg_match('/^[A-Za-z0-9]*$/', $value) && strlen($value) >= $minLength;
152
    }
153
154
    /**
155
     * Validates an alpha-numeric string with dashes and underscores
156
     * OPTIONAL alpha_dash:7 can specify minimum length.
157
     *
158
     * @param mixed $value
159
     */
160
    private function alpha_dash(&$value, array $parameters): bool
161
    {
162
        $minLength = $parameters[0] ?? 0;
163
164
        return preg_match('/^[A-Za-z0-9_-]*$/', $value) && strlen($value) >= $minLength;
165
    }
166
167
    /**
168
     * Validates a boolean value.
169
     *
170
     * @param mixed $value
171
     */
172
    private function boolean(&$value): bool
173
    {
174
        $value = filter_var($value, FILTER_VALIDATE_BOOLEAN);
175
176
        return true;
177
    }
178
179
    /**
180
     * Validates an e-mail address.
181
     *
182
     * @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...
183
     * @param array  $parameters parameters for validation
184
     *
185
     * @return bool success
186
     */
187
    private function email(&$value, array $parameters): bool
0 ignored issues
show
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...
188
    {
189
        $value = trim(strtolower($value));
190
191
        return filter_var($value, FILTER_VALIDATE_EMAIL);
192
    }
193
194
    /**
195
     * Validates a value exists in an array. i.e. enum:blue,red,green,yellow.
196
     *
197
     * @param mixed $value
198
     */
199
    private function enum(&$value, array $parameters): bool
200
    {
201
        $enum = explode(',', $parameters[0]);
202
203
        return in_array($value, $enum);
204
    }
205
206
    /**
207
     * Validates a date string.
208
     *
209
     * @param mixed $value
210
     */
211
    private function date(&$value): bool
212
    {
213
        return strtotime($value);
214
    }
215
216
    /**
217
     * Validates an IP address.
218
     *
219
     * @param mixed $value
220
     */
221
    private function ip(&$value): bool
222
    {
223
        return filter_var($value, FILTER_VALIDATE_IP);
224
    }
225
226
    /**
227
     * Validates that an array of values matches. The array will
228
     * be flattened to a single value if it matches.
229
     *
230
     * @param mixed $value
231
     */
232
    private function matching(&$value): bool
233
    {
234
        if (!is_array($value)) {
235
            return true;
236
        }
237
238
        $matches = true;
239
        $cur = reset($value);
240
        foreach ($value as $v) {
241
            $matches = ($v == $cur) && $matches;
242
            $cur = $v;
243
        }
244
245
        if ($matches) {
246
            $value = $cur;
247
        }
248
249
        return $matches;
250
    }
251
252
    /**
253
     * Validates a number.
254
     * OPTIONAL numeric:int specifies a type.
255
     *
256
     * @param mixed $value
257
     */
258
    private function numeric(&$value, array $parameters): bool
259
    {
260
        if (!isset($parameters[0])) {
261
            return is_numeric($value);
262
        }
263
264
        $check = 'is_'.$parameters[0];
265
266
        return $check($value);
267
    }
268
269
    /**
270
     * Validates a password and hashes the value using
271
     * password_hash().
272
     * OPTIONAL password:10 sets the minimum length.
273
     *
274
     * @param mixed $value
275
     */
276
    private function password_php(&$value, array $parameters): bool
277
    {
278
        $minimumPasswordLength = (isset($parameters[0])) ? $parameters[0] : 8;
279
280
        if (strlen($value) < $minimumPasswordLength) {
281
            return false;
282
        }
283
284
        $parameters = [];
285
        if (isset(self::$config['password_cost'])) {
286
            $parameters['cost'] = self::$config['password_cost'];
287
        }
288
289
        $value = password_hash($value, PASSWORD_DEFAULT, $parameters);
290
291
        return true;
292
    }
293
294
    /**
295
     * Validates that a number falls within a range.
296
     *
297
     * @param mixed $value
298
     */
299
    private function range(&$value, array $parameters): bool
300
    {
301
        // check min
302
        if (isset($parameters[0]) && $value < $parameters[0]) {
303
            return false;
304
        }
305
306
        // check max
307
        if (isset($parameters[1]) && $value > $parameters[1]) {
308
            return false;
309
        }
310
311
        return true;
312
    }
313
314
    /**
315
     * Makes sure that a variable is not empty.
316
     *
317
     * @param mixed $value
318
     */
319
    private function required(&$value): bool
320
    {
321
        return !empty($value);
322
    }
323
324
    /**
325
     * Validates a string.
326
     * OPTIONAL string:5 supplies a minimum length
327
     *          string:1:5 supplies a minimum and maximum length.
328
     *
329
     * @param mixed $value
330
     */
331
    private function string(&$value, array $parameters): bool
332
    {
333
        if (!is_string($value)) {
334
            return false;
335
        }
336
337
        $len = strlen($value);
338
        $min = $parameters[0] ?? 0;
339
        $max = $parameters[1] ?? null;
340
341
        return $len >= $min && (!$max || $len <= $max);
342
    }
343
344
    /**
345
     * Validates a PHP time zone identifier.
346
     *
347
     * @param mixed $value
348
     */
349
    private function time_zone(&$value): bool
350
    {
351
        try {
352
            $tz = new DateTimeZone($value);
0 ignored issues
show
Unused Code introduced by
$tz 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...
353
        } catch (Exception $e) {
354
            return false;
355
        }
356
357
        return true;
358
    }
359
360
    /**
361
     * Validates a Unix timestamp. If the value is not a timestamp it will be
362
     * converted to one with `strtotime()`.
363
     *
364
     * @param mixed $value
365
     */
366
    private function timestamp(&$value): bool
367
    {
368
        if (ctype_digit((string) $value)) {
369
            return true;
370
        }
371
372
        $value = strtotime($value);
373
374
        return (bool) $value;
375
    }
376
377
    /**
378
     * Converts a Unix timestamp into a format compatible with database
379
     * timestamp types.
380
     *
381
     * @param mixed $value
382
     */
383
    private function db_timestamp(&$value): bool
384
    {
385
        if (is_integer($value)) {
386
            // MySQL datetime format
387
            $value = date('Y-m-d H:i:s', $value);
388
389
            return true;
390
        }
391
392
        return false;
393
    }
394
395
    /**
396
     * Validates a URL.
397
     *
398
     * @param mixed $value
399
     */
400
    private function url(&$value): bool
401
    {
402
        return filter_var($value, FILTER_VALIDATE_URL);
403
    }
404
405
    /////////////////////////
406
    // Uniqueness
407
    /////////////////////////
408
409
    /**
410
     * Checks if a value is unique for a property.
411
     *
412
     * @param mixed $value
413
     */
414
    public static function isUnique(Model $model, Property $property, $value): bool
415
    {
416
        return 0 == $model::query()->where([$property->getName() => $value])->count();
417
    }
418
}
419