Repository: codeigniter4/CodeIgniter4 (PHP)

Issue:
Bug: Custom validation error for a field with an asterisk.
### CodeIgniter4 Version

dev

### What happened?

If you specify a custom error message for a field with an asterisk, the default error text will be used.
This is due to the fact that a field without an asterisk is passed to generate an error (it contains the full path of the key)


### Steps to Reproduce

```php
$data = [
    'user' => [
        ['name' => null],
        ['name' => null],
    ]
];

$validator = Services::validation();

$validator->setRules(['user.*.name' => 'required'], ['user.*.name' => ['required' => 'Required']]);

$validator->run($data);

dd($validator->getErrors());
// array (2) [
//     'user.0.name' => 'The user.*.name field is required.'
//     'user.1.name' => 'The user.*.name field is required.'
// ]
```

### Expected Output

```php
array (2) [
    'user.0.name' => 'Required'
    'user.1.name' => 'Required'
]
```


Discussion:
I sent a PR, review please. #6340



Patch:
diff --git a/system/Validation/Validation.php b/system/Validation/Validation.php
index 7e4a55db55cf..ed370a2f5c08 100644
--- a/system/Validation/Validation.php
+++ b/system/Validation/Validation.php
@@ -168,7 +168,7 @@ public function run(?array $data = null, ?string $group = null, ?string $dbGroup
             if (strpos($field, '*') !== false) {
                 // Process multiple fields
                 foreach ($values as $dotField => $value) {
-                    $this->processRules($dotField, $setup['label'] ?? $field, $value, $rules, $data);
+                    $this->processRules($dotField, $setup['label'] ?? $field, $value, $rules, $data, $field);
                 }
             } else {
                 // Process single field
@@ -201,10 +201,17 @@ public function check($value, string $rule, array $errors = []): bool
      *
      * @param array|string $value
      * @param array|null   $rules
-     * @param array        $data
+     * @param array        $data          The array of data to validate, with `DBGroup`.
+     * @param string|null  $originalField The original asterisk field name like "foo.*.bar".
      */
-    protected function processRules(string $field, ?string $label, $value, $rules = null, ?array $data = null): bool
-    {
+    protected function processRules(
+        string $field,
+        ?string $label,
+        $value,
+        $rules = null,
+        ?array $data = null,
+        ?string $originalField = null
+    ): bool {
         if ($data === null) {
             throw new InvalidArgumentException('You must supply the parameter: data.');
         }
@@ -333,7 +340,8 @@ protected function processRules(string $field, ?string $label, $value, $rules =
                     $field,
                     $label,
                     $param,
-                    (string) $value
+                    (string) $value,
+                    $originalField
                 );
 
                 return false;
@@ -706,13 +714,21 @@ public function setError(string $field, string $error): ValidationInterface
      *
      * @param string|null $value The value that caused the validation to fail.
      */
-    protected function getErrorMessage(string $rule, string $field, ?string $label = null, ?string $param = null, ?string $value = null): string
-    {
+    protected function getErrorMessage(
+        string $rule,
+        string $field,
+        ?string $label = null,
+        ?string $param = null,
+        ?string $value = null,
+        ?string $originalField = null
+    ): string {
         $param ??= '';
 
         // Check if custom message has been defined by user
         if (isset($this->customErrors[$field][$rule])) {
             $message = lang($this->customErrors[$field][$rule]);
+        } elseif (null !== $originalField && isset($this->customErrors[$originalField][$rule])) {
+            $message = lang($this->customErrors[$originalField][$rule]);
         } else {
             // Try to grab a localized version of the message...
             // lang() will return the rule name back if not found,
diff --git a/user_guide_src/source/changelogs/v4.2.5.rst b/user_guide_src/source/changelogs/v4.2.5.rst
index a670705e4153..eb9c8f7ec643 100644
--- a/user_guide_src/source/changelogs/v4.2.5.rst
+++ b/user_guide_src/source/changelogs/v4.2.5.rst
@@ -14,6 +14,7 @@ BREAKING
 
 - The method signature of ``BaseConnection::tableExists()`` has been changed. A second optional parameter ``$cached`` was added. This directs whether to use cache data or not. Default is ``true``, use cache data.
 - The abstract method signature of ``BaseBuilder::_listTables()`` has been changed. A second optional parameter ``$tableName`` was added. Providing a table name will generate SQL listing only that table.
+- The method signature of ``Validation::processRules()`` and ``Validation::getErrorMessage()`` have been changed. Both of these methods add new ``$originalField`` parameter.
 
 Enhancements
 ************


Test patch:
diff --git a/tests/system/Validation/ValidationTest.php b/tests/system/Validation/ValidationTest.php
index c6339139044d..5a48615b6c05 100644
--- a/tests/system/Validation/ValidationTest.php
+++ b/tests/system/Validation/ValidationTest.php
@@ -463,6 +463,28 @@ public function testRunGroupWithCustomErrorMessage(): void
         ], $this->validation->getErrors());
     }
 
+    /**
+     * @see https://github.com/codeigniter4/CodeIgniter4/issues/6245
+     */
+    public function testRunWithCustomErrorsAndAsteriskField(): void
+    {
+        $data = [
+            'foo' => [
+                ['bar' => null],
+                ['bar' => null],
+            ],
+        ];
+        $this->validation->setRules(
+            ['foo.*.bar' => ['label' => 'foo bar', 'rules' => 'required']],
+            ['foo.*.bar' => ['required' => 'Required']]
+        );
+        $this->validation->run($data);
+        $this->assertSame([
+            'foo.0.bar' => 'Required',
+            'foo.1.bar' => 'Required',
+        ], $this->validation->getErrors());
+    }
+
     /**
      * @dataProvider rulesSetupProvider
      *