Skip to content

Commit ed9387b

Browse files
feat: validate wildcard array elements
1 parent f6af317 commit ed9387b

File tree

3 files changed

+158
-36
lines changed

3 files changed

+158
-36
lines changed

.gitattributes

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
/tests export-ignore
2-
/phpunit.xml export-ignore
2+
/phpunit.xml export-ignore
3+
.gitattributes export-ignore
4+
.gitignore export-ignore
5+
composer.lock export-ignore

src/Validator.php

Lines changed: 102 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -15,63 +15,130 @@ class Validator
1515

1616
private $validated = [];
1717

18+
private $_customMessages = [];
19+
20+
private $_attributeLabels = [];
21+
22+
private $_data = [];
23+
1824
public function make($data, $ruleFields, $customMessages = null, $attributeLabels = null)
1925
{
26+
$this->_data = $data;
27+
$this->_customMessages = $customMessages;
28+
$this->_attributeLabels = $attributeLabels;
29+
2030
$this->inputContainer = new InputDataContainer($data);
2131

2232
$this->errorBag = new ErrorBag();
2333

2434
foreach ($ruleFields as $field => $rules) {
25-
$attributeLabel = $field;
35+
$this->processAndValidateField($field, $rules);
36+
}
2637

27-
if (isset($attributeLabels[$field])) {
28-
$attributeLabel = $attributeLabels[$field];
29-
}
38+
return $this;
39+
}
40+
41+
public function processAndValidateField($field, $rules)
42+
{
43+
$attributeLabel = $field;
3044

31-
$this->inputContainer->setAttributeKey($field);
45+
$fieldKeys = $this->processWildcardFieldKey($field);
3246

33-
$this->inputContainer->setAttributeLabel($attributeLabel);
47+
foreach ($fieldKeys as $fieldKey) {
48+
$this->validateField($fieldKey, $rules, $attributeLabel);
49+
}
50+
}
3451

35-
$value = $this->inputContainer->getAttributeValue();
52+
public function processWildcardFieldKey($field)
53+
{
54+
if (strpos($field, '*') === false) {
55+
return [$field];
56+
}
3657

37-
$this->setValidatedData($field, $data, $value);
58+
$nestedKeyQueue = explode('.', $field);
59+
$visitedFieldKeys = [];
60+
$dataByKey = (array) $this->_data;
3861

39-
if (\in_array('nullable', $rules) && $this->isEmpty($value)) {
40-
continue;
62+
while ($head = array_shift($nestedKeyQueue)) {
63+
if (trim($head) === '*') {
64+
$keys = array_keys((array) $dataByKey);
65+
$dataByKey = count($keys) && \array_key_exists($keys[0], $dataByKey) ? $dataByKey[$keys[0]] : [];
66+
} else {
67+
$keys = [$head];
68+
$dataByKey = \array_key_exists($head, $dataByKey) ? $dataByKey[$head] : [];
4169
}
4270

43-
foreach ($rules as $ruleName) {
44-
if (\is_string($ruleName) && strpos($ruleName, 'sanitize') !== false) {
45-
$this->applyFilter($ruleName, $field, $value);
46-
47-
continue;
71+
if (empty($visitedFieldKeys)) {
72+
foreach ($keys as $keyToVisit) {
73+
$visitedFieldKeys[$keyToVisit] = 1;
4874
}
49-
50-
if (is_subclass_of($ruleName, Rule::class)) {
51-
$ruleClass = \is_object($ruleName) ? $ruleName : new $ruleName();
52-
} else {
53-
list($ruleName, $paramValues) = $this->parseRule($ruleName);
54-
$ruleClass = $this->resolveRule($ruleName);
75+
} else {
76+
foreach ($visitedFieldKeys as $key => $v) {
77+
foreach ($keys as $keyToVisit) {
78+
unset($visitedFieldKeys[$key]);
79+
$visitedFieldKeys["{$key}.{$keyToVisit}"] = 1;
80+
}
5581
}
82+
}
83+
}
5684

57-
$ruleClass->setInputDataContainer($this->inputContainer);
58-
$ruleClass->setRuleName($ruleName);
85+
return array_keys($visitedFieldKeys);
86+
}
5987

60-
if (!empty($paramValues)) {
61-
$ruleClass->setParameterValues($ruleClass->getParamKeys(), $paramValues);
62-
}
88+
public function validateField($fieldKey, $rules, $fieldLabel)
89+
{
90+
if (isset($this->_attributeLabels[$fieldLabel])) {
91+
$attributeLabel = $this->_attributeLabels[$fieldLabel];
92+
} else {
93+
$attributeLabel = $fieldKey;
94+
}
6395

64-
$isValidated = $ruleClass->validate($this->inputContainer->getAttributeValue());
96+
$this->inputContainer->setAttributeKey($fieldKey);
6597

66-
if (!$isValidated) {
67-
$this->errorBag->addError($ruleClass, $customMessages);
98+
$this->inputContainer->setAttributeLabel($attributeLabel);
6899

69-
break;
70-
}
71-
}
100+
$value = $this->inputContainer->getAttributeValue();
101+
102+
$this->setValidatedData($fieldKey, $this->_data, $value);
103+
104+
if (\in_array('nullable', $rules) && $this->isEmpty($value)) {
105+
return;
72106
}
73107

74-
return $this;
108+
$this->validateByRules($fieldKey, $value, $rules);
109+
}
110+
111+
public function validateByRules($fieldKey, $value, $rules)
112+
{
113+
foreach ($rules as $ruleName) {
114+
if (\is_string($ruleName) && strpos($ruleName, 'sanitize') !== false) {
115+
$this->applyFilter($ruleName, $fieldKey, $value);
116+
117+
continue;
118+
}
119+
120+
if (is_subclass_of($ruleName, Rule::class)) {
121+
$ruleClass = \is_object($ruleName) ? $ruleName : new $ruleName();
122+
} else {
123+
list($ruleName, $paramValues) = $this->parseRule($ruleName);
124+
$ruleClass = $this->resolveRule($ruleName);
125+
}
126+
127+
$ruleClass->setInputDataContainer($this->inputContainer);
128+
$ruleClass->setRuleName($ruleName);
129+
130+
if (!empty($paramValues)) {
131+
$ruleClass->setParameterValues($ruleClass->getParamKeys(), $paramValues);
132+
}
133+
134+
$isValidated = $ruleClass->validate($this->inputContainer->getAttributeValue());
135+
136+
if (!$isValidated) {
137+
$this->errorBag->addError($ruleClass, $this->_customMessages);
138+
139+
break;
140+
}
141+
}
75142
}
76143

77144
public function fails()
@@ -96,8 +163,8 @@ private function resolveRule($ruleName)
96163
}
97164

98165
$ruleClass = __NAMESPACE__
99-
. '\\Rules\\'
100-
. str_replace(' ', '', ucwords(str_replace('_', ' ', $ruleName)))
166+
. '\\Rules\\'
167+
. str_replace(' ', '', ucwords(str_replace('_', ' ', $ruleName)))
101168
. 'Rule';
102169

103170
if (!class_exists($ruleClass)) {

tests/ValidateWildcardPathTest.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
use BitApps\WPValidator\Validator;
4+
5+
test(
6+
'validate-wildcard',
7+
function () {
8+
$validator = new Validator;
9+
10+
$data = [
11+
'by_role' => [
12+
'administrator' => [
13+
'path' => '/home/data/www/wp-dev/wp-content',
14+
'commands' => [
15+
'download', 'cut', 'copy', 'edit', 'rm', 'upload', 'duplicate', 'paste',
16+
'mkfile', 'mkdir', 'rename', 'archive', 'extract'
17+
],
18+
],
19+
'editor' => [
20+
'path' => '/home/data/www/wp-dev/wp-content/uploads',
21+
'commands' => ['download'],
22+
],
23+
'author' => [
24+
'path' => '/home/data/www/wp-dev/wp-content/uploads/file-manager',
25+
],
26+
'contributor' => ['path' => ["/path/to/folder"]],
27+
'subscriber' => [
28+
'path' => '/home/data/www/wp-dev/wp-content/uploads/file-manager',
29+
'commands' => 'cut',
30+
]
31+
],
32+
];
33+
34+
$rules = [
35+
'by_role.*.path' => ['nullable', 'string'],
36+
'by_role.*.commands' => ['nullable', 'array'],
37+
];
38+
39+
$validation = $validator->make($data, $rules);
40+
$errors = $validation->errors();
41+
expect($validation->fails())->toBe(true);
42+
expect($errors)->toBeArray();
43+
expect($errors)->toHaveCount(2);
44+
expect($errors)->toHaveKeys(['by_role.contributor.path', 'by_role.subscriber.commands']);
45+
expect($errors)->toBe(
46+
[
47+
'by_role.contributor.path' => ['The by_role.contributor.path field should be string'],
48+
'by_role.subscriber.commands' => ['The by_role.subscriber.commands must be array'],
49+
]
50+
);
51+
}
52+
);

0 commit comments

Comments
 (0)