Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion src/Laravel/Eloquent/Metadata/ModelMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,13 @@ private function getCastType(string $column, Model $model): ?string
/**
* Gets the model casts, including any date casts.
*
* In Laravel 11+, casts can be defined via the protected casts() method
* in addition to the $casts property. Since models may be instantiated
* without calling the constructor (newInstanceWithoutConstructor),
* initializeHasAttributes() is never called and the casts() method
* results are not merged into $casts. We call casts() via reflection
* to ensure both sources are included.
*
* @return array<string, mixed>
*/
private function getCastsWithDates(Model $model): array
Expand All @@ -280,7 +287,15 @@ private function getCastsWithDates(Model $model): array
}
}

return array_merge($dateCasts, $model->getCasts());
$casts = $model->getCasts();

try {
$castsMethod = new \ReflectionMethod($model, 'casts');
$casts = array_merge($casts, $castsMethod->invoke($model));
} catch (\ReflectionException) {
}

return array_merge($dateCasts, $casts);
}

/**
Expand Down
20 changes: 20 additions & 0 deletions src/Laravel/Tests/Eloquent/Metadata/ModelMetadataTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use Orchestra\Testbench\Concerns\WithWorkbench;
use Orchestra\Testbench\TestCase;
use Workbench\App\Models\Book;
use Workbench\App\Models\BookWithMethodCasts;
use Workbench\App\Models\Device;

/**
Expand Down Expand Up @@ -82,6 +83,25 @@ public function secret(): HasMany // @phpstan-ignore-line
$this->assertCount(1, $metadata->getRelations($model));
}

/**
* Casts defined via the casts() method (Laravel 11+) should be detected
* just like those defined via the $casts property.
*
* @see https://github.com/api-platform/core/issues/7662
*/
public function testCastsMethodIsDetected(): void
{
// Use newInstanceWithoutConstructor to replicate how API Platform creates models
$refl = new \ReflectionClass(BookWithMethodCasts::class);
$model = $refl->newInstanceWithoutConstructor();

$metadata = new ModelMetadata();
$attributes = $metadata->getAttributes($model);

$this->assertSame('boolean', $attributes['is_available']['cast']);
$this->assertSame('datetime', $attributes['publication_date']['cast']);
}

/**
* When a model has a custom primary key (e.g. device_id) and a HasMany
* relation whose foreign key on the related table has the same name,
Expand Down
49 changes: 49 additions & 0 deletions src/Laravel/workbench/app/Models/BookWithMethodCasts.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Workbench\App\Models;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Model;

/**
* Model that uses the casts() method instead of the $casts property.
*
* @see https://github.com/api-platform/core/issues/7662
*/
#[ApiResource(
operations: [
new Get(),
new GetCollection(),
],
)]
class BookWithMethodCasts extends Model
{
use HasUlids;

protected $table = 'books';

protected $visible = ['name', 'isbn', 'publication_date', 'is_available'];
protected $fillable = ['name', 'isbn', 'publication_date', 'is_available'];

protected function casts(): array
{
return [
'is_available' => 'boolean',
'publication_date' => 'datetime',
];
}
}
Loading