1. Eloquent ORM
  2. Eloquent: Recursos de API

Introducción

Al construir una API, es posible que necesites una capa de transformación que se encuentre entre tus modelos Eloquent y las respuestas JSON que se devuelven realmente a los usuarios de tu aplicación. Por ejemplo, es posible que desees mostrar ciertos atributos solo para un subconjunto de usuarios y no para otros, o que desees incluir siempre ciertas relaciones en la representación JSON de tus modelos. Las clases de recursos de Eloquent te permiten transformar de manera expresiva y fácil tus modelos y colecciones de modelos en JSON.

Por supuesto, siempre puedes convertir modelos o colecciones Eloquent a JSON utilizando sus métodos toJson; sin embargo, los recursos de Eloquent proporcionan un control más granular y robusto sobre la serialización JSON de tus modelos y sus relaciones.

Generar Recursos

Para generar una clase de recurso, puedes utilizar el comando Artisan make:resource. De forma predeterminada, los recursos se colocarán en el directorio app/Http/Resources de tu aplicación. Los recursos extienden la clase Illuminate\Http\Resources\Json\JsonResource:

php artisan make:resource UserResource

Colecciones de Recursos

Además de generar recursos que transforman modelos individuales, puedes generar recursos que son responsables de transformar colecciones de modelos. Esto permite que tus respuestas JSON incluyan enlaces y otra información meta relevante para toda una colección de un recurso dado.

Para crear una colección de recursos, debes usar la bandera --collection al crear el recurso. O, incluir la palabra Collection en el nombre del recurso indicará a Laravel que debe crear un recurso de colección. Las colecciones de recursos extienden la clase Illuminate\Http\Resources\Json\ResourceCollection:

php artisan make:resource User --collection
 
php artisan make:resource UserCollection

Resumen del Concepto

Nota Esta es una descripción general de alto nivel de recursos y colecciones de recursos. Se recomienda encarecidamente leer las otras secciones de esta documentación para obtener una comprensión más profunda de la personalización y la potencia que te ofrecen los recursos.

Antes de sumergirnos en todas las opciones disponibles al escribir recursos, veamos primero cómo se utilizan los recursos dentro de Laravel desde una perspectiva de alto nivel. Una clase de recurso representa un solo modelo que debe transformarse en una estructura JSON. Por ejemplo, aquí tienes una simple clase de recurso UserResource:

<?php
 
namespace App\Http\Resources;
 
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
 
class UserResource extends JsonResource
{
/**
* Transforma el recurso en una matriz.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}

Cada clase de recurso define un método toArray que devuelve la matriz de atributos que se deben convertir a JSON cuando el recurso se devuelve como una respuesta desde una ruta o método del controlador.

Ten en cuenta que podemos acceder a las propiedades del modelo directamente desde la variable $this. Esto se debe a que una clase de recurso automáticamente proxy el acceso a propiedades y métodos hacia abajo al modelo subyacente para un acceso conveniente. Una vez que se define el recurso, se puede devolver desde una ruta o controlador. El recurso acepta la instancia del modelo subyacente a través de su constructor:

use App\Http\Resources\UserResource;
use App\Models\User;
 
Route::get('/user/{id}', function (string $id) {
return new UserResource(User::findOrFail($id));
});

Colecciones de Recursos

Si estás devolviendo una colección de recursos o una respuesta paginada, debes usar el método collection proporcionado por tu clase de recurso al crear la instancia del recurso en tu ruta o controlador:

use App\Http\Resources\UserResource;
use App\Models\User;
 
Route::get('/users', function () {
return UserResource::collection(User::all());
});

Ten en cuenta que esto no permite la adición de metadatos personalizados que puedan necesitar ser devueltos con tu colección. Si deseas personalizar la respuesta de la colección de recursos, puedes crear un recurso dedicado para representar la colección:

php artisan make:resource UserCollection

Una vez que se ha generado la clase de colección de recursos, puedes definir fácilmente cualquier metadato que deba incluirse en la respuesta:

<?php
 
namespace App\Http\Resources;
 
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
 
class UserCollection extends ResourceCollection
{
/**
* Transforma la colección de recursos en una matriz.
*
* @return array<int|string, mixed>
*/
public function toArray(Request $request): array
{
return [
'data' => $this->collection,
'links' => [
'self' => 'link-value',
],
];
}
}

Después de definir tu colección de recursos, puede devolverse desde una ruta o controlador:

use App\Http\Resources\UserCollection;
use App\Models\User;
 
Route::get('/users', function () {
return new UserCollection(User::all());
});

Conservar Claves de la Colección

Al devolver una colección de recursos desde una ruta, Laravel restablece las claves de la colección para que estén en orden numérico. Sin embargo, puedes agregar una propiedad preserveKeys a tu clase de recurso indicando si las claves originales de la colección deben conservarse:

<?php
 
namespace App\Http\Resources;
 
use Illuminate\Http\Resources\Json\JsonResource;
 
class UserResource extends JsonResource
{
/**
* Indica si las claves de la colección del recurso deben conservarse.
*
* @var bool
*/
public $preserveKeys = true;
}

Cuando la propiedad preserveKeys se establece en true, las claves de la colección se conservarán cuando la colección se devuelva desde una ruta o controlador:

use App\Http\Resources\UserResource;
use App\Models\User;
 
Route::get('/users', function () {
return UserResource::collection(User::all()->keyBy->id);
});

Personalizar la Clase de Recurso Subyacente

Normalmente, la propiedad $this->collection de una colección de recursos se rellena automáticamente con el resultado de asignar cada elemento de la colección a su clase de recurso singular. Se asume que la clase de recurso singular es el nombre de la clase de la colección sin la parte Collection al final del nombre de la clase. Además, dependiendo de tus preferencias personales, la clase de recurso singular puede o no tener un sufijo de Resource.

Por ejemplo, UserCollection intentará asignar las instancias de usuario dadas al recurso UserResource. Para personalizar este comportamiento, puedes anular la propiedad $collects de tu colección de recursos:

<?php
 
namespace App\Http\Resources;
 
use Illuminate\Http\Resources\Json\ResourceCollection;
 
class UserCollection extends ResourceCollection
{
/**
* El recurso que recopila este recurso.
*
* @var string
*/
public $collects = Member::class;
}

Escribir Recursos

Nota Si no has leído la visión general del concepto, se recomienda encarecidamente hacerlo antes de continuar con esta documentación.

Los recursos solo necesitan transformar un modelo dado en una matriz. Así que, cada recurso contiene un método toArray que traduce los atributos de tu modelo en una matriz amigable para la API que puede devolverse desde las rutas o controladores de tu aplicación:

<?php
 
namespace App\Http\Resources;
 
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
 
class UserResource extends JsonResource
{
/**
* Transforma el recurso en una matriz.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}

Una vez que se ha definido un recurso, puede devolverse directamente desde una ruta o controlador:

use App\Http\Resources\UserResource;
use App\Models\User;
 
Route::get('/user/{id}', function (string $id) {
return new UserResource(User::findOrFail($id));
});

Relaciones

Si deseas incluir recursos relacionados en tu respuesta, puedes agregarlos a la matriz devuelta por el método toArray de tu recurso. En este ejemplo, utilizaremos el método collection del recurso PostResource para agregar las publicaciones del blog del usuario a la respuesta del recurso:

use App\Http\Resources\PostResource;
use Illuminate\Http\Request;
 
/**
* Transforma el recurso en una matriz.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'posts' => PostResource::collection($this->posts),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}

Nota Si deseas incluir relaciones solo cuando ya han sido cargadas, consulta la documentación sobre relaciones condicionales.

Colecciones de Recursos

Mientras que los recursos transforman un solo modelo en una matriz, las colecciones de recursos transforman una colección de modelos en una matriz. Sin embargo, no es absolutamente necesario definir una clase de colección de recursos para cada uno de tus modelos, ya que todos los recursos proporcionan un método collection para generar una colección de recursos "ad-hoc" sobre la marcha:

use App\Http\Resources\UserResource;
use App\Models\User;
 
Route::get('/users', function () {
return UserResource::collection(User::all());
});

Sin embargo, si necesitas personalizar los metadatos devueltos con la colección, es necesario definir tu propia colección de recursos:

<?php
 
namespace App\Http\Resources;
 
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
 
class UserCollection extends ResourceCollection
{
/**
* Transforma la colección de recursos en una matriz.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'data' => $this->collection,
'links' => [
'self' => 'link-value',
],
];
}
}

Al igual que los recursos singulares, las colecciones de recursos pueden devolverse directamente desde rutas o controladores:

use App\Http\Resources\UserCollection;
use App\Models\User;
 
Route::get('/users', function () {
return new UserCollection(User::all());
});

Envoltura de Datos

De forma predeterminada, tu recurso más externo se envuelve en una clave data cuando la respuesta del recurso se convierte a JSON. Entonces, por ejemplo, una respuesta típica de colección de recursos se ve así:

{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": "[email protected]"
},
{
"id": 2,
"name": "Liliana Mayert",
"email": "[email protected]"
}
]
}

Si deseas utilizar una clave personalizada en lugar de data, puedes definir un atributo $wrap en la clase de recurso:

<?php
 
namespace App\Http\Resources;
 
use Illuminate\Http\Resources\Json\JsonResource;
 
class UserResource extends JsonResource
{
/**
* El envoltorio "data" que se debe aplicar.
*
* @var string|null
*/
public static $wrap = 'user';
}

Si deseas deshabilitar el envoltorio del recurso más externo, debes invocar el método withoutWrapping en la clase base Illuminate\Http\Resources\Json\JsonResource. Por lo general, debes llamar a este método desde tu AppServiceProvider u otro proveedor de servicios que se cargue en cada solicitud de tu aplicación:

<?php
 
namespace App\Providers;
 
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\ServiceProvider;
 
class AppServiceProvider extends ServiceProvider
{
/**
* Registra los servicios de la aplicación.
*/
public function register(): void
{
// ...
}
 
/**
* Inicia los servicios de la aplicación.
*/
public function boot(): void
{
JsonResource::withoutWrapping();
}
}

Advertencia El método withoutWrapping solo afecta a la respuesta más externa y no eliminará las claves data que agregues manualmente a tus propias colecciones de recursos.

Envolver Recursos Anidados

Tienes total libertad para determinar cómo se envuelven las relaciones de tu recurso. Si deseas que todas las colecciones de recursos se envuelvan en una clave data, independientemente de su anidación, debes definir una clase de colección de recursos para cada recurso y devolver la colección dentro de una clave data.

Puede que te preguntes si esto hará que tu recurso más externo se envuelva accidentalmente en dos claves data. No te preocupes, Laravel nunca permitirá que tus recursos se envuelvan accidentalmente dos veces, así que no tienes que preocuparte por el nivel de anidación de la colección de recursos que estás transformando:

<?php
 
namespace App\Http\Resources;
 
use Illuminate\Http\Resources\Json\ResourceCollection;
 
class CommentsCollection extends ResourceCollection
{
/**
* Transforma la colección de recursos en una matriz.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return ['data' => $this->collection];
}
}

Envoltura de Datos y Paginación

Cuando se devuelven colecciones paginadas a través de una respuesta de recurso, Laravel envuelve los datos del recurso en una clave data incluso si se ha llamado al método withoutWrapping. Esto se debe a que las respuestas paginadas siempre contienen las claves meta y links con información sobre el estado del paginador:

{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": "[email protected]"
},
{
"id": 2,
"name": "Liliana Mayert",
"email": "[email protected]"
}
],
"links":{
"first": "http://example.com/users?page=1",
"last": "http://example.com/users?page=1",
"prev": null,
"next": null
},
"meta":{
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "http://example.com/users",
"per_page": 15,
"to": 10,
"total": 10
}
}

Paginación

Puedes pasar una instancia de paginador de Laravel al método collection de un recurso o a una colección de recursos personalizada:

use App\Http\Resources\UserCollection;
use App\Models\User;
 
Route::get('/users', function () {
return new UserCollection(User::paginate());
});

Las respuestas paginadas siempre contienen las claves meta y links con información sobre el estado del paginador:

{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": "[email protected]"
},
{
"id": 2,
"name": "Liliana Mayert",
"email": "[email protected]"
}
],
"links":{
"first": "http://example.com/users?page=1",
"last": "http://example.com/users?page=1",
"prev": null,
"next": null
},
"meta":{
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "http://example.com/users",
"per_page": 15,
"to": 10,
"total": 10
}
}

Personalizar la Información de Paginación

Si deseas personalizar la información incluida en las claves links o meta de la respuesta de paginación, puedes definir un método paginationInformation en el recurso. Este método recibirá los datos $paginated y el array de información $default, que es un array que contiene las claves links y meta:

/**
* Personaliza la información de paginación para el recurso.
*
* @param \Illuminate\Http\Request $request
* @param array $paginated
* @param array $default
* @return array
*/
public function paginationInformation($request, $paginated, $default)
{
$default['links']['custom'] = 'https://example.com';
 
return $default;
}

Atributos Condicionales

A veces, es posible que desees incluir un atributo solo en una respuesta de recurso si se cumple una condición dada. Por ejemplo, es posible que desees incluir un valor solo si el usuario actual es un "administrador". Laravel proporciona una variedad de métodos auxiliares para ayudarte en esta situación. El método when se puede utilizar para agregar condicionalmente un atributo a una respuesta de recurso:

/**
* Transforma el recurso en una matriz.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'secret' => $this->when($request->user()->isAdmin(), 'secret-value'),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}

En este ejemplo, la clave secret solo se devolverá en la respuesta final del recurso si el método isAdmin del usuario autenticado devuelve true. Si el método devuelve false, la clave secret se eliminará de la respuesta del recurso antes de enviarse al cliente. El método when te permite definir expresivamente tus recursos sin recurrir a declaraciones condicionales al construir la matriz.

El método when también acepta un cierre como su segundo argumento, lo que te permite calcular el valor resultante solo si la condición dada es true:

'secret' => $this->when($request->user()->isAdmin(), function () {
return 'modify_10x/eloquent-resources.return_text_541';
}),

El método whenHas se puede utilizar para incluir un atributo si está presente en el modelo subyacente:

'name' => $this->whenHas('name'),

Además, el método whenNotNull se puede utilizar para incluir un atributo en la respuesta del recurso si el atributo no es nulo:

'name' => $this->whenNotNull($this->name),

Combinar Atributos Condicionales

A veces, puedes tener varios atributos que solo deben incluirse en la respuesta del recurso según la misma condición. En este caso, puedes utilizar el método mergeWhen para incluir los atributos en la respuesta solo cuando la condición dada sea true:

/**
* Transforma el recurso en una matriz.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
$this->mergeWhen($request->user()->isAdmin(), [
'first-secret' => 'value',
'second-secret' => 'value',
]),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}

Nuevamente, si la condición dada es false, estos atributos se eliminarán de la respuesta del recurso antes de enviarse al cliente.

Advertencia El método mergeWhen no debe utilizarse dentro de arrays que mezclan claves de cadena y numéricas. Además, no debe usarse dentro de arrays con claves numéricas que no estén ordenadas secuencialmente.

Relaciones Condicionales

Además de cargar atributos condicionalmente, puedes incluir condicionalmente relaciones en las respuestas de tus recursos según si la relación ya se ha cargado en el modelo. Esto permite que tu controlador decida qué relaciones deben cargarse en el modelo y tu recurso puede incluirlas fácilmente solo cuando realmente se han cargado. En última instancia, esto facilita evitar problemas de consulta "N+1" dentro de tus recursos.

El método whenLoaded se puede utilizar para cargar condicionalmente una relación. Para evitar cargar relaciones innecesariamente, este método acepta el nombre de la relación en lugar de la relación en sí:

use App\Http\Resources\PostResource;
 
/**
* Transforma el recurso en una matriz.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'posts' => PostResource::collection($this->whenLoaded('posts')),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}

En este ejemplo, si la relación no se ha cargado, la clave posts se eliminará de la respuesta del recurso antes de enviarse al cliente.

Conteos de Relaciones Condicionales

Además de incluir relaciones condicionalmente, puedes incluir condicionalmente "conteos" de relaciones en las respuestas de tus recursos según si se ha cargado el conteo de la relación en el modelo:

new UserResource($user->loadCount('posts'));

El método whenCounted se puede utilizar para incluir condicionalmente el conteo de una relación en la respuesta de tu recurso. Este método evita incluir innecesariamente el atributo si el conteo de las relaciones no está presente:

/**
* Transforma el recurso en una matriz.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'posts_count' => $this->whenCounted('posts'),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}

En este ejemplo, si no se ha cargado el conteo de la relación posts, la clave posts_count se eliminará de la respuesta del recurso antes de enviarse al cliente.

Otros tipos de agregados, como avg, sum, min y max, también se pueden cargar condicionalmente utilizando el método whenAggregated:

'words_avg' => $this->whenAggregated('posts', 'words', 'avg'),
'words_sum' => $this->whenAggregated('posts', 'words', 'sum'),
'words_min' => $this->whenAggregated('posts', 'words', 'min'),
'words_max' => $this->whenAggregated('posts', 'words', 'max'),

Información de Pivote Condicional

Además de incluir condicionalmente información de relaciones en las respuestas de tus recursos, puedes incluir condicionalmente datos de las tablas intermedias de relaciones de muchos a muchos utilizando el método whenPivotLoaded. El método whenPivotLoaded acepta el nombre de la tabla intermedia como su primer argumento. El segundo argumento debe ser un cierre que devuelva el valor que se devolverá si la información de la tabla intermedia está disponible en el modelo:

/**
* Transforma el recurso en una matriz.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'expires_at' => $this->whenPivotLoaded('role_user', function () {
return $this->pivot->expires_at;
}),
];
}

Si tu relación está utilizando un modelo de tabla intermedia personalizado, puedes pasar una instancia del modelo de tabla intermedia como primer argumento al método whenPivotLoaded:

'expires_at' => $this->whenPivotLoaded(new Membership, function () {
return $this->pivot->expires_at;
}),

Si tu tabla intermedia está utilizando un accesorio que no sea pivot, puedes usar el método whenPivotLoadedAs:

/**
* Transforma el recurso en una matriz.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'expires_at' => $this->whenPivotLoadedAs('subscription', 'role_user', function () {
return $this->subscription->expires_at;
}),
];
}

Añadir Metadatos

Algunas normas de JSON API requieren la adición de metadatos a las respuestas de tus recursos y colecciones de recursos. Esto incluye cosas como links al recurso o recursos relacionados, o metadatos sobre el propio recurso. Si necesitas devolver metadatos adicionales sobre un recurso, inclúyelos en tu método toArray. Por ejemplo, podrías incluir información link al transformar una colección de recursos:

/**
* Transforma el recurso en una matriz.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'data' => $this->collection,
'links' => [
'self' => 'link-value',
],
];
}

Cuando devuelves metadatos adicionales desde tus recursos, nunca tienes que preocuparte por sobrescribir accidentalmente las claves links o meta que se agregan automáticamente Laravel al devolver respuestas paginadas. Cualquier links adicional que definas se fusionará con los enlaces proporcionados por el paginador.

Metadatos de Nivel Superior

A veces, puedes desear incluir solo ciertos metadatos con una respuesta de recurso si el recurso es el recurso más externo que se está devolviendo. Por lo general, esto incluye información meta sobre la respuesta en su conjunto. Para definir estos metadatos, agrega un método with a tu clase de recurso. Este método debe devolver un array de metadatos que se incluirán solo con la respuesta del recurso cuando el recurso sea el recurso más externo que se está transformando:

<?php
 
namespace App\Http\Resources;
 
use Illuminate\Http\Resources\Json\ResourceCollection;
 
class UserCollection extends ResourceCollection
{
/**
* Transforma la colección de recursos en una matriz.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return parent::toArray($request);
}
 
/**
* Obtiene datos adicionales que deben devolverse con la matriz de recursos.
*
* @return array<string, mixed>
*/
public function with(Request $request): array
{
return [
'meta' => [
'key' => 'value',
],
];
}
}

Añadir Metadatos al Construir Recursos

También puedes agregar datos de nivel superior al construir instancias de recursos en tu ruta o controlador. El método additional, que está disponible en todos los recursos, acepta un array de datos que se deben agregar a la respuesta del recurso:

return (new UserCollection(User::all()->load('roles')))
->additional(['meta' => [
'key' => 'value',
]]);

Respuestas de Recursos

Como ya has leído, los recursos se pueden devolver directamente desde rutas y controladores:

use App\Http\Resources\UserResource;
use App\Models\User;
 
Route::get('/user/{id}', function (string $id) {
return new UserResource(User::findOrFail($id));
});

Sin embargo, a veces puedes necesitar personalizar la respuesta HTTP saliente antes de que se envíe al cliente. Hay dos formas de lograr esto. En primer lugar, puedes encadenar el método response en el recurso. Este método devolverá una instancia de Illuminate\Http\JsonResponse, dándote control total sobre las cabeceras de la respuesta:

use App\Http\Resources\UserResource;
use App\Models\User;
 
Route::get('/user', function () {
return (new UserResource(User::find(1)))
->response()
->header('X-Value', 'True');
});

Alternativamente, puedes definir un método withResponse dentro del propio recurso. Este método se llamará cuando el recurso se devuelva como el recurso más externo en una respuesta:

<?php
 
namespace App\Http\Resources;
 
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
 
class UserResource extends JsonResource
{
/**
* Transforma el recurso en una matriz.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
];
}
 
/**
* Personaliza la respuesta saliente para el recurso.
*/
public function withResponse(Request $request, JsonResponse $response): void
{
$response->header('X-Value', 'True');
}
}