Completed
Push — master ( c079a8...7e06c0 )
by Jared
01:34
created

Validator::validate()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
c 0
b 0
f 0
rs 9.7666
cc 3
nc 3
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 Pulsar\Interfaces\ValidationRuleInterface;
15
use Pulsar\Validation\Alpha;
16
use Pulsar\Validation\AlphaDash;
17
use Pulsar\Validation\AlphaNumeric;
18
use Pulsar\Validation\Boolean;
19
use Pulsar\Validation\Date;
20
use Pulsar\Validation\Email;
21
use Pulsar\Validation\Enum;
22
use Pulsar\Validation\Ip;
23
use Pulsar\Validation\Matching;
24
use Pulsar\Validation\MySqlDatetime;
25
use Pulsar\Validation\Numeric;
26
use Pulsar\Validation\Password;
27
use Pulsar\Validation\Range;
28
use Pulsar\Validation\Required;
29
use Pulsar\Validation\Strings;
30
use Pulsar\Validation\Timestamp;
31
use Pulsar\Validation\Timezone;
32
use Pulsar\Validation\Unique;
33
use Pulsar\Validation\Url;
34
35
/**
36
 * Validates one or more fields based upon certain filters.
37
 * Filters may be chained and will be executed in order:
38
 * i.e. new Validate('email') or new Validate('matching|password:8|required').
39
 *
40
 * NOTE: some filters may modify the data, which is passed in by reference
41
 */
42
final class Validator
43
{
44
    /**
45
     * @var string[]
46
     */
47
    private static $validators = [
48
        'alpha' => Alpha::class,
49
        'alpha_dash' => AlphaDash::class,
50
        'alpha_numeric' => AlphaNumeric::class,
51
        'boolean' => Boolean::class,
52
        'date' => Date::class,
53
        'db_timestamp' => MySqlDatetime::class,
54
        'email' => Email::class,
55
        'enum' => Enum::class,
56
        'ip' => Ip::class,
57
        'matching' => Matching::class,
58
        'numeric' => Numeric::class,
59
        'password' => Password::class,
60
        'password_php' => Password::class, // deprecated
61
        'range' => Range::class,
62
        'required' => Required::class,
63
        'string' => Strings::class,
64
        'timestamp' => Timestamp::class,
65
        'time_zone' => Timezone::class,
66
        'unique' => Unique::class,
67
        'url' => Url::class,
68
    ];
69
70
    /**
71
     * @var ValidationRuleInterface[]
72
     */
73
    private static $instances = [];
74
75
    /**
76
     * @var array
77
     */
78
    private $rules;
79
80
    /**
81
     * @var string|null
82
     */
83
    private $failingRule;
84
85
    /**
86
     * Rules can be defined in these formats:
87
     * - [['matching'], ['string', 'min' => '5']]
88
     * - ['matching', ['string', 'min' => '5']]
89
     * -  ['string', 'min' => '5']
90
     * - matching|password.
91
     *
92
     * @param array|string $rules
93
     */
94
    public function __construct($rules)
95
    {
96
        // parses this format: matching|password_php
97
        if (!is_array($rules)) {
98
            $rules = explode('|', $rules);
99
        }
100
101
        // parses this format: ['string', 'min' => 5]
102
        if (count($rules) > 0 && !is_array($rules[0]) && !isset($rules[1])) {
103
            $rules = [$rules];
104
        }
105
106
        foreach ($rules as &$rule) {
107
            if (!is_array($rule)) {
108
                $rule = [$rule];
109
            }
110
        }
111
112
        $this->rules = $rules;
113
    }
114
115
    /**
116
     * Gets the rules.
117
     */
118
    public function getRules(): array
119
    {
120
        return $this->rules;
121
    }
122
123
    /**
124
     * Validates the given value against the rules.
125
     *
126
     * @param mixed $value
127
     */
128
    public function validate(&$value, Model $model): bool
129
    {
130
        foreach ($this->rules as $options) {
131
            $name = $options[0];
132
            unset($options[0]);
133
134
            if (!$this->getRule($name)->validate($value, $options, $model)) {
135
                $this->failingRule = $name;
136
137
                return false;
138
            }
139
        }
140
141
        return true;
142
    }
143
144
    /**
145
     * Gets the failing rule.
146
     */
147
    public function getFailingRule(): ?string
148
    {
149
        return $this->failingRule;
150
    }
151
152
    private function getRule(string $name): ValidationRuleInterface
153
    {
154
        if (!isset(self::$instances[$name])) {
155
            if (!isset(self::$validators[$name])) {
156
                throw new \InvalidArgumentException('Invalid validation rule: '.$name);
157
            }
158
159
            $class = self::$validators[$name];
160
            self::$instances[$name] = new $class();
161
        }
162
163
        return self::$instances[$name];
164
    }
165
166
    /**
167
     * Validates and marshals a property value prior to saving.
168
     *
169
     * @param Property $property property definition
170
     * @param mixed    $value
171
     */
172
    public static function validateProperty(Model $model, Property $property, &$value): bool
173
    {
174
        // assume empty string is a null value for properties
175
        // that are marked as optionally-null
176
        if ($property->isNullable() && ('' === $value || null === $value)) {
177
            $value = null;
178
179
            return true;
180
        }
181
182
        $valid = true;
183
        $validationRules = $property->getValidationRules();
184
        if (is_callable($validationRules)) {
185
            $valid = call_user_func_array($validationRules, [$value]);
186
            $error = 'pulsar.validation.failed';
187
        } elseif ($validationRules) {
188
            $validator = new Validator($validationRules);
189
            $valid = $validator->validate($value, $model);
190
            $error = 'pulsar.validation.'.$validator->getFailingRule();
191
        }
192
193
        if (!$valid) {
194
            $model->getErrors()->add($error, [
0 ignored issues
show
Bug introduced by
The variable $error does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
195
                'field' => $property->getName(),
196
                'field_name' => $property->getTitle($model),
197
            ]);
198
        }
199
200
        return $valid;
201
    }
202
}
203