Skip to content

Commit 6eaaa84

Browse files
Lukasss93taylorotwell
authored andcommitted
[12.x] Add support for #[UseResource(...)] and #[UseResourceCollection(...)] attributes on models (laravel#56966)
* test: add fixture resources for Eloquent resource collection tests * feat: add UseResource and UseResourceCollection attributes for model resource transformation * refactor: update parameter type hints for resource and resource collection attributes * formatting --------- Co-authored-by: Taylor Otwell <[email protected]>
1 parent 59fd459 commit 6eaaa84

11 files changed

+211
-9
lines changed

src/Illuminate/Collections/Traits/TransformsToResourceCollection.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22

33
namespace Illuminate\Support\Traits;
44

5+
use Illuminate\Database\Eloquent\Attributes\UseResource;
6+
use Illuminate\Database\Eloquent\Attributes\UseResourceCollection;
57
use Illuminate\Database\Eloquent\Model;
68
use Illuminate\Http\Resources\Json\ResourceCollection;
79
use LogicException;
10+
use ReflectionClass;
811

912
trait TransformsToResourceCollection
1013
{
@@ -47,6 +50,18 @@ protected function guessResourceCollection(): ResourceCollection
4750

4851
throw_unless(method_exists($className, 'guessResourceName'), LogicException::class, sprintf('Expected class %s to implement guessResourceName method. Make sure the model uses the TransformsToResource trait.', $className));
4952

53+
$useResourceCollection = $this->resolveResourceCollectionFromAttribute($className);
54+
55+
if ($useResourceCollection !== null && class_exists($useResourceCollection)) {
56+
return new $useResourceCollection($this);
57+
}
58+
59+
$useResource = $this->resolveResourceFromAttribute($className);
60+
61+
if ($useResource !== null && class_exists($useResource)) {
62+
return $useResource::collection($this);
63+
}
64+
5065
$resourceClasses = $className::guessResourceName();
5166

5267
foreach ($resourceClasses as $resourceClass) {
@@ -65,4 +80,42 @@ protected function guessResourceCollection(): ResourceCollection
6580

6681
throw new LogicException(sprintf('Failed to find resource class for model [%s].', $className));
6782
}
83+
84+
/**
85+
* Get the resource class from the class attribute.
86+
*
87+
* @param class-string<\Illuminate\Http\Resources\Json\JsonResource> $class
88+
* @return class-string<*>|null
89+
*/
90+
protected function resolveResourceFromAttribute(string $class): ?string
91+
{
92+
if (! class_exists($class)) {
93+
return null;
94+
}
95+
96+
$attributes = (new ReflectionClass($class))->getAttributes(UseResource::class);
97+
98+
return $attributes !== []
99+
? $attributes[0]->newInstance()->class
100+
: null;
101+
}
102+
103+
/**
104+
* Get the resource collection class from the class attribute.
105+
*
106+
* @param class-string<\Illuminate\Http\Resources\Json\ResourceCollection> $class
107+
* @return class-string<*>|null
108+
*/
109+
protected function resolveResourceCollectionFromAttribute(string $class): ?string
110+
{
111+
if (! class_exists($class)) {
112+
return null;
113+
}
114+
115+
$attributes = (new ReflectionClass($class))->getAttributes(UseResourceCollection::class);
116+
117+
return $attributes !== []
118+
? $attributes[0]->newInstance()->class
119+
: null;
120+
}
68121
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Illuminate\Database\Eloquent\Attributes;
4+
5+
use Attribute;
6+
7+
#[Attribute(Attribute::TARGET_CLASS)]
8+
class UseResource
9+
{
10+
/**
11+
* Create a new attribute instance.
12+
*
13+
* @param class-string<*> $class
14+
*/
15+
public function __construct(public string $class)
16+
{
17+
}
18+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Illuminate\Database\Eloquent\Attributes;
4+
5+
use Attribute;
6+
7+
#[Attribute(Attribute::TARGET_CLASS)]
8+
class UseResourceCollection
9+
{
10+
/**
11+
* Create a new attribute instance.
12+
*
13+
* @param class-string<*> $class
14+
*/
15+
public function __construct(public string $class)
16+
{
17+
}
18+
}

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
namespace Illuminate\Database\Eloquent\Concerns;
44

5+
use Illuminate\Database\Eloquent\Attributes\UseResource;
56
use Illuminate\Http\Resources\Json\JsonResource;
67
use Illuminate\Support\Str;
78
use LogicException;
9+
use ReflectionClass;
810

911
trait TransformsToResource
1012
{
@@ -30,6 +32,12 @@ public function toResource(?string $resourceClass = null): JsonResource
3032
*/
3133
protected function guessResource(): JsonResource
3234
{
35+
$resourceClass = $this->resolveResourceFromAttribute(static::class);
36+
37+
if ($resourceClass !== null && class_exists($resourceClass)) {
38+
return $resourceClass::make($this);
39+
}
40+
3341
foreach (static::guessResourceName() as $resourceClass) {
3442
if (is_string($resourceClass) && class_exists($resourceClass)) {
3543
return $resourceClass::make($this);
@@ -67,4 +75,23 @@ class_basename($modelClass)
6775

6876
return [$potentialResource.'Resource', $potentialResource];
6977
}
78+
79+
/**
80+
* Get the resource class from the class attribute.
81+
*
82+
* @param class-string<\Illuminate\Http\Resources\Json\JsonResource> $class
83+
* @return class-string<*>|null
84+
*/
85+
protected function resolveResourceFromAttribute(string $class): ?string
86+
{
87+
if (! class_exists($class)) {
88+
return null;
89+
}
90+
91+
$attributes = (new ReflectionClass($class))->getAttributes(UseResource::class);
92+
93+
return $attributes !== []
94+
? $attributes[0]->newInstance()->class
95+
: null;
96+
}
7097
}

tests/Database/DatabaseEloquentResourceCollectionTest.php

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,14 @@
33
namespace Illuminate\Tests\Database;
44

55
use Illuminate\Database\Eloquent\Collection;
6+
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
67
use Illuminate\Http\Resources\Json\JsonResource;
78
use Illuminate\Tests\Database\Fixtures\Models\EloquentResourceCollectionTestModel;
9+
use Illuminate\Tests\Database\Fixtures\Models\EloquentResourceTestResourceModelWithUseResourceAttribute;
10+
use Illuminate\Tests\Database\Fixtures\Models\EloquentResourceTestResourceModelWithUseResourceCollectionAttribute;
11+
use Illuminate\Tests\Database\Fixtures\Resources\EloquentResourceCollectionTestResource;
12+
use Illuminate\Tests\Database\Fixtures\Resources\EloquentResourceTestJsonResource;
13+
use Illuminate\Tests\Database\Fixtures\Resources\EloquentResourceTestJsonResourceCollection;
814
use PHPUnit\Framework\TestCase;
915

1016
class DatabaseEloquentResourceCollectionTest extends TestCase
@@ -43,9 +49,27 @@ class_alias(EloquentResourceCollectionTestResource::class, 'Illuminate\Tests\Dat
4349

4450
$this->assertInstanceOf(JsonResource::class, $resource);
4551
}
46-
}
4752

48-
class EloquentResourceCollectionTestResource extends JsonResource
49-
{
50-
//
53+
public function testItCanTransformToResourceViaUseResourceAttribute()
54+
{
55+
$collection = new Collection([
56+
new EloquentResourceTestResourceModelWithUseResourceCollectionAttribute(),
57+
]);
58+
59+
$resource = $collection->toResourceCollection();
60+
61+
$this->assertInstanceOf(EloquentResourceTestJsonResourceCollection::class, $resource);
62+
}
63+
64+
public function testItCanTransformToResourceViaUseResourceCollectionAttribute()
65+
{
66+
$collection = new Collection([
67+
new EloquentResourceTestResourceModelWithUseResourceAttribute(),
68+
]);
69+
70+
$resource = $collection->toResourceCollection();
71+
72+
$this->assertInstanceOf(AnonymousResourceCollection::class, $resource);
73+
$this->assertInstanceOf(EloquentResourceTestJsonResource::class, $resource[0]);
74+
}
5175
}

tests/Database/DatabaseEloquentResourceModelTest.php

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22

33
namespace Illuminate\Tests\Database;
44

5-
use Illuminate\Http\Resources\Json\JsonResource;
65
use Illuminate\Tests\Database\Fixtures\Models\EloquentResourceTestResourceModel;
76
use Illuminate\Tests\Database\Fixtures\Models\EloquentResourceTestResourceModelWithGuessableResource;
7+
use Illuminate\Tests\Database\Fixtures\Models\EloquentResourceTestResourceModelWithUseResourceAttribute;
8+
use Illuminate\Tests\Database\Fixtures\Resources\EloquentResourceTestJsonResource;
89
use PHPUnit\Framework\TestCase;
910

1011
class DatabaseEloquentResourceModelTest extends TestCase
@@ -59,9 +60,14 @@ public function testItCanGuessResourceName()
5960
'Illuminate\Tests\Database\Fixtures\Http\Resources\EloquentResourceTestResourceModel',
6061
], $model::guessResourceName());
6162
}
62-
}
6363

64-
class EloquentResourceTestJsonResource extends JsonResource
65-
{
66-
//
64+
public function testItCanTransformToResourceViaUseResourceAttribute()
65+
{
66+
$model = new EloquentResourceTestResourceModelWithUseResourceAttribute();
67+
68+
$resource = $model->toResource();
69+
70+
$this->assertInstanceOf(EloquentResourceTestJsonResource::class, $resource);
71+
$this->assertSame($model, $resource->resource);
72+
}
6773
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Illuminate\Tests\Database\Fixtures\Models;
4+
5+
use Illuminate\Database\Eloquent\Attributes\UseResource;
6+
use Illuminate\Database\Eloquent\Model;
7+
use Illuminate\Tests\Database\Fixtures\Resources\EloquentResourceTestJsonResource;
8+
9+
#[UseResource(EloquentResourceTestJsonResource::class)]
10+
class EloquentResourceTestResourceModelWithUseResourceAttribute extends Model
11+
{
12+
//
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Illuminate\Tests\Database\Fixtures\Models;
4+
5+
use Illuminate\Database\Eloquent\Attributes\UseResourceCollection;
6+
use Illuminate\Database\Eloquent\Model;
7+
use Illuminate\Tests\Database\Fixtures\Resources\EloquentResourceTestJsonResourceCollection;
8+
9+
#[UseResourceCollection(EloquentResourceTestJsonResourceCollection::class)]
10+
class EloquentResourceTestResourceModelWithUseResourceCollectionAttribute extends Model
11+
{
12+
//
13+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Illuminate\Tests\Database\Fixtures\Resources;
4+
5+
use Illuminate\Http\Resources\Json\JsonResource;
6+
7+
class EloquentResourceCollectionTestResource extends JsonResource
8+
{
9+
//
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Illuminate\Tests\Database\Fixtures\Resources;
4+
5+
use Illuminate\Http\Resources\Json\JsonResource;
6+
7+
class EloquentResourceTestJsonResource extends JsonResource
8+
{
9+
//
10+
}

0 commit comments

Comments
 (0)