# Understanding `whenLoaded()` and `when()` in Laravel API Resources

<details data-node-type="hn-details-summary"><summary>Summery</summary><div data-type="detailsContent">This blog post explains how to use <code>whenLoaded()</code> and <code>when()</code> in Laravel API Resources to conditionally include data in API responses. <code>whenLoaded()</code> only includes related data if it's been eager-loaded, preventing performance issues, while <code>when()</code> offers a more general way to include data based on any condition, including dynamically added attributes. The post also highlights that <code>when(isset())</code> can be used as a fallback when <code>whenLoaded()</code> fails, especially when dealing with attributes added using <code>setAttribute()</code>.</div></details>

In Laravel, `whenLoaded()` is a useful method in API resources that ensures relationships are only included in the response if they have been eager-loaded. However, sometimes `whenLoaded()` may not work as expected. In this post, we'll explore **why that happens**, and how to fix it with a practical example of **a Post with Comments**.

---

## **1\. Setting Up Our Models**

### **Post Model (**`Post.php`)

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use HasFactory;

    protected $fillable = ['title', 'content'];

    public function comments()
    {
        return $this->hasMany(Comment::class);
    }
}
```

### **Comment Model (**`Comment.php`)

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    use HasFactory;

    protected $fillable = ['post_id', 'body'];

    public function post()
    {
        return $this->belongsTo(Post::class);
    }
}
```

---

## **2\. Creating the API Resource (**`PostResource.php`)

Now, let's create a resource to format our API response.

```php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class PostResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'content' => $this->content,
            // Using whenLoaded() to include comments only if they are eager-loaded
            'comments' => $this->whenLoaded('comments', fn() => CommentResource::collection($this->comments)),
            'created_at' => $this->created_at,
            'updated_at' => $this->updated_at,
        ];
    }
}
```

### **Comment Resource (**`CommentResource.php`)

```php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class CommentResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'body' => $this->body,
            'post_id' => $this->post_id,
            'created_at' => $this->created_at,
        ];
    }
}
```

---

## **3\. Controller with and without Eager Loading**

### **Using** `whenLoaded()` Correctly

To make `whenLoaded()` work, **we must eager-load the** `comments` relationship.

#### ✅ **Correct way:**

```php
use App\Models\Post;
use App\Http\Resources\PostResource;

public function show($id)
{
    $post = Post::with('comments')->findOrFail($id);
    return new PostResource($post);
}
```

### **Response (with eager loading)**

```json
{
    "id": 1,
    "title": "My First Post",
    "content": "This is the content of the post.",
    "comments": [
        {
            "id": 1,
            "body": "Great post!",
            "post_id": 1,
            "created_at": "2024-03-08T12:00:00.000000Z"
        }
    ],
    "created_at": "2024-03-08T11:00:00.000000Z",
    "updated_at": "2024-03-08T11:30:00.000000Z"
}
```

---

### **Using** `whenLoaded()` Incorrectly

If you forget to eager-load `comments`, the `whenLoaded()` method will return `null`.

#### ❌ **Incorrect way (no eager loading):**

```php
public function show($id)
{
    $post = Post::findOrFail($id); // No eager loading
    return new PostResource($post);
}
```

### **Response (without eager loading)**

```json
{
    "id": 1,
    "title": "My First Post",
    "content": "This is the content of the post.",
    "comments": null,
    "created_at": "2024-03-08T11:00:00.000000Z",
    "updated_at": "2024-03-08T11:30:00.000000Z"
}
```

---

## **4\. Alternative Using** `when()`

If `whenLoaded()` doesn’t work, you can use `when()` with `isset()` as a fallback.

```php
'comments' => $this->when(
    isset($this->comments),
    CommentResource::collection($this->comments)
),
```

This works even if the comments are not eager-loaded because it checks whether the property exists.

---

## **5\. Using setAttribute to Modify Attributes Dynamically**

We can dynamically add attributes to our models using the `setAttribute` method and conditionally include them in our API responses using the `when` method in API resources. Here's how we can implement this:

### Modifying the Model in the Controller

In our controller, after retrieving the `Post` model instance, we can use the `setAttribute` method to add a custom attribute. This attribute can then be conditionally included in the API resource response.

```php
use App\Models\Post;
use App\Http\Resources\PostResource;

public function show($id)
{
    // Retrieve the post along with its comments
    $post = Post::with('comments')->findOrFail($id);

    // Dynamically add a custom attribute to the model
    $post->setAttribute('custom_message', 'This is a dynamically added attribute');

    // Return the post resource
    return new PostResource($post);
}
```

### Updating the API Resource to Include the Custom Attribute

In our `PostResource`, we can use the `when` method to conditionally include the `custom_message` attribute in the response. The `when` method checks if the `custom_message` attribute exists and is not null before including it in the serialized output.

```php
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class PostResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'content' => $this->content,
            'comments' => CommentResource::collection($this->whenLoaded('comments')),
            'custom_message' => $this->when(
                $this->resource->getAttribute('custom_message') !== null,
                $this->resource->getAttribute('custom_message')
            ),
            'created_at' => $this->created_at,
            'updated_at' => $this->updated_at,
        ];
    }
}
```

In this example, the `when` method checks if the `custom_message` attribute is not null before including it in the response. This ensures that the attribute is only included when it has been set, providing flexibility in our API responses.

### Expected Response

When the `custom_message` attribute is set as shown above, the API response will include this attribute:

```json
{
    "id": 1,
    "title": "My First Post",
    "content": "This is the content of the post.",
    "comments": [
        {
            "id": 1,
            "body": "Great post!",
            "post_id": 1,
            "created_at": "2024-03-08T12:00:00.000000Z"
        }
    ],
    "custom_message": "This is a dynamically added attribute",
    "created_at": "2024-03-08T11:00:00.000000Z",
    "updated_at": "2024-03-08T11:30:00.000000Z"
}
```

If the `custom_message` attribute is not set, it will be excluded from the response, ensuring a clean and efficient API output.

By following this approach, we can dynamically add attributes to our models and conditionally include them in our API responses using Laravel's `setAttribute` and `when` methods.

---

## **6\. Summary**

| Scenario | `whenLoaded()` | `when(isset())` |
| --- | --- | --- |
| Relationship is eager-loaded (`with('comments')`) | ✅ Works | ✅ Works |
| Relationship is **not** eager-loaded | ❌ Returns `null` | ✅ Works |
| `setAttribute` is used | ❌ Not detected | ✅ Works |

---

## **7\. Best Practices**

1. **Always eager-load relationships** when using `whenLoaded()`.
    
2. **Use** `when(isset())` when working with dynamically added attributes.
    
3. **Prefer** `whenLoaded()` for performance when dealing with large datasets.
