Skip to content

Commit 47e6d0f

Browse files
[12.x] Ensures casts objects can be transformed into strings (#56687)
* [12.x] Adds Cast as class instances * Modifies exception message * Add tests * Style changes [skip ci] * Removes CastAttributes magic as being unreliable * Fixes test exception message. * Update HasAttributes.php * Update DatabaseEloquentModelTest.php --------- Co-authored-by: Taylor Otwell <[email protected]>
1 parent f24fb47 commit 47e6d0f

File tree

2 files changed

+53
-0
lines changed

2 files changed

+53
-0
lines changed

src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use DateTimeImmutable;
1212
use DateTimeInterface;
1313
use Illuminate\Contracts\Database\Eloquent\Castable;
14+
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
1415
use Illuminate\Contracts\Database\Eloquent\CastsInboundAttributes;
1516
use Illuminate\Contracts\Support\Arrayable;
1617
use Illuminate\Database\Eloquent\Casts\AsArrayObject;
@@ -41,6 +42,7 @@
4142
use ReflectionMethod;
4243
use ReflectionNamedType;
4344
use RuntimeException;
45+
use Stringable;
4446
use ValueError;
4547

4648
use function Illuminate\Support\enum_value;
@@ -790,6 +792,13 @@ protected function ensureCastsAreStringValues($casts)
790792
{
791793
foreach ($casts as $attribute => $cast) {
792794
$casts[$attribute] = match (true) {
795+
is_object($cast) => value(function () use ($cast, $attribute) {
796+
return $cast instanceof Stringable
797+
? (string) $cast
798+
: throw new InvalidArgumentException(
799+
"The cast object for the {$attribute} attribute must implement Stringable."
800+
);
801+
}),
793802
is_array($cast) => value(function () use ($cast) {
794803
if (count($cast) === 1) {
795804
return $cast[0];

tests/Database/DatabaseEloquentModelTest.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Illuminate\Tests\Database;
44

5+
use Closure;
56
use DateTime;
67
use DateTimeImmutable;
78
use DateTimeInterface;
@@ -59,6 +60,7 @@
5960
use PHPUnit\Framework\TestCase;
6061
use ReflectionClass;
6162
use stdClass;
63+
use Stringable as NativeStringable;
6264

6365
include_once 'Enums.php';
6466

@@ -3350,6 +3352,37 @@ public function testCastOnArrayFormatWithOneElement()
33503352
$this->assertEquals(['bar' => 'foo'], $model->getAttribute('singleElementInArrayAttribute')->toArray());
33513353
}
33523354

3355+
public function testUsingStringableObjectCastUsesStringRepresentation()
3356+
{
3357+
$model = new EloquentModelCastingStub;
3358+
3359+
$this->assertEquals('int', $model->getCasts()['castStringableObject']);
3360+
}
3361+
3362+
public function testMergeingStringableObjectCastUSesStringRepresentation()
3363+
{
3364+
$stringable = new StringableCastBuilder();
3365+
$stringable->cast = 'test';
3366+
3367+
$model = (new EloquentModelCastingStub)->mergeCasts([
3368+
'something' => $stringable
3369+
]);
3370+
3371+
$this->assertEquals('test', $model->getCasts()['something']);
3372+
}
3373+
3374+
public function testUsingPlainObjectAsCastThrowsException()
3375+
{
3376+
$model = new EloquentModelCastingStub;
3377+
3378+
$this->expectException(InvalidArgumentException::class);
3379+
$this->expectExceptionMessage('The cast object for the something attribute must implement Stringable.');
3380+
3381+
$model->mergeCasts([
3382+
'something' => (object) [],
3383+
]);
3384+
}
3385+
33533386
public function testUnsavedModel()
33543387
{
33553388
$user = new UnsavedModel;
@@ -3918,6 +3951,7 @@ protected function casts(): array
39183951
'singleElementInArrayAttribute' => [AsCollection::class],
39193952
'duplicatedAttribute' => 'int',
39203953
'asToObjectCast' => TestCast::class,
3954+
'castStringableObject' => new StringableCastBuilder(),
39213955
];
39223956
}
39233957

@@ -4384,3 +4418,13 @@ class EloquentChildModelBootingCallbackTestStub extends EloquentModelBootingCall
43844418
{
43854419
public static bool $bootHasFinished = false;
43864420
}
4421+
4422+
class StringableCastBuilder implements NativeStringable
4423+
{
4424+
public $cast = 'int';
4425+
4426+
public function __toString()
4427+
{
4428+
return $this->cast;
4429+
}
4430+
}

0 commit comments

Comments
 (0)