JsonResource — toArray(), mergeWhen(), whenLoaded()
Concept
toArray(), mergeWhen(), and whenLoaded() are the key methods for building rich, conditional resource transformations.
$this->attribute: Inside a Resource, $this proxies to the underlying model. $this->id → $model->id. Accessors, casts, and custom attributes work: $this->full_name, $this->is_published.
$this->whenLoaded(string $relationship): Include relationship data ONLY if it was eager loaded. If not loaded, omits the key entirely from the response. This is critical — without it, accessing $this->posts triggers lazy loading (N+1) and always includes the relationship.
$this->when($condition, $value, $default = null): Include a field only when the condition is true. The value is only computed if the condition is true (lazy evaluation). Optional third arg: default value or closure when condition is false.
$this->mergeWhen($condition, array $data): Merge an entire array into the resource when condition is true. Clean for conditional blocks of related fields.
$this->whenHas($attribute): Include only when the attribute exists on the model (not null vs non-existent distinction, but whether the key is present in $attributes).
$this->whenNotNull($attribute): Include only when the attribute value is not null.
Nested resources: Return new OtherResource($this->relationship) for nested transformation: 'author' => new UserResource($this->whenLoaded('author')).
Code Example
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Http\Request;
class PostResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'title' => $this->title,
'slug' => $this->slug,
'excerpt' => $this->excerpt,
'published_at' => $this->published_at?->toISOString(),
// whenLoaded — include only if eager loaded, no lazy loading!
'author' => new UserResource($this->whenLoaded('author')),
'tags' => TagResource::collection($this->whenLoaded('tags')),
'comments' => CommentResource::collection($this->whenLoaded('comments')),
// when — conditional fields
'body' => $this->when($request->has('include_body'), $this->body),
'view_count' => $this->when($this->is_published, $this->view_count),
// admin-only fields
'internal_notes' => $this->when(
$request->user()?->isAdmin(),
$this->internal_notes
),
// whenNotNull — omit if null
'deleted_at' => $this->whenNotNull($this->deleted_at?->toISOString()),
// mergeWhen — include an entire block conditionally
$this->mergeWhen($request->user()?->id === $this->user_id, [
'edit_url' => route('posts.edit', $this->id),
'delete_url' => route('posts.destroy', $this->id),
]),
'meta' => [
'is_published' => $this->is_published,
'reading_time' => $this->reading_time, // accessor
'comments_count' => $this->comments_count, // withCount
],
];
}
}
// Controller with eager loading
public function show(Request $request, Post $post): PostResource
{
$post->load(['author', 'tags']); // load before passing to resource
// 'comments' NOT loaded — whenLoaded('comments') returns MissingValue (omitted)
return new PostResource($post);
}