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
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

namespace Illuminate\Support\Traits;

use Illuminate\Database\Eloquent\Attributes\UseResource;
use Illuminate\Database\Eloquent\Attributes\UseResourceCollection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Resources\Json\ResourceCollection;
use LogicException;
use ReflectionClass;

trait TransformsToResourceCollection
{
Expand Down Expand Up @@ -47,6 +50,18 @@ protected function guessResourceCollection(): ResourceCollection

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));

$useResourceCollection = $this->resolveResourceCollectionFromAttribute($className);

if ($useResourceCollection !== null && class_exists($useResourceCollection)) {
return new $useResourceCollection($this);
}

$useResource = $this->resolveResourceFromAttribute($className);

if ($useResource !== null && class_exists($useResource)) {
return $useResource::collection($this);
}

$resourceClasses = $className::guessResourceName();

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

throw new LogicException(sprintf('Failed to find resource class for model [%s].', $className));
}

/**
* Get the resource class from the class attribute.
*
* @param class-string<\Illuminate\Http\Resources\Json\JsonResource> $class
* @return class-string<*>|null
*/
protected function resolveResourceFromAttribute(string $class): ?string
{
if (! class_exists($class)) {
return null;
}

$attributes = (new ReflectionClass($class))->getAttributes(UseResource::class);

return $attributes !== []
? $attributes[0]->newInstance()->class
: null;
}

/**
* Get the resource collection class from the class attribute.
*
* @param class-string<\Illuminate\Http\Resources\Json\ResourceCollection> $class
* @return class-string<*>|null
*/
protected function resolveResourceCollectionFromAttribute(string $class): ?string
{
if (! class_exists($class)) {
return null;
}

$attributes = (new ReflectionClass($class))->getAttributes(UseResourceCollection::class);

return $attributes !== []
? $attributes[0]->newInstance()->class
: null;
}
}
18 changes: 18 additions & 0 deletions src/Illuminate/Database/Eloquent/Attributes/UseResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Illuminate\Database\Eloquent\Attributes;

use Attribute;

#[Attribute(Attribute::TARGET_CLASS)]
class UseResource
{
/**
* Create a new attribute instance.
*
* @param class-string<*> $class
*/
public function __construct(public string $class)
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Illuminate\Database\Eloquent\Attributes;

use Attribute;

#[Attribute(Attribute::TARGET_CLASS)]
class UseResourceCollection
{
/**
* Create a new attribute instance.
*
* @param class-string<*> $class
*/
public function __construct(public string $class)
{
}
}
27 changes: 27 additions & 0 deletions src/Illuminate/Database/Eloquent/Concerns/TransformsToResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

namespace Illuminate\Database\Eloquent\Concerns;

use Illuminate\Database\Eloquent\Attributes\UseResource;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Str;
use LogicException;
use ReflectionClass;

trait TransformsToResource
{
Expand All @@ -30,6 +32,12 @@ public function toResource(?string $resourceClass = null): JsonResource
*/
protected function guessResource(): JsonResource
{
$resourceClass = $this->resolveResourceFromAttribute(static::class);

if ($resourceClass !== null && class_exists($resourceClass)) {
return $resourceClass::make($this);
}

foreach (static::guessResourceName() as $resourceClass) {
if (is_string($resourceClass) && class_exists($resourceClass)) {
return $resourceClass::make($this);
Expand Down Expand Up @@ -67,4 +75,23 @@ class_basename($modelClass)

return [$potentialResource.'Resource', $potentialResource];
}

/**
* Get the resource class from the class attribute.
*
* @param class-string<\Illuminate\Http\Resources\Json\JsonResource> $class
* @return class-string<*>|null
*/
protected function resolveResourceFromAttribute(string $class): ?string
{
if (! class_exists($class)) {
return null;
}

$attributes = (new ReflectionClass($class))->getAttributes(UseResource::class);

return $attributes !== []
? $attributes[0]->newInstance()->class
: null;
}
}
32 changes: 28 additions & 4 deletions tests/Database/DatabaseEloquentResourceCollectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@
namespace Illuminate\Tests\Database;

use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Tests\Database\Fixtures\Models\EloquentResourceCollectionTestModel;
use Illuminate\Tests\Database\Fixtures\Models\EloquentResourceTestResourceModelWithUseResourceAttribute;
use Illuminate\Tests\Database\Fixtures\Models\EloquentResourceTestResourceModelWithUseResourceCollectionAttribute;
use Illuminate\Tests\Database\Fixtures\Resources\EloquentResourceCollectionTestResource;
use Illuminate\Tests\Database\Fixtures\Resources\EloquentResourceTestJsonResource;
use Illuminate\Tests\Database\Fixtures\Resources\EloquentResourceTestJsonResourceCollection;
use PHPUnit\Framework\TestCase;

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

$this->assertInstanceOf(JsonResource::class, $resource);
}
}

class EloquentResourceCollectionTestResource extends JsonResource
{
//
public function testItCanTransformToResourceViaUseResourceAttribute()
{
$collection = new Collection([
new EloquentResourceTestResourceModelWithUseResourceCollectionAttribute(),
]);

$resource = $collection->toResourceCollection();

$this->assertInstanceOf(EloquentResourceTestJsonResourceCollection::class, $resource);
}

public function testItCanTransformToResourceViaUseResourceCollectionAttribute()
{
$collection = new Collection([
new EloquentResourceTestResourceModelWithUseResourceAttribute(),
]);

$resource = $collection->toResourceCollection();

$this->assertInstanceOf(AnonymousResourceCollection::class, $resource);
$this->assertInstanceOf(EloquentResourceTestJsonResource::class, $resource[0]);
}
}
16 changes: 11 additions & 5 deletions tests/Database/DatabaseEloquentResourceModelTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

namespace Illuminate\Tests\Database;

use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Tests\Database\Fixtures\Models\EloquentResourceTestResourceModel;
use Illuminate\Tests\Database\Fixtures\Models\EloquentResourceTestResourceModelWithGuessableResource;
use Illuminate\Tests\Database\Fixtures\Models\EloquentResourceTestResourceModelWithUseResourceAttribute;
use Illuminate\Tests\Database\Fixtures\Resources\EloquentResourceTestJsonResource;
use PHPUnit\Framework\TestCase;

class DatabaseEloquentResourceModelTest extends TestCase
Expand Down Expand Up @@ -59,9 +60,14 @@ public function testItCanGuessResourceName()
'Illuminate\Tests\Database\Fixtures\Http\Resources\EloquentResourceTestResourceModel',
], $model::guessResourceName());
}
}

class EloquentResourceTestJsonResource extends JsonResource
{
//
public function testItCanTransformToResourceViaUseResourceAttribute()
{
$model = new EloquentResourceTestResourceModelWithUseResourceAttribute();

$resource = $model->toResource();

$this->assertInstanceOf(EloquentResourceTestJsonResource::class, $resource);
$this->assertSame($model, $resource->resource);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Illuminate\Tests\Database\Fixtures\Models;

use Illuminate\Database\Eloquent\Attributes\UseResource;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Tests\Database\Fixtures\Resources\EloquentResourceTestJsonResource;

#[UseResource(EloquentResourceTestJsonResource::class)]
class EloquentResourceTestResourceModelWithUseResourceAttribute extends Model
{
//
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Illuminate\Tests\Database\Fixtures\Models;

use Illuminate\Database\Eloquent\Attributes\UseResourceCollection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Tests\Database\Fixtures\Resources\EloquentResourceTestJsonResourceCollection;

#[UseResourceCollection(EloquentResourceTestJsonResourceCollection::class)]
class EloquentResourceTestResourceModelWithUseResourceCollectionAttribute extends Model
{
//
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Illuminate\Tests\Database\Fixtures\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class EloquentResourceCollectionTestResource extends JsonResource
{
//
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Illuminate\Tests\Database\Fixtures\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class EloquentResourceTestJsonResource extends JsonResource
{
//
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Illuminate\Tests\Database\Fixtures\Resources;

use Illuminate\Http\Resources\Json\ResourceCollection;

class EloquentResourceTestJsonResourceCollection extends ResourceCollection
{
//
}