Documentación de Laravel 10.x
Aquí encontrarás fragmentos de código de Laravel y consejos útiles sobre desarrollo web.
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.
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
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
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));});
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());});
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);});
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;}
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));});
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.
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());});
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.", }, { "id": 2, "name": "Liliana Mayert", } ]}
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 clavesdata
que agregues manualmente a tus propias colecciones de recursos.
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]; }}
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.", }, { "id": 2, "name": "Liliana Mayert", } ], "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 }}
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.", }, { "id": 2, "name": "Liliana Mayert", } ], "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 }}
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;}
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),
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.
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.
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'),
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; }), ];}
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.
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', ], ]; }}
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', ]]);
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'); }}