0

JsonResource — toArray(), mergeWhen(), whenLoaded()

Intermediate5 min read·lv-24-002

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