Skip to content

Commit 2f566bb

Browse files
committed
Enhance error message for array attributes
1 parent aed105e commit 2f566bb

File tree

4 files changed

+121
-6
lines changed

4 files changed

+121
-6
lines changed

src/Attribute.php

Lines changed: 27 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,28 @@ public function getKey()
97101
return $this->key;
98102
}
99103

104+
public function getKeyIndexes()
105+
{
106+
return $this->keyIndexes;
107+
}
108+
100109
public function getHumanizedKey()
101110
{
102-
return $this->humanizedKey;
111+
$primaryAttribute = $this->getPrimaryAttribute();
112+
$key = str_replace('_', ' ', $this->key);
113+
114+
// Resolve key from array validation
115+
if ($primaryAttribute) {
116+
$split = explode('.', $key);
117+
$key = implode(' ', array_map(function($word) {
118+
if (is_numeric($word)) {
119+
$word = $word + 1;
120+
}
121+
return Helper::snakeCase($word, ' ');
122+
}, $split));
123+
}
124+
125+
return ucfirst($key);
103126
}
104127

105128
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/Validation.php

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ protected function parseArrayAttribute(Attribute $attribute)
117117
$attributeKey = $attribute->getKey();
118118
$data = Helper::arrayDot($this->initializeAttributeOnData($attributeKey));
119119

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

122122
$data = array_merge($data, $this->extractValuesForWildcards(
123123
$data, $attributeKey
@@ -126,9 +126,10 @@ protected function parseArrayAttribute(Attribute $attribute)
126126
$attributes = [];
127127

128128
foreach ($data as $key => $value) {
129-
if ((bool) preg_match('/^'.$pattern.'\z/', $key)) {
129+
if ((bool) preg_match('/^'.$pattern.'\z/', $key, $match)) {
130130
$attr = new Attribute($this, $key, null, $attribute->getRules());
131131
$attr->setPrimaryAttribute($attribute);
132+
$attr->setKeyIndexes(array_slice($match, 1));
132133
$attributes[] = $attr;
133134
}
134135
}
@@ -304,6 +305,7 @@ protected function resolveMessage(Attribute $attribute, $value, Rule $validator)
304305
}
305306
}
306307

308+
// Replace message params
307309
$vars = array_merge($params, [
308310
'attribute' => $alias,
309311
'value' => $value,
@@ -314,6 +316,20 @@ protected function resolveMessage(Attribute $attribute, $value, Rule $validator)
314316
$message = str_replace(':'.$key, $value, $message);
315317
}
316318

319+
// Replace key indexes
320+
$keyIndexes = $attribute->getKeyIndexes();
321+
foreach ($keyIndexes as $pathIndex => $index) {
322+
$replacers = [
323+
"[{$pathIndex}]" => $index,
324+
];
325+
326+
if (is_numeric($index)) {
327+
$replacers["{{$pathIndex}}"] = $index + 1;
328+
}
329+
330+
$message = str_replace(array_keys($replacers), array_values($replacers), $message);
331+
}
332+
317333
return $message;
318334
}
319335

tests/ValidatorTest.php

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,4 +749,63 @@ 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+
752811
}

0 commit comments

Comments
 (0)