Skip to content

Commit 1298536

Browse files
soyukagd6tm
andauthoredMar 14, 2025··
feat(laravel): stateOptions modelClass for eloquent (#7020)
refactor(laravel): using trait to handle modelClass option test(laravel): add test to validate the modelClass option Co-authored-by: Guillaume Durand <guillaume.durand@6tm.com>
1 parent 1e7076c commit 1298536

File tree

7 files changed

+120
-4
lines changed

7 files changed

+120
-4
lines changed
 

‎src/Laravel/Eloquent/State/CollectionProvider.php

+8-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use ApiPlatform\Laravel\Eloquent\Extension\QueryExtensionInterface;
1717
use ApiPlatform\Laravel\Eloquent\Paginator;
1818
use ApiPlatform\Laravel\Eloquent\PartialPaginator;
19+
use ApiPlatform\Metadata\Exception\RuntimeException;
1920
use ApiPlatform\Metadata\Operation;
2021
use ApiPlatform\State\Pagination\Pagination;
2122
use ApiPlatform\State\ProviderInterface;
@@ -29,6 +30,7 @@
2930
final class CollectionProvider implements ProviderInterface
3031
{
3132
use LinksHandlerLocatorTrait;
33+
use ModelClassTrait;
3234

3335
/**
3436
* @param LinksHandlerInterface<Model> $linksHandler
@@ -45,8 +47,12 @@ public function __construct(
4547

4648
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
4749
{
48-
/** @var Model $model */
49-
$model = new ($operation->getClass())();
50+
$resourceClass = $this->getModelClass($operation);
51+
$model = new $resourceClass();
52+
53+
if (!$model instanceof Model) {
54+
throw new RuntimeException(\sprintf('The class "%s" is not an Eloquent model.', $resourceClass));
55+
}
5056

5157
if ($handleLinks = $this->getLinksHandler($operation)) {
5258
$query = $handleLinks($model->query(), $uriVariables, ['operation' => $operation, 'modelClass' => $operation->getClass()] + $context);

‎src/Laravel/Eloquent/State/ItemProvider.php

+8-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace ApiPlatform\Laravel\Eloquent\State;
1515

1616
use ApiPlatform\Laravel\Eloquent\Extension\QueryExtensionInterface;
17+
use ApiPlatform\Metadata\Exception\RuntimeException;
1718
use ApiPlatform\Metadata\Operation;
1819
use ApiPlatform\State\ProviderInterface;
1920
use Illuminate\Database\Eloquent\Model;
@@ -25,6 +26,7 @@
2526
final class ItemProvider implements ProviderInterface
2627
{
2728
use LinksHandlerLocatorTrait;
29+
use ModelClassTrait;
2830

2931
/**
3032
* @param LinksHandlerInterface<Model> $linksHandler
@@ -40,7 +42,12 @@ public function __construct(
4042

4143
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
4244
{
43-
$model = new ($operation->getClass())();
45+
$resourceClass = $this->getModelClass($operation);
46+
$model = new $resourceClass();
47+
48+
if (!$model instanceof Model) {
49+
throw new RuntimeException(\sprintf('The class "%s" is not an Eloquent model.', $resourceClass));
50+
}
4451

4552
if ($handleLinks = $this->getLinksHandler($operation)) {
4653
$query = $handleLinks($model->query(), $uriVariables, ['operation' => $operation] + $context);

‎src/Laravel/Eloquent/State/LinksHandlerLocatorTrait.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ trait LinksHandlerLocatorTrait
2626

2727
private function getLinksHandler(Operation $operation): ?callable
2828
{
29-
if (!($options = $operation->getStateOptions()) || !$options instanceof Options) {
29+
if (!($options = $operation->getStateOptions()) || !$options instanceof Options || !$options->getHandleLinks()) {
3030
return null;
3131
}
3232

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Laravel\Eloquent\State;
15+
16+
use ApiPlatform\Metadata\Exception\RuntimeException;
17+
use ApiPlatform\Metadata\Operation;
18+
19+
/**
20+
* @internal
21+
*/
22+
trait ModelClassTrait
23+
{
24+
/**
25+
* @return class-string
26+
*/
27+
private function getModelClass(Operation $operation): string
28+
{
29+
$modelClass = $operation->getClass();
30+
31+
if (($options = $operation->getStateOptions()) && $options instanceof Options && $options->getModelClass()) {
32+
$modelClass = $options->getModelClass();
33+
}
34+
35+
if (!$modelClass || !class_exists($modelClass)) {
36+
throw new RuntimeException(\sprintf('No class found on operation %s.', $operation->getName()));
37+
}
38+
39+
return $modelClass;
40+
}
41+
}

‎src/Laravel/Eloquent/State/Options.php

+14
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class Options implements OptionsInterface
2323
* @see LinksHandlerInterface
2424
*/
2525
public function __construct(
26+
protected ?string $modelClass = null,
2627
protected mixed $handleLinks = null,
2728
) {
2829
}
@@ -39,4 +40,17 @@ public function withHandleLinks(mixed $handleLinks): self
3940

4041
return $self;
4142
}
43+
44+
public function getModelClass(): ?string
45+
{
46+
return $this->modelClass;
47+
}
48+
49+
public function withModelClass(?string $modelClass): self
50+
{
51+
$self = clone $this;
52+
$self->modelClass = $modelClass;
53+
54+
return $self;
55+
}
4256
}

‎src/Laravel/Tests/JsonLdTest.php

+12
Original file line numberDiff line numberDiff line change
@@ -357,4 +357,16 @@ public function testSimilarRoutesWithFormat(): void
357357
$response->assertStatus(200);
358358
$this->assertSame('/api/staff_position_histories', $response->json()['@id']);
359359
}
360+
361+
public function testResourceWithOptionModel(): void
362+
{
363+
$response = $this->get('/api/resource_with_models?page=1', ['accept' => 'application/ld+json']);
364+
$response->assertStatus(200);
365+
$response->assertHeader('content-type', 'application/ld+json; charset=utf-8');
366+
$response->assertJsonFragment([
367+
'@context' => '/api/contexts/ResourceWithModel',
368+
'@id' => '/api/resource_with_models',
369+
'@type' => 'Collection',
370+
]);
371+
}
360372
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Workbench\App\ApiResource;
15+
16+
use ApiPlatform\Laravel\Eloquent\State\CollectionProvider;
17+
use ApiPlatform\Laravel\Eloquent\State\ItemProvider;
18+
use ApiPlatform\Laravel\Eloquent\State\Options;
19+
use ApiPlatform\Metadata\ApiProperty;
20+
use ApiPlatform\Metadata\ApiResource;
21+
use ApiPlatform\Metadata\Get;
22+
use ApiPlatform\Metadata\GetCollection;
23+
use Workbench\App\Models\Book;
24+
25+
#[ApiResource(
26+
operations: [
27+
new GetCollection(provider: CollectionProvider::class),
28+
new Get(provider: ItemProvider::class),
29+
],
30+
stateOptions: new Options(modelClass: Book::class),
31+
)]
32+
class ResourceWithModel
33+
{
34+
#[ApiProperty(identifier: true)]
35+
public ?string $id = null;
36+
}

0 commit comments

Comments
 (0)
Please sign in to comment.