Illuminate\Support\MessageBag::add() silently discards distinct numeric-looking string messages because its duplicate detection uses PHP's loose equality comparison.
For example, PHP evaluates '0' == '00' as true, although these are distinct strings.
The issue also creates an inconsistency between initializing a MessageBag through its constructor and adding the same messages individually.
The constructor uses array_unique(), which compares values as strings by default, while the add() method delegates to isUnique(), which currently uses in_array() without strict comparison.
This contradicts the consistency previously addressed in #23485.
Steps To Reproduce
use Illuminate\Support\MessageBag;
// Initialize through the constructor.
$fromConstructor = new MessageBag([
'code' => ['0', '00'],
]);
// Initialize through the add() method.
$fromAdd = new MessageBag;
$fromAdd->add('code', '0');
$fromAdd->add('code', '00');
var_export($fromConstructor->getMessages());
// ['code' => ['0', '00']]
var_export($fromAdd->getMessages());
// ['code' => ['0']]
Expected Behavior
Both approaches should preserve the same distinct string messages:
[
'code' => ['0', '00'],
]
Repeated identical messages should continue to be deduplicated.
Actual Behavior
The add() method silently drops '00', treating it as a duplicate of '0'.
This may result in lost messages when an application stores validation errors or other messages containing distinct numeric-looking strings.
This aligns the behavior of add() with the string-based uniqueness behavior already used by the constructor.
Proposed Regression Test
File:tests/Support/SupportMessageBagTest.php
public function testDistinctNumericStringMessagesArePreserved()
{
$bag = new MessageBag;
$bag->add('code', '0');
$bag->add('code', '00');
$bag->add('code', '0');
$this->assertSame(
['0', '00'],
$bag->getMessages()['code']
);
$this->assertSame(
(new MessageBag([
'code' => ['0', '00'],
]))->getMessages(),
$bag->getMessages()
);
}
The regression test verifies that:
Distinct numeric-looking strings are preserved.
Identical messages are still deduplicated.
Constructor initialization and incremental addition produce equivalent results.
Impact and Backward Compatibility
The proposed change is limited to duplicate detection in MessageBag::add().
No public API changes are required.
Ordinary nonnumeric messages retain their current behavior.
Identical string messages continue to be deduplicated.
Distinct numeric-looking strings are no longer incorrectly discarded.
The existing constructor behavior remains unchanged.
Applications that unintentionally relied on loose equality for message deduplication may observe additional messages being retained. However, preserving distinct string messages is consistent with the documented string-oriented behavior of MessageBag.
Validation
The issue was reproduced in an isolated PHP 8.4.24 test using the relevant upstream method logic.
The isolated reproduction demonstrated the difference between loose and strict comparison and confirmed the expected behavior after applying the proposed change.
The complete Laravel PHPUnit suite has not yet been run against this patch.
Related Work
#23485: Make MessageBag constructor behavior consistent with add() (merged).
The proposed fix addresses an additional edge case not covered by the existing uniqueness regression tests.
Steps To Reproduce
Steps To Reproduce
Install Laravel 13.35.0 in a PHP 8.4 environment.
Open Laravel Tinker using php artisan tinker.
Execute the following code:
use Illuminate\Support\MessageBag;
// Create a MessageBag using the constructor.
$fromConstructor = new MessageBag([
'code' => ['0', '00'],
]);
// Create another MessageBag using add().
$fromAdd = new MessageBag;
$fromAdd->add('code', '0');
$fromAdd->add('code', '00');
// Compare both results.
dump($fromConstructor->getMessages());
dump($fromAdd->getMessages());
Actual Result
The constructor preserves both values:
[
'code' => ['0', '00'],
]
However, add() silently discards the second value:
[
'code' => ['0'],
]
Expected Result
Both methods should produce identical results:
[
'code' => ['0', '00'],
]
The strings '0' and '00' are distinct and should not be treated as duplicates.
The third addition should be ignored because it is an exact duplicate, while the second should be preserved because it is a distinct string.
Note: This behavior was verified using an isolated reproduction of the upstream method logic. The complete Laravel 13.35.0 application reproduction and PHPUnit suite have not yet been executed.