0

Conditional attributes — when(), unless()

Intermediate5 min read·lv-24-004

Concept

Versioned API resources allow different API versions to return different response shapes without duplicating controller logic.

The problem: An API v1 returns {"name": "Alice Smith"}. API v2 returns {"first_name": "Alice", "last_name": "Smith"}. Mobile apps pinned to v1 break if v1's resource changes to v2's shape.

Approach 1: Versioned resource classes: app/Http/Resources/V1/UserResource.php and app/Http/Resources/V2/UserResource.php. The controller picks the right resource based on the version detected from the request.

Approach 2: Single resource with version condition: $this->when($this->apiVersion($request) === 1, ...) inside toArray(). Gets messy quickly — avoid for more than 2 versions.

Approach 3: Inheritance: V2\UserResource extends V1\UserResource. Override toArray() to return a modified array. Clean for incremental versions.

Version detection: From URL path (/api/v1/), route name, Accept header (application/vnd.app.v2+json), or query parameter. Store the detected version in the request.

Router-level version switching: Register route groups per version, each pointing to version-specific controllers or resources.

When to version: Version when you have clients you can't force to upgrade (mobile apps, third-party integrations). Internal APIs (same-team frontend) don't need versioning — just update together.

Code Example

php
<?php
// app/Http/Resources/V1/UserResource.php
namespace App\Http\Resources\V1;

use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource
{
    public function toArray(\Illuminate\Http\Request $request): array
    {
        return [
            'id'         => $this->id,
            'name'       => $this->name,              // V1: single name field
            'email'      => $this->email,
            'created_at' => $this->created_at->toISOString(),
        ];
    }
}

// app/Http/Resources/V2/UserResource.php
namespace App\Http\Resources\V2;

use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource
{
    public function toArray(\Illuminate\Http\Request $request): array
    {
        return [
            'id'         => $this->id,
            'first_name' => $this->first_name,        // V2: split name
            'last_name'  => $this->last_name,
            'email'      => $this->email,
            'avatar_url' => $this->avatar_url,        // V2: added fields
            'created_at' => $this->created_at->toISOString(),
        ];
    }
}

// Routes — separate version prefixes
Route::prefix('api/v1')->group(function() {
    Route::apiResource('users', \App\Http\Controllers\Api\V1\UserController::class);
});
Route::prefix('api/v2')->group(function() {
    Route::apiResource('users', \App\Http\Controllers\Api\V2\UserController::class);
});

// V1 Controller
namespace App\Http\Controllers\Api\V1;
class UserController extends Controller
{
    public function show(User $user): \App\Http\Resources\V1\UserResource
    {
        return new \App\Http\Resources\V1\UserResource($user);
    }
}

// V2 Controller inherits from V1, overrides resource
namespace App\Http\Controllers\Api\V2;
class UserController extends \App\Http\Controllers\Api\V1\UserController
{
    public function show(User $user): \App\Http\Resources\V2\UserResource
    {
        return new \App\Http\Resources\V2\UserResource($user);
    }
}