Conditional attributes — when(), unless()
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
// 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);
}
}