Laravel 13 API资源进阶深度解析
API资源是 Laravel 提供的一种优雅的方式来转换模型和模型集合为 JSON 格式。本文将深入探讨 Laravel 13 中 API资源的高级用法。
API资源基础
创建资源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource { public function toArray($request): array { return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, 'created_at' => $this->created_at->toIso8601String(), 'updated_at' => $this->updated_at->toIso8601String(), ]; } }
|
使用资源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <?php
namespace App\Http\Controllers;
use App\Http\Resources\UserResource; use App\Models\User;
class UserController extends Controller { public function show(int $id): UserResource { $user = User::findOrFail($id); return new UserResource($user); }
public function index(): \Illuminate\Http\Resources\Json\AnonymousResourceCollection { $users = User::paginate(15); return UserResource::collection($users); } }
|
条件属性
when() 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource { public function toArray($request): array { return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, 'is_admin' => $this->when( $this->isAdmin(), $this->role === 'admin' ), 'secret' => $this->when( $request->user()->isAdmin(), 'secret-value' ), 'subscription' => $this->when( $this->hasSubscription(), fn() => new SubscriptionResource($this->subscription) ), ]; } }
|
whenLoaded() 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource { public function toArray($request): array { return [ 'id' => $this->id, 'name' => $this->name, 'posts' => PostResource::collection( $this->whenLoaded('posts') ), 'comments' => CommentResource::collection( $this->whenLoaded('comments') ), 'profile' => new ProfileResource( $this->whenLoaded('profile') ), ]; } }
|
whenCounted() 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource { public function toArray($request): array { return [ 'id' => $this->id, 'name' => $this->name, 'posts_count' => $this->whenCounted('posts'), 'comments_count' => $this->whenCounted('comments'), 'followers_count' => $this->whenCounted('followers'), ]; } }
|
whenPivotLoaded() 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class BookResource extends JsonResource { public function toArray($request): array { return [ 'id' => $this->id, 'title' => $this->title, 'author' => new AuthorResource($this->author), 'pivot' => $this->whenPivotLoaded( 'book_user', fn() => [ 'rating' => $this->pivot->rating, 'read_at' => $this->pivot->read_at?->toIso8601String(), 'notes' => $this->pivot->notes, ] ), ]; } }
|
资源嵌套
嵌套资源示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class OrderResource extends JsonResource { public function toArray($request): array { return [ 'id' => $this->id, 'order_number' => $this->order_number, 'status' => $this->status, 'total' => $this->total, 'user' => new UserResource($this->whenLoaded('user')), 'items' => OrderItemResource::collection( $this->whenLoaded('items') ), 'shipping_address' => new AddressResource( $this->whenLoaded('shippingAddress') ), 'billing_address' => new AddressResource( $this->whenLoaded('billingAddress') ), 'payments' => PaymentResource::collection( $this->whenLoaded('payments') ), ]; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class OrderItemResource extends JsonResource { public function toArray($request): array { return [ 'id' => $this->id, 'quantity' => $this->quantity, 'unit_price' => $this->unit_price, 'total' => $this->total, 'product' => new ProductResource( $this->whenLoaded('product') ), ]; } }
|
资源集合
自定义集合资源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class UserCollection extends ResourceCollection { public function toArray($request): array { return [ 'data' => $this->collection, 'links' => [ 'self' => route('users.index'), 'first' => $this->url(1), 'last' => $this->url($this->lastPage()), 'prev' => $this->previousPageUrl(), 'next' => $this->nextPageUrl(), ], 'meta' => [ 'current_page' => $this->currentPage(), 'from' => $this->firstItem(), 'last_page' => $this->lastPage(), 'per_page' => $this->perPage(), 'to' => $this->lastItem(), 'total' => $this->total(), ], ]; } }
|
带统计的集合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class OrderCollection extends ResourceCollection { protected array $statistics;
public function __construct($resource, array $statistics = []) { parent::__construct($resource); $this->statistics = $statistics; }
public function toArray($request): array { return [ 'data' => $this->collection, 'statistics' => $this->statistics, 'meta' => [ 'total_orders' => $this->collection->count(), 'total_amount' => $this->collection->sum('total'), 'average_amount' => $this->collection->avg('total'), ], ]; } }
|
数据包装
自定义包装键
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource { public static $wrap = 'user';
public function toArray($request): array { return [ 'id' => $this->id, 'name' => $this->name, ]; } }
|
禁用包装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource { public static $wrap = null;
public function toArray($request): array { return [ 'id' => $this->id, 'name' => $this->name, ]; } }
|
全局禁用包装
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider; use Illuminate\Http\Resources\Json\JsonResource;
class AppServiceProvider extends ServiceProvider { public function boot(): void { JsonResource::withoutWrapping(); } }
|
附加响应数据
with() 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource { public function toArray($request): array { return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, ]; }
public function with($request): array { return [ 'meta' => [ 'api_version' => '1.0', 'generated_at' => now()->toIso8601String(), ], ]; } }
|
withResponse() 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\JsonResponse;
class UserResource extends JsonResource { public function toArray($request): array { return [ 'id' => $this->id, 'name' => $this->name, ]; }
public function withResponse($request, JsonResponse $response): void { $response->setStatusCode(200); $response->header('X-Resource-Type', 'User'); $response->header('X-Resource-Id', $this->id); } }
|
资源条件合并
merge() 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource { public function toArray($request): array { return [ 'id' => $this->id, 'name' => $this->name, $this->mergeWhen( $request->user()->isAdmin(), [ 'email' => $this->email, 'role' => $this->role, 'last_login' => $this->last_login?->toIso8601String(), ] ), ]; } }
|
mergeWhen() 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class ProductResource extends JsonResource { public function toArray($request): array { return [ 'id' => $this->id, 'name' => $this->name, 'price' => $this->price, $this->mergeWhen( $this->hasDiscount(), fn() => [ 'discount_price' => $this->discount_price, 'discount_percentage' => $this->discount_percentage, 'discount_ends_at' => $this->discount_ends_at?->toIso8601String(), ] ), ]; } }
|
资源继承
基础资源类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
abstract class BaseResource extends JsonResource { protected function formatTimestamp($timestamp): ?string { return $timestamp?->toIso8601String(); }
protected function formatPrice(float $price): string { return number_format($price, 2, '.', ''); }
protected function getCommonFields(): array { return [ 'id' => $this->id, 'created_at' => $this->formatTimestamp($this->created_at), 'updated_at' => $this->formatTimestamp($this->updated_at), ]; } }
|
继承资源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?php
namespace App\Http\Resources;
class ProductResource extends BaseResource { public function toArray($request): array { return array_merge($this->getCommonFields(), [ 'name' => $this->name, 'description' => $this->description, 'price' => $this->formatPrice($this->price), 'stock' => $this->stock, 'category' => new CategoryResource($this->whenLoaded('category')), ]); } }
|
分页资源
自定义分页响应
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class PaginatedCollection extends ResourceCollection { public function toArray($request): array { return [ 'data' => $this->collection, 'pagination' => [ 'total' => $this->total(), 'count' => $this->count(), 'per_page' => $this->perPage(), 'current_page' => $this->currentPage(), 'total_pages' => $this->lastPage(), ], 'links' => [ 'self' => $this->url($this->currentPage()), 'first' => $this->url(1), 'last' => $this->url($this->lastPage()), 'prev' => $this->previousPageUrl(), 'next' => $this->nextPageUrl(), ], ]; } }
|
测试资源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| <?php
namespace Tests\Unit\Resources;
use Tests\TestCase; use App\Http\Resources\UserResource; use App\Models\User;
class UserResourceTest extends TestCase { public function test_user_resource_structure(): void { $user = User::factory()->create([ 'name' => 'John Doe', 'email' => 'john@example.com', ]);
$resource = new UserResource($user); $array = $resource->toArray(request());
$this->assertEquals($user->id, $array['id']); $this->assertEquals('John Doe', $array['name']); $this->assertEquals('john@example.com', $array['email']); }
public function test_conditional_attributes(): void { $user = User::factory()->create(['role' => 'admin']);
$adminRequest = request()->setUserResolver(fn() => $user);
$resource = new UserResource($user); $array = $resource->toArray($adminRequest);
$this->assertArrayHasKey('is_admin', $array); } }
|
最佳实践
1. 保持资源简洁
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?php
class CleanUserResource extends JsonResource { public function toArray($request): array { return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, ]; } }
|
2. 使用关系加载
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?php
class OrderResource extends JsonResource { public function toArray($request): array { return [ 'id' => $this->id, 'user' => UserResource::make($this->whenLoaded('user')), 'items' => OrderItemResource::collection($this->whenLoaded('items')), ]; } }
|
3. 格式化数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?php
class FormattedResource extends JsonResource { protected function formatDate($date): string { return $date->format('Y-m-d H:i:s'); }
protected function formatMoney(float $amount): string { return '$' . number_format($amount, 2); } }
|
总结
Laravel 13 的 API资源提供了一种强大的方式来转换数据为 JSON 格式。通过合理使用资源类,可以创建一致、可维护的 API 响应格式,同时保持代码的清晰和可测试性。