livewire/livewire: Validation no longer crashes when rules(), messages() or validationAttributes() return a Collection
Contributed by joshhanley
Part of the livewire v4.4.7 release
Pull request #10303 fixes a small but sharp edge in Livewire's validation handling: when a component's rules(), messages(), or validationAttributes() returned a Laravel Collection instead of a plain array, calling $this->validate() threw a TypeError and the Livewire update endpoint responded with a 500. The patch normalises any Arrayable value to an array before merging, bringing Livewire in line with how Laravel's own validation layer treats collections. If you're upgrading from v3.8.10, there's nothing to change in your application code: components that already return arrays behave identically, and components that return collections now simply work.
The bug in practice
On builds prior to this fix, the following component — reproduced from issue #10301 — would fail the moment you clicked Save:
class PromptForm extends Component
{
public string $prompt = '';
protected function rules()
{
return collect(['prompt' => 'required']);
}
protected function messages()
{
// Built with a collection pipeline — ends up a Collection, not an array.
return collect(['prompt.required' => 'A prompt is required.'])
->mapWithKeys(fn ($message, $key) => [$key => $message]);
}
public function save(): void
{
$this->validate();
}
}
Nothing here looks wrong. Building messages with collect()->mapWithKeys() is idiomatic Laravel. But because the final return value is a Collection rather than an array, Livewire's internals choke on it before any validation runs:
TypeError: array_merge(): Argument #1 ($array) must be of type array,
Illuminate\Support\Collection given
The user sees a failed request and a 500 from the update endpoint; no validation message ever reaches the component. The same failure occurs whether the Collection comes back from rules(), messages(), or validationAttributes(), and whether those are defined as methods or as component properties.
Why a Collection caused a TypeError
Inside HandlesValidation, the three getters read whatever the component supplies and pass it straight into PHP's array functions:
if (method_exists($this, 'messages')) $messages = $this->messages();
elseif (property_exists($this, 'messages')) $messages = $this->messages;
// ...
return array_merge($messages, $messagesFromOutside);
array_merge() requires its first argument to be an actual array. A Collection — no matter how array-like it feels — doesn't satisfy that contract, so PHP throws before validation even begins. The same flaw existed in getRules() (which uses array_merge_recursive()) and in getValidationAttributes(), meaning all three entry points into component-level validation were affected.
What the fix does
The patch adds one normalisation step to each of the three getters, immediately after the value is resolved from the method or property:
protected function getMessages()
{
if (method_exists($this, 'messages')) $messages = $this->messages();
elseif (property_exists($this, 'messages')) $messages = $this->messages;
if ($messages instanceof Arrayable) $messages = $messages->toArray();
// ... merge with messages supplied from outside the component ...
return array_merge($messages, $messagesFromOutside);
}
Because the check is against Illuminate\Contracts\Support\Arrayable rather than Collection specifically, anything else that can present itself as an array — API resources, your own value objects implementing the contract — is converted too. Components returning plain arrays are untouched; the conversion only runs when it's needed.
This also matches conventions in the framework itself: Laravel's Rule::in() and Rule::notIn() perform the identical if ($values instanceof Arrayable) $values = $values->toArray(); normalisation before use. Livewire's validation API now accepts the same range of inputs the rest of the ecosystem does. The change is covered by three new tests asserting that rules(), messages(), and validationAttributes() may each return a collection and that $this->validate() still reports the expected errors.
Upgrade impact
For anyone upgrading from v3.8.10, this is a pure bug fix with no migration steps:
- No public API, configuration, or behaviour contract changed.
- Components returning arrays from these methods are completely unaffected.
- Components returning collections (or other
Arrayablevalues) no longer crash. The following now works out of the box:
protected function validationAttributes()
{
return collect(['prompt' => 'prompt field']);
}
If you had previously refactored working code back into array syntax specifically to dodge the TypeError, you're welcome to keep it that way — but the collection-pipeline version is equally safe now. Either way, the resolved arrays are merged with rules, messages, and attributes supplied from outside the component, exactly as before.
Takeaways
rules(),messages(), andvalidationAttributes()may now return aCollectionor anyArrayablevalue; Livewire converts it to an array internally before merging.- Fixes the
TypeErrorand 500 response from the update endpoint when validation ran on a component returning collections. - No action is required when upgrading from v3.8.10: array-returning components behave exactly as before, with no config or API changes.
- The behaviour now mirrors Laravel core (
Rule::in()/Rule::notIn()), keeping validation input handling consistent across the ecosystem.