Skip to content

Commit 06d8482

Browse files
authored
Merge pull request #33 from rakit/add-support-to-get-sibling-value
Add support to get sibling value
2 parents aed105e + 3289d7d commit 06d8482

File tree

7 files changed

+251
-11
lines changed

7 files changed

+251
-11
lines changed

src/Attribute.php

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ class Attribute
99

1010
protected $key;
1111

12-
protected $humanizedKey;
13-
1412
protected $alias;
1513

1614
protected $validation;
@@ -21,12 +19,13 @@ class Attribute
2119

2220
protected $otherAttributes = [];
2321

22+
protected $keyIndexes = [];
23+
2424
public function __construct(Validation $validation, $key, $alias = null, array $rules = array())
2525
{
2626
$this->validation = $validation;
2727
$this->alias = $alias;
2828
$this->key = $key;
29-
$this->humanizedKey = ucfirst(str_replace('_', ' ', $key));
3029
foreach($rules as $rule) {
3130
$this->addRule($rule);
3231
}
@@ -37,6 +36,11 @@ public function setPrimaryAttribute(Attribute $primaryAttribute)
3736
$this->primaryAttribute = $primaryAttribute;
3837
}
3938

39+
public function setKeyIndexes(array $keyIndexes)
40+
{
41+
$this->keyIndexes = $keyIndexes;
42+
}
43+
4044
public function getPrimaryAttribute()
4145
{
4246
return $this->primaryAttribute;
@@ -97,9 +101,58 @@ public function getKey()
97101
return $this->key;
98102
}
99103

104+
public function getKeyIndexes()
105+
{
106+
return $this->keyIndexes;
107+
}
108+
109+
public function getValue($key = null)
110+
{
111+
if ($key && $this->isArrayAttribute()) {
112+
$key = $this->resolveSiblingKey($key);
113+
}
114+
115+
if (!$key) {
116+
$key = $this->getKey();
117+
}
118+
119+
return $this->validation->getValue($key);
120+
}
121+
122+
public function isArrayAttribute()
123+
{
124+
return count($this->getKeyIndexes()) > 0;
125+
}
126+
127+
public function resolveSiblingKey($key)
128+
{
129+
$indexes = $this->getKeyIndexes();
130+
$keys = explode("*", $key);
131+
$countAsterisks = count($keys) - 1;
132+
if (count($indexes) < $countAsterisks) {
133+
$indexes = array_merge($indexes, array_fill(0, $countAsterisks - count($indexes), "*"));
134+
}
135+
$args = array_merge([str_replace("*", "%s", $key)], $indexes);
136+
return call_user_func_array('sprintf', $args);
137+
}
138+
100139
public function getHumanizedKey()
101140
{
102-
return $this->humanizedKey;
141+
$primaryAttribute = $this->getPrimaryAttribute();
142+
$key = str_replace('_', ' ', $this->key);
143+
144+
// Resolve key from array validation
145+
if ($primaryAttribute) {
146+
$split = explode('.', $key);
147+
$key = implode(' ', array_map(function($word) {
148+
if (is_numeric($word)) {
149+
$word = $word + 1;
150+
}
151+
return Helper::snakeCase($word, ' ');
152+
}, $split));
153+
}
154+
155+
return ucfirst($key);
103156
}
104157

105158
public function setAlias($alias)

src/Helper.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,4 +158,21 @@ public static function arraySet(&$target, $key, $value, $overwrite = true)
158158
return $target;
159159
}
160160

161+
/**
162+
* Get snake_case format from given string
163+
*
164+
* @param string $value
165+
* @param string $delimiter
166+
* @return string
167+
*/
168+
public static function snakeCase($value, $delimiter = '_')
169+
{
170+
if (! ctype_lower($value)) {
171+
$value = preg_replace('/\s+/u', '', ucwords($value));
172+
$value = strtolower(preg_replace('/(.)(?=[A-Z])/u', '$1'.$delimiter, $value));
173+
}
174+
175+
return $value;
176+
}
177+
161178
}

src/Rules/RequiredIf.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public function check($value)
2323

2424
$anotherAttribute = $this->parameter('field');
2525
$definedValues = $this->parameter('values');
26-
$anotherValue = $this->validation->getValue($anotherAttribute);
26+
$anotherValue = $this->getAttribute()->getValue($anotherAttribute);
2727

2828
$validator = $this->validation->getValidator();
2929
$required_validator = $validator('required');

src/Rules/RequiredUnless.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public function check($value)
2323

2424
$anotherAttribute = $this->parameter('field');
2525
$definedValues = $this->parameter('values');
26-
$anotherValue = $this->validation->getValue($anotherAttribute);
26+
$anotherValue = $this->getAttribute()->getValue($anotherAttribute);
2727

2828
$validator = $this->validation->getValidator();
2929
$required_validator = $validator('required');

src/Rules/Same.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public function check($value)
1616
$this->requireParameters($this->fillable_params);
1717

1818
$field = $this->parameter('field');
19-
$anotherValue = $this->validation->getValue($field);
19+
$anotherValue = $this->getAttribute()->getValue($field);
2020

2121
return $value == $anotherValue;
2222
}

src/Validation.php

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,22 +73,25 @@ protected function validateAttribute(Attribute $attribute)
7373

7474
$attributeKey = $attribute->getKey();
7575
$rules = $attribute->getRules();
76+
7677
$value = $this->getValue($attributeKey);
7778
$isEmptyValue = $this->isEmptyValue($value);
7879

7980
$isValid = true;
8081
foreach($rules as $ruleValidator) {
82+
$ruleValidator->setAttribute($attribute);
83+
8184
if ($isEmptyValue && $ruleValidator instanceof Defaults) {
8285
$value = $ruleValidator->check(null);
8386
$isEmptyValue = $this->isEmptyValue($value);
8487
continue;
8588
}
8689

90+
$valid = $ruleValidator->check($value);
91+
8792
if ($isEmptyValue AND $this->ruleIsOptional($attribute, $ruleValidator)) {
8893
continue;
8994
}
90-
91-
$valid = $ruleValidator->check($value);
9295

9396
if (!$valid) {
9497
$isValid = false;
@@ -117,7 +120,7 @@ protected function parseArrayAttribute(Attribute $attribute)
117120
$attributeKey = $attribute->getKey();
118121
$data = Helper::arrayDot($this->initializeAttributeOnData($attributeKey));
119122

120-
$pattern = str_replace('\*', '[^\.]+', preg_quote($attributeKey));
123+
$pattern = str_replace('\*', '([^\.]+)', preg_quote($attributeKey));
121124

122125
$data = array_merge($data, $this->extractValuesForWildcards(
123126
$data, $attributeKey
@@ -126,9 +129,10 @@ protected function parseArrayAttribute(Attribute $attribute)
126129
$attributes = [];
127130

128131
foreach ($data as $key => $value) {
129-
if ((bool) preg_match('/^'.$pattern.'\z/', $key)) {
132+
if ((bool) preg_match('/^'.$pattern.'\z/', $key, $match)) {
130133
$attr = new Attribute($this, $key, null, $attribute->getRules());
131134
$attr->setPrimaryAttribute($attribute);
135+
$attr->setKeyIndexes(array_slice($match, 1));
132136
$attributes[] = $attr;
133137
}
134138
}
@@ -304,6 +308,7 @@ protected function resolveMessage(Attribute $attribute, $value, Rule $validator)
304308
}
305309
}
306310

311+
// Replace message params
307312
$vars = array_merge($params, [
308313
'attribute' => $alias,
309314
'value' => $value,
@@ -314,6 +319,20 @@ protected function resolveMessage(Attribute $attribute, $value, Rule $validator)
314319
$message = str_replace(':'.$key, $value, $message);
315320
}
316321

322+
// Replace key indexes
323+
$keyIndexes = $attribute->getKeyIndexes();
324+
foreach ($keyIndexes as $pathIndex => $index) {
325+
$replacers = [
326+
"[{$pathIndex}]" => $index,
327+
];
328+
329+
if (is_numeric($index)) {
330+
$replacers["{{$pathIndex}}"] = $index + 1;
331+
}
332+
333+
$message = str_replace(array_keys($replacers), array_values($replacers), $message);
334+
}
335+
317336
return $message;
318337
}
319338

tests/ValidatorTest.php

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,4 +749,155 @@ public function testUsingDefaults()
749749
'is_published' => 'invalid-value',
750750
]);
751751
}
752+
753+
public function testHumanizedKeyInArrayValidation()
754+
{
755+
$validation = $this->validator->validate([
756+
'cart' => [
757+
[
758+
'qty' => 'xyz',
759+
],
760+
]
761+
], [
762+
'cart.*.itemName' => 'required',
763+
'cart.*.qty' => 'required|numeric'
764+
]);
765+
766+
$errors = $validation->errors();
767+
768+
$this->assertEquals($errors->first('cart.*.qty'), 'The Cart 1 qty must be numeric');
769+
$this->assertEquals($errors->first('cart.*.itemName'), 'The Cart 1 item name is required');
770+
}
771+
772+
public function testCustomMessageInArrayValidation()
773+
{
774+
$validation = $this->validator->make([
775+
'cart' => [
776+
[
777+
'qty' => 'xyz',
778+
'itemName' => 'Lorem ipsum'
779+
],
780+
[
781+
'qty' => 10,
782+
'attributes' => [
783+
[
784+
'name' => 'color',
785+
'value' => null
786+
]
787+
]
788+
],
789+
]
790+
], [
791+
'cart.*.itemName' => 'required',
792+
'cart.*.qty' => 'required|numeric',
793+
'cart.*.attributes.*.value' => 'required'
794+
]);
795+
796+
$validation->setMessages([
797+
'cart.*.itemName:required' => 'Item [0] name is required',
798+
'cart.*.qty:numeric' => 'Item {0} qty is not a number',
799+
'cart.*.attributes.*.value' => 'Item {0} attribute {1} value is required',
800+
]);
801+
802+
$validation->validate();
803+
804+
$errors = $validation->errors();
805+
806+
$this->assertEquals($errors->first('cart.*.qty'), 'Item 1 qty is not a number');
807+
$this->assertEquals($errors->first('cart.*.itemName'), 'Item 1 name is required');
808+
$this->assertEquals($errors->first('cart.*.attributes.*.value'), 'Item 2 attribute 1 value is required');
809+
}
810+
811+
public function testRequiredIfOnArrayAttribute()
812+
{
813+
$validation = $this->validator->validate([
814+
'products' => [
815+
// invalid because has_notes is not empty
816+
'10' => [
817+
'quantity' => 8,
818+
'has_notes' => 1,
819+
'notes' => ''
820+
],
821+
// valid because has_notes is null
822+
'12' => [
823+
'quantity' => 0,
824+
'has_notes' => null,
825+
'notes' => ''
826+
],
827+
// valid because no has_notes
828+
'14' => [
829+
'quantity' => 0,
830+
'notes' => ''
831+
],
832+
]
833+
], [
834+
'products.*.notes' => 'required_if:products.*.has_notes,1',
835+
]);
836+
837+
$this->assertFalse($validation->passes());
838+
839+
$errors = $validation->errors();
840+
$this->assertNotNull($errors->first('products.10.notes'));
841+
$this->assertNull($errors->first('products.12.notes'));
842+
$this->assertNull($errors->first('products.14.notes'));
843+
}
844+
845+
public function testRequiredUnlessOnArrayAttribute()
846+
{
847+
$validation = $this->validator->validate([
848+
'products' => [
849+
// valid because has_notes is 1
850+
'10' => [
851+
'quantity' => 8,
852+
'has_notes' => 1,
853+
'notes' => ''
854+
],
855+
// invalid because has_notes is not 1
856+
'12' => [
857+
'quantity' => 0,
858+
'has_notes' => null,
859+
'notes' => ''
860+
],
861+
// invalid because no has_notes
862+
'14' => [
863+
'quantity' => 0,
864+
'notes' => ''
865+
],
866+
]
867+
], [
868+
'products.*.notes' => 'required_unless:products.*.has_notes,1',
869+
]);
870+
871+
$this->assertFalse($validation->passes());
872+
873+
$errors = $validation->errors();
874+
$this->assertNull($errors->first('products.10.notes'));
875+
$this->assertNotNull($errors->first('products.12.notes'));
876+
$this->assertNotNull($errors->first('products.14.notes'));
877+
}
878+
879+
public function testSameRuleOnArrayAttribute()
880+
{
881+
$validation = $this->validator->validate([
882+
'users' => [
883+
[
884+
'password' => 'foo',
885+
'password_confirmation' => 'foo'
886+
],
887+
[
888+
'password' => 'foo',
889+
'password_confirmation' => 'bar'
890+
],
891+
]
892+
], [
893+
'users.*.password_confirmation' => 'required|same:users.*.password',
894+
]);
895+
896+
$this->assertFalse($validation->passes());
897+
898+
$errors = $validation->errors();
899+
$this->assertNull($errors->first('users.0.password_confirmation:same'));
900+
$this->assertNotNull($errors->first('users.1.password_confirmation:same'));
901+
}
902+
752903
}

0 commit comments

Comments
 (0)