Skip to content

getFromLocalArray returns array instead of null when wildcard custom message key matches but specific rule is absent, causing TypeError in v12.55.1 #59316

@ignium559

Description

@ignium559

Laravel Version

12.55.1

PHP Version

8.3.23

Database Driver & Version

N/A

Description

When a custom validation message is defined for a wildcard attribute key (e.g. students.*.grade) using a nested array of rule-specific messages, and a failing rule is not in that nested array, getFromLocalArray returns the entire array instead of null. This causes a TypeError crash in replaceInputPlaceholder, which was introduced in v12.55.1.

<?php
if (preg_match('#^'.$pattern.'\z#u', $key) === 1) {
    $message = $source[$sourceKey];

    if (is_array($message) && isset($message[$lowerRule])) {
        return $message[$lowerRule];
    }

    return $message; // BUG: returns the array when $lowerRule is not in $message
}

When $message is an array and $message[$lowerRule] is not set, return $message returns the whole array. This is then passed to replaceInputPlaceholder in v12.55.1, which now calls str_contains($message, ':input') — crashing with: TypeError: str_contains(): Argument #1 ($haystack) must be of type string, array given

This was silent in v12.54.1 because replaceInputPlaceholder did not have the str_contains guard and would just skip the replacement harmlessly.

Steps To Reproduce

  1. Define a custom message for only one rule on a wildcard attribute key:
<?php
// lang/en/validation.php  (or returned from FormRequest::messages())
'custom' => [
    'students.*.grade' => [
        'required' => 'Custom required message.',
        // 'in' is intentionally absent
    ],
],
  1. Submit a value that fails the in rule for students.*.grade:
<?php
'students.*.grade' => ['required', Rule::in(['1','2','3','4','5'])],
  1. Observe a 500 TypeError instaed of the expected 422 validation error.

###Expected Behavior
When a wildcard key matches but does not contain the specific failing rule, getFromLocalArray should return null so Laravel falls back to the default validation message for that rule.

###Suggested Fix
In getFromLocalArray, change the wildcard fallthrough to return null when the message is an array but doesn't contain the rule:

<?php
if (preg_match('#^'.$pattern.'\z#u', $key) === 1) {
    $message = $source[$sourceKey];

    if (is_array($message)) {
        return $message[$lowerRule] ?? null; // return null instead of the whole array
    }

    return $message;
}

This makes the wildcard branch consistent with the exact-match branch directly below it, which already does return $message[$lowerRule] ?? null.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions