Completed
Push — master ( 308fa6...9c8755 )
by Jared
01:30
created

Validator   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 180
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
wmc 23
lcom 1
cbo 4
dl 0
loc 180
rs 10
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 20 7
A getRules() 0 4 1
A validate() 0 16 3
A getFailingRule() 0 4 1
A getFailingRuleOptions() 0 4 1
A getRule() 0 13 3
B validateProperty() 0 34 7
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\Callables;
20
use Pulsar\Validation\Date;
21
use Pulsar\Validation\Email;
22
use Pulsar\Validation\Encrypt;
23
use Pulsar\Validation\Enum;
24
use Pulsar\Validation\Ip;
25
use Pulsar\Validation\Matching;
26
use Pulsar\Validation\MySqlDatetime;
27
use Pulsar\Validation\Numeric;
28
use Pulsar\Validation\Password;
29
use Pulsar\Validation\Range;
30
use Pulsar\Validation\Required;
31
use Pulsar\Validation\Strings;
32
use Pulsar\Validation\Timestamp;
33
use Pulsar\Validation\Timezone;
34
use Pulsar\Validation\Unique;
35
use Pulsar\Validation\Url;
36
37
/**
38
 * Validates one or more fields based upon certain filters.
39
 * Filters may be chained and will be executed in order:
40
 * i.e. new Validate('email') or new Validate('matching|password:8|required').
41
 *
42
 * NOTE: some filters may modify the data, which is passed in by reference
43
 */
44
final class Validator
45
{
46
    /**
47
     * @var string[]
48
     */
49
    private static $validators = [
50
        'alpha' => Alpha::class,
51
        'alpha_dash' => AlphaDash::class,
52
        'alpha_numeric' => AlphaNumeric::class,
53
        'boolean' => Boolean::class,
54
        'callable' => Callables::class,
55
        'date' => Date::class,
56
        'db_timestamp' => MySqlDatetime::class,
57
        'encrypt' => Encrypt::class,
58
        'email' => Email::class,
59
        'enum' => Enum::class,
60
        'ip' => Ip::class,
61
        'matching' => Matching::class,
62
        'numeric' => Numeric::class,
63
        'password' => Password::class,
64
        'range' => Range::class,
65
        'required' => Required::class,
66
        'string' => Strings::class,
67
        'time_zone' => Timezone::class,
68
        'timestamp' => Timestamp::class,
69
        'unique' => Unique::class,
70
        'url' => Url::class,
71
    ];
72
73
    /**
74
     * @var ValidationRuleInterface[]
75
     */
76
    private static $instances = [];
77
78
    /**
79
     * @var array
80
     */
81
    private $rules;
82
83
    /**
84
     * @var string|null
85
     */
86
    private $failingRule;
87
88
    /**
89
     * @var array
90
     */
91
    private $failingOptions;
92
93
    /**
94
     * Rules can be defined in these formats:
95
     * - [['matching'], ['string', 'min' => '5']]
96
     * - ['matching', ['string', 'min' => '5']]
97
     * -  ['string', 'min' => '5']
98
     * - matching|password.
99
     *
100
     * @param array|string $rules
101
     */
102
    public function __construct($rules)
103
    {
104
        // parses this format: matching|password_php
105
        if (!is_array($rules)) {
106
            $rules = explode('|', $rules);
107
        }
108
109
        // parses this format: ['string', 'min' => 5]
110
        if (count($rules) > 0 && !is_array($rules[0]) && !isset($rules[1])) {
111
            $rules = [$rules];
112
        }
113
114
        foreach ($rules as &$rule) {
115
            if (!is_array($rule)) {
116
                $rule = [$rule];
117
            }
118
        }
119
120
        $this->rules = $rules;
121
    }
122
123
    /**
124
     * Gets the rules.
125
     */
126
    public function getRules(): array
127
    {
128
        return $this->rules;
129
    }
130
131
    /**
132
     * Validates the given value against the rules.
133
     *
134
     * @param mixed $value
135
     */
136
    public function validate(&$value, Model $model): bool
137
    {
138
        foreach ($this->rules as $options) {
139
            $name = $options[0];
140
            unset($options[0]);
141
142
            if (!$this->getRule($name)->validate($value, $options, $model)) {
143
                $this->failingRule = $name;
144
                $this->failingOptions = $options;
145
146
                return false;
147
            }
148
        }
149
150
        return true;
151
    }
152
153
    /**
154
     * Gets the failing rule.
155
     */
156
    public function getFailingRule(): ?string
157
    {
158
        return $this->failingRule;
159
    }
160
161
    /**
162
     * Gets the options of the failing rule.
163
     */
164
    public function getFailingRuleOptions(): ?array
165
    {
166
        return $this->failingOptions;
167
    }
168
169
    private function getRule(string $name): ValidationRuleInterface
170
    {
171
        if (!isset(self::$instances[$name])) {
172
            if (!isset(self::$validators[$name])) {
173
                throw new \InvalidArgumentException('Invalid validation rule: '.$name);
174
            }
175
176
            $class = self::$validators[$name];
177
            self::$instances[$name] = new $class();
178
        }
179
180
        return self::$instances[$name];
181
    }
182
183
    /**
184
     * Validates and marshals a property value prior to saving.
185
     *
186
     * @param Property $property property definition
187
     * @param mixed    $value
188
     */
189
    public static function validateProperty(Model $model, Property $property, &$value): bool
190
    {
191
        // assume empty string is a null value for properties
192
        // that are marked as optionally-null
193
        if ($property->isNullable() && ('' === $value || null === $value)) {
194
            $value = null;
195
196
            return true;
197
        }
198
199
        $validationRules = $property->getValidationRules();
200
        if (!$validationRules) {
201
            return true;
202
        }
203
204
        $validator = new Validator($validationRules);
205
        if ($validator->validate($value, $model)) {
206
            return true;
207
        }
208
209
        // add a validation error message if one was not already added
210
        $errors = $model->getErrors();
211
        if (!$errors->has($property->getName())) {
212
            $params = [
213
                'field' => $property->getName(),
214
                'field_name' => $property->getTitle($model),
215
                'rule' => $validator->getFailingRule(),
216
            ];
217
            $params = array_replace($validator->getFailingRuleOptions(), $params);
218
            $errors->add('pulsar.validation.'.$validator->getFailingRule(), $params);
219
        }
220
221
        return false;
222
    }
223
}
224