How to use whenLoaded in Laravel API Resources
An Eloquent API resource in Laravel is a class that allows you to transform your Eloquent models and model collections into a JSON format suitable for API responses. It provides a convenient way to shape the output of your model data, making it easier to control what data is exposed and how it's structured in your API responses.
Here's an example:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}
Resources are highly customizable, as you can see in the Laravel documentation. Among all the available methods, I stumbled upon the whenLoaded
method, whose explanation in the documentation was rather confusing, at least to me.
The
whenLoaded
method may be used to conditionally load a relationship. In order to avoid unnecessarily loading relationships, this method accepts the name of the relationship instead of the relationship itself:
use App\Http\Resources\PostResource;
/**
* Transform the resource into an array.
*
* @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,
];
}
Example provided by the documentation
In this example, if the relationship has not been loaded, the posts key will be removed from the resource response before it is sent to the client.
It's not clear what is supposed to load the posts
collection, as the documentation doesn't explicitly state it at any point. To make the example more understandable, I've tweaked it a bit, as it's a bit confusing to have a PostResource
check if a posts
collection is loaded.
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class PostResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'content' => $this->content,
'comments' => CommentResource::collection($this->whenLoaded('comments')),
];
}
}
Now the relationship makes more sense, since it's a Post
with optional Comment
s attached. However, the question of what loads the comments
collection remains unanswered. After reading the documentation for Eloquent: Relationships and doing some testing, you can figure out that we should load the Post
collection from the PostController
as follows:
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use App\Http\Resources\PostResource;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function index(Request $request)
{
$posts = Post::with('comments')->get();
return PostResource::collection($posts);
}
}
In the example above, , thanks to the whenLoaded
method, the comments
property of the JSON should contain all of the comments associated with each post. Conversely, if we change the $posts = Post::with('comments')->get();
for $posts = Post::all();
, the comments
property will be empty because we haven't loaded the comments
associated with each post.
Now that we know where the relationship comes from, we might come up with the following question: how can we use whenLoaded
for relationships deeper than one level? The answer for this in recent versions of Laravel is quite simple, as you can just do Post::with('comments.replies')
, or even pass a nested array to the with
method, which can be convenient when eagerly loading multiple nested relationships:
$books = Book::with([
'author' => [
'contacts',
'publisher',
],
])->get();
Example taken from the nested eager loading section of the documentation.
As you can see, whenLoaded
is indeed a powerful method "to avoid unnecessarily loading relationships", as the docs describe.