Skip to content

Commit a4f7a8f

Browse files
authored
[10.x] Fix attribute name used on Validator instance within certain rule classes (#54943)
Backport PR #54845 to Laravel 10 Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com>
1 parent fc47dca commit a4f7a8f

File tree

3 files changed

+129
-12
lines changed

3 files changed

+129
-12
lines changed

src/Illuminate/Validation/Validator.php

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -302,11 +302,11 @@ class Validator implements ValidatorContract
302302
protected $defaultNumericRules = ['Numeric', 'Integer', 'Decimal'];
303303

304304
/**
305-
* The current placeholder for dots in rule keys.
305+
* The current random hash for the validator.
306306
*
307307
* @var string
308308
*/
309-
protected $dotPlaceholder;
309+
protected static $placeholderHash;
310310

311311
/**
312312
* The exception to throw upon failure.
@@ -335,7 +335,9 @@ class Validator implements ValidatorContract
335335
public function __construct(Translator $translator, array $data, array $rules,
336336
array $messages = [], array $attributes = [])
337337
{
338-
$this->dotPlaceholder = Str::random();
338+
if (! isset(static::$placeholderHash)) {
339+
static::$placeholderHash = Str::random();
340+
}
339341

340342
$this->initialRules = $rules;
341343
$this->translator = $translator;
@@ -363,7 +365,7 @@ public function parseData(array $data)
363365

364366
$key = str_replace(
365367
['.', '*'],
366-
[$this->dotPlaceholder, '__asterisk__'],
368+
['__dot__'.static::$placeholderHash, '__asterisk__'.static::$placeholderHash],
367369
$key
368370
);
369371

@@ -401,7 +403,7 @@ protected function replacePlaceholders($data)
401403
protected function replacePlaceholderInString(string $value)
402404
{
403405
return str_replace(
404-
[$this->dotPlaceholder, '__asterisk__'],
406+
['__dot__'.static::$placeholderHash, '__asterisk__'.static::$placeholderHash],
405407
['.', '*'],
406408
$value
407409
);
@@ -720,7 +722,7 @@ protected function getPrimaryAttribute($attribute)
720722
protected function replaceDotInParameters(array $parameters)
721723
{
722724
return array_map(function ($field) {
723-
return str_replace('\.', $this->dotPlaceholder, $field);
725+
return str_replace('\.', '__dot__'.static::$placeholderHash, $field);
724726
}, $parameters);
725727
}
726728

@@ -846,11 +848,23 @@ protected function hasNotFailedPreviousRuleIfPresenceRule($rule, $attribute)
846848
*/
847849
protected function validateUsingCustomRule($attribute, $value, $rule)
848850
{
849-
$attribute = $this->replacePlaceholderInString($attribute);
851+
$originalAttribute = $this->replacePlaceholderInString($attribute);
852+
853+
$attribute = match (true) {
854+
$rule instanceof Rules\File => $attribute,
855+
$rule instanceof Rules\Password => $attribute,
856+
default => $originalAttribute,
857+
};
850858

851859
$value = is_array($value) ? $this->replacePlaceholders($value) : $value;
852860

853861
if ($rule instanceof ValidatorAwareRule) {
862+
if ($attribute !== $originalAttribute) {
863+
$this->addCustomAttributes([
864+
$attribute => $this->customAttributes[$originalAttribute] ?? $originalAttribute,
865+
]);
866+
}
867+
854868
$rule->setValidator($this);
855869
}
856870

@@ -863,14 +877,14 @@ protected function validateUsingCustomRule($attribute, $value, $rule)
863877
get_class($rule->invokable()) :
864878
get_class($rule);
865879

866-
$this->failedRules[$attribute][$ruleClass] = [];
880+
$this->failedRules[$originalAttribute][$ruleClass] = [];
867881

868-
$messages = $this->getFromLocalArray($attribute, $ruleClass) ?? $rule->message();
882+
$messages = $this->getFromLocalArray($originalAttribute, $ruleClass) ?? $rule->message();
869883

870884
$messages = $messages ? (array) $messages : [$ruleClass];
871885

872886
foreach ($messages as $key => $message) {
873-
$key = is_string($key) ? $key : $attribute;
887+
$key = is_string($key) ? $key : $originalAttribute;
874888

875889
$this->messages->add($key, $this->makeReplacements(
876890
$message, $key, $ruleClass, []
@@ -1159,7 +1173,7 @@ public function getRulesWithoutPlaceholders()
11591173
{
11601174
return collect($this->rules)
11611175
->mapWithKeys(fn ($value, $key) => [
1162-
str_replace($this->dotPlaceholder, '\\.', $key) => $value,
1176+
str_replace('__dot__'.static::$placeholderHash, '\\.', $key) => $value,
11631177
])
11641178
->all();
11651179
}
@@ -1173,7 +1187,7 @@ public function getRulesWithoutPlaceholders()
11731187
public function setRules(array $rules)
11741188
{
11751189
$rules = collect($rules)->mapWithKeys(function ($value, $key) {
1176-
return [str_replace('\.', $this->dotPlaceholder, $key) => $value];
1190+
return [str_replace('\.', '__dot__'.static::$placeholderHash, $key) => $value];
11771191
})->toArray();
11781192

11791193
$this->initialRules = $rules;
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
namespace Illuminate\Tests\Integration\Validation\Rules;
4+
5+
use Illuminate\Http\UploadedFile;
6+
use Illuminate\Support\Facades\Validator;
7+
use Illuminate\Validation\Rules\File;
8+
use Orchestra\Testbench\TestCase;
9+
use PHPUnit\Framework\Attributes\TestWith;
10+
11+
class FileValidationTest extends TestCase
12+
{
13+
#[TestWith(['0'])]
14+
#[TestWith(['.'])]
15+
#[TestWith(['*'])]
16+
#[TestWith(['__asterisk__'])]
17+
public function test_it_can_validate_attribute_as_array(string $attribute)
18+
{
19+
$file = UploadedFile::fake()->create('laravel.png', 1, 'image/png');
20+
21+
$validator = Validator::make([
22+
'files' => [
23+
$attribute => $file,
24+
],
25+
], [
26+
'files.*' => ['required', File::types(['image/png', 'image/jpeg'])],
27+
]);
28+
29+
$this->assertTrue($validator->passes());
30+
}
31+
32+
#[TestWith(['0'])]
33+
#[TestWith(['.'])]
34+
#[TestWith(['*'])]
35+
#[TestWith(['__asterisk__'])]
36+
public function test_it_can_validate_attribute_as_array_when_validation_should_fails(string $attribute)
37+
{
38+
$file = UploadedFile::fake()->create('laravel.php', 1, 'image/php');
39+
40+
$validator = Validator::make([
41+
'files' => [
42+
$attribute => $file,
43+
],
44+
], [
45+
'files.*' => ['required', File::types($mimes = ['image/png', 'image/jpeg'])],
46+
]);
47+
48+
$this->assertFalse($validator->passes());
49+
50+
$this->assertSame([
51+
0 => __('validation.mimetypes', ['attribute' => sprintf('files.%s', str_replace('_', ' ', $attribute)), 'values' => implode(', ', $mimes)]),
52+
], $validator->messages()->all());
53+
}
54+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
namespace Illuminate\Tests\Integration\Validation\Rules;
4+
5+
use Illuminate\Support\Facades\Validator;
6+
use Illuminate\Validation\Rules\Password;
7+
use Orchestra\Testbench\TestCase;
8+
use PHPUnit\Framework\Attributes\TestWith;
9+
10+
class PasswordValidationTest extends TestCase
11+
{
12+
#[TestWith(['0'])]
13+
#[TestWith(['.'])]
14+
#[TestWith(['*'])]
15+
#[TestWith(['__asterisk__'])]
16+
public function test_it_can_validate_attribute_as_array(string $attribute)
17+
{
18+
$validator = Validator::make([
19+
'passwords' => [
20+
$attribute => 'secret',
21+
],
22+
], [
23+
'passwords.*' => ['required', Password::default()->min(6)],
24+
]);
25+
26+
$this->assertTrue($validator->passes());
27+
}
28+
29+
#[TestWith(['0'])]
30+
#[TestWith(['.'])]
31+
#[TestWith(['*'])]
32+
#[TestWith(['__asterisk__'])]
33+
public function test_it_can_validate_attribute_as_array_when_validation_should_fails(string $attribute)
34+
{
35+
$validator = Validator::make([
36+
'passwords' => [
37+
$attribute => 'secret',
38+
],
39+
], [
40+
'passwords.*' => ['required', Password::default()->min(8)],
41+
]);
42+
43+
$this->assertFalse($validator->passes());
44+
45+
$this->assertSame([
46+
0 => sprintf('The passwords.%s field must be at least 8 characters.', str_replace('_', ' ', $attribute)),
47+
], $validator->messages()->all());
48+
}
49+
}

0 commit comments

Comments
 (0)