Passed
Push — develop ( 535216...2a3c68 )
by Paul
07:18
created

Validator::hasRule()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace GeminiLabs\SiteReviews\Modules;
4
5
use GeminiLabs\SiteReviews\Defaults\ValidationStringsDefaults;
6
use GeminiLabs\SiteReviews\Helper;
7
use GeminiLabs\SiteReviews\Helpers\Str;
8
use GeminiLabs\SiteReviews\Modules\Validator\ValidationRules;
9
10
/**
11
 * @see \Illuminate\Validation\Validator (5.3)
12
 */
13
class Validator
14
{
15
    use ValidationRules;
16
17
    /**
18
     * @var array
19
     */
20
    public $errors = [];
21
22
    /**
23
     * The data under validation.
24
     * @var array
25
     */
26
    protected $data = [];
27
28
    /**
29
     * The failed validation rules.
30
     * @var array
31
     */
32
    protected $failedRules = [];
33
34
    /**
35
     * The rules to be applied to the data.
36
     * @var array
37
     */
38
    protected $rules = [];
39
40
    /**
41
     * The size related validation rules.
42
     * @var array
43
     */
44
    protected $sizeRules = [
45
        'Between', 'Max', 'Min',
46
    ];
47
48
    /**
49
     * The validation rules that imply the field is required.
50
     * @var array
51
     */
52
    protected $implicitRules = [
53
        'Required',
54
    ];
55
56
    /**
57
     * The numeric related validation rules.
58
     * @var array
59
     */
60
    protected $numericRules = [
61
        'Number',
62
    ];
63
64
    /**
65
     * Run the validator's rules against its data.
66
     */
67 6
    public function validate(array $data, array $rules = []): array
68
    {
69 6
        $this->data = $data;
70 6
        $this->setRules($rules);
71 6
        foreach ($this->rules as $attribute => $rules) {
72 6
            foreach ($rules as $rule) {
73 6
                $this->validateAttribute($attribute, $rule);
74 6
                if ($this->shouldStopValidating($attribute)) {
75 2
                    break;
76
                }
77
            }
78
        }
79 6
        return $this->errors;
80
    }
81
82
    /**
83
     * Validate a given attribute against a rule.
84
     * @throws \BadMethodCallException
85
     */
86 6
    public function validateAttribute(string $attribute, string $rule): void
87
    {
88 6
        [$rule, $parameters] = $this->parseRule($rule);
89 6
        if ('' === $rule) {
90
            return;
91
        }
92 6
        $value = $this->getValue($attribute);
93 6
        $method = Helper::buildMethodName($rule, 'validate');
94 6
        if (!method_exists($this, $method)) {
95
            throw new \BadMethodCallException("Method [$method] does not exist.");
96
        }
97 6
        if (!$this->$method($value, $attribute, $parameters)) {
98 2
            $this->addFailure($attribute, $rule, $parameters);
99
        }
100
    }
101
102
    /**
103
     * Add an error message to the validator's collection of errors.
104
     */
105 2
    protected function addError(string $attribute, string $rule, array $parameters): void
106
    {
107 2
        $message = $this->getMessage($attribute, $rule, $parameters);
108 2
        $this->errors[$attribute][] = $message;
109
    }
110
111
    /**
112
     * Add a failed rule and error message to the collection.
113
     */
114 2
    protected function addFailure(string $attribute, string $rule, array $parameters): void
115
    {
116 2
        $this->addError($attribute, $rule, $parameters);
117 2
        $this->failedRules[$attribute][$rule] = $parameters;
118
    }
119
120
    /**
121
     * Get the data type of the given attribute.
122
     */
123
    protected function getAttributeType(string $attribute): string
124
    {
125
        return !$this->hasRule($attribute, $this->numericRules)
126
            ? 'length'
127
            : '';
128
    }
129
130
    /**
131
     * Get the validation message for an attribute and rule.
132
     */
133 2
    protected function getMessage(string $attribute, string $rule, array $parameters): ?string
134
    {
135 2
        if (in_array($rule, $this->sizeRules)) {
136
            return $this->getSizeMessage($attribute, $rule, $parameters);
137
        }
138 2
        $lowerRule = Str::snakeCase($rule);
139 2
        return $this->translator($lowerRule, $parameters);
140
    }
141
142
    /**
143
     * Get a rule and its parameters for a given attribute.
144
     */
145 6
    protected function getRule(string $attribute, array $rules): ?array
146
    {
147 6
        if (!array_key_exists($attribute, $this->rules)) {
148
            return null;
149
        }
150 6
        $rules = (array) $rules;
151 6
        foreach ($this->rules[$attribute] as $rule) {
152 6
            [$rule, $parameters] = $this->parseRule($rule);
153 6
            if (in_array($rule, $rules)) {
154 6
                return [$rule, $parameters];
155
            }
156
        }
157 6
        return null;
158
    }
159
160
    /**
161
     * Get the size of an attribute.
162
     * @param mixed $value
163
     */
164 5
    protected function getSize(string $attribute, $value): int
165
    {
166 5
        $hasNumeric = $this->hasRule($attribute, $this->numericRules);
167 5
        if (is_numeric($value) && $hasNumeric) {
168
            return (int) $value;
169 5
        } elseif (is_array($value)) {
170
            return count($value);
171
        }
172 5
        return mb_strlen((string) $value);
173
    }
174
175
    /**
176
     * Get the proper error message for an attribute and size rule.
177
     */
178
    protected function getSizeMessage(string $attribute, string $rule, array $parameters): string
179
    {
180
        $type = $this->getAttributeType($attribute);
181
        $lowerRule = Str::snakeCase($rule.$type);
182
        return $this->translator($lowerRule, $parameters);
183
    }
184
185
    /**
186
     * Get the value of a given attribute.
187
     * @return mixed
188
     */
189 6
    protected function getValue(string $attribute)
190
    {
191 6
        if (isset($this->data[$attribute])) {
192 6
            return $this->data[$attribute];
193
        }
194
    }
195
196
    /**
197
     * Determine if the given attribute has a rule in the given set.
198
     */
199 6
    protected function hasRule(string $attribute, array $rules): bool
200
    {
201 6
        return !is_null($this->getRule($attribute, $rules));
202
    }
203
204
    /**
205
     * Parse a parameter list.
206
     */
207 5
    protected function parseParameters(string $rule, string $parameter): array
208
    {
209 5
        return 'regex' === strtolower($rule)
210
            ? [$parameter]
211 5
            : str_getcsv($parameter);
212
    }
213
214
    /**
215
     * Extract the rule name and parameters from a rule.
216
     */
217 6
    protected function parseRule(string $rule): array
218
    {
219 6
        $parameters = [];
220 6
        if (str_contains($rule, ':')) {
221 5
            [$rule, $parameter] = explode(':', $rule, 2);
222 5
            $parameters = $this->parseParameters($rule, $parameter);
223
        }
224 6
        $rule = Str::camelCase($rule);
225 6
        return [$rule, $parameters];
226
    }
227
228
    /**
229
     * Set the validation rules.
230
     */
231 6
    protected function setRules(array $rules): void
232
    {
233 6
        foreach ($rules as $key => $rule) {
234 6
            $validationRules = is_string($rule)
235 6
                ? explode('|', $rule)
236 6
                : $rule;
237
            // unset rules if the attribute is not required and the value is an empty string
238 6
            if (empty(array_intersect(['accepted', 'required'], $validationRules)) && '' === $this->getValue($key)) {
239
                $validationRules = [];
240
            }
241 6
            $rules[$key] = $validationRules;
242
        }
243 6
        $this->rules = $rules;
244
    }
245
246
    /**
247
     * Check if we should stop further validations on a given attribute.
248
     */
249 6
    protected function shouldStopValidating(string $attribute): bool
250
    {
251 6
        return $this->hasRule($attribute, $this->implicitRules)
252 6
            && isset($this->failedRules[$attribute])
253 6
            && array_intersect(array_keys($this->failedRules[$attribute]), $this->implicitRules);
254
    }
255
256
    /**
257
     * Returns a translated message for the attribute.
258
     */
259 2
    protected function translator($key, array $parameters): string
260
    {
261 2
        $strings = glsr(ValidationStringsDefaults::class)->defaults();
262 2
        if (isset($strings[$key])) {
263 2
            return $this->replace($strings[$key], $parameters);
264
        }
265
        return 'error';
266
    }
267
}
268