api-resource-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

API Resource Patterns

API Resource 模式

Basic Resource Structure

基础资源结构

php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class PostResource extends JsonResource
{
    public function toArray($request): array
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'content' => $this->content,
            'created_at' => $this->created_at->toISOString(),
            'updated_at' => $this->updated_at->toISOString(),
        ];
    }
}
php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class PostResource extends JsonResource
{
    public function toArray($request): array
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'content' => $this->content,
            'created_at' => $this->created_at->toISOString(),
            'updated_at' => $this->updated_at->toISOString(),
        ];
    }
}

Conditional Attributes

条件属性

php
public function toArray($request): array
{
    return [
        'id' => $this->id,
        'title' => $this->title,
        
        // Only include if loaded
        'author' => new UserResource($this->whenLoaded('user')),
        
        // Only include if condition is true
        'content' => $this->when($request->user()?->can('view', $this->resource), $this->content),
        
        // Only include if not null
        'comments_count' => $this->when($this->comments_count !== null, $this->comments_count),
        
        // Merge conditionally
        $this->mergeWhen($request->user()?->isAdmin(), [
            'internal_notes' => $this->internal_notes,
        ]),
    ];
}
php
public function toArray($request): array
{
    return [
        'id' => $this->id,
        'title' => $this->title,
        
        // Only include if loaded
        'author' => new UserResource($this->whenLoaded('user')),
        
        // Only include if condition is true
        'content' => $this->when($request->user()?->can('view', $this->resource), $this->content),
        
        // Only include if not null
        'comments_count' => $this->when($this->comments_count !== null, $this->comments_count),
        
        // Merge conditionally
        $this->mergeWhen($request->user()?->isAdmin(), [
            'internal_notes' => $this->internal_notes,
        ]),
    ];
}

Nested Relationships

嵌套关联

php
public function toArray($request): array
{
    return [
        'id' => $this->id,
        'title' => $this->title,
        
        // Single relationship
        'author' => new UserResource($this->whenLoaded('user')),
        
        // Collection relationship
        'comments' => CommentResource::collection($this->whenLoaded('comments')),
        
        // Nested relationships
        'category' => new CategoryResource($this->whenLoaded('category')),
    ];
}
php
public function toArray($request): array
{
    return [
        'id' => $this->id,
        'title' => $this->title,
        
        // Single relationship
        'author' => new UserResource($this->whenLoaded('user')),
        
        // Collection relationship
        'comments' => CommentResource::collection($this->whenLoaded('comments')),
        
        // Nested relationships
        'category' => new CategoryResource($this->whenLoaded('category')),
    ];
}

Resource Collections

资源集合

php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\ResourceCollection;

class PostCollection extends ResourceCollection
{
    public function toArray($request): array
    {
        return [
            'data' => $this->collection,
            'meta' => [
                'total' => $this->total(),
                'count' => $this->count(),
                'per_page' => $this->perPage(),
                'current_page' => $this->currentPage(),
                'total_pages' => $this->lastPage(),
            ],
            'links' => [
                'self' => $request->url(),
                'first' => $this->url(1),
                'last' => $this->url($this->lastPage()),
                'prev' => $this->previousPageUrl(),
                'next' => $this->nextPageUrl(),
            ],
        ];
    }
}
php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\ResourceCollection;

class PostCollection extends ResourceCollection
{
    public function toArray($request): array
    {
        return [
            'data' => $this->collection,
            'meta' => [
                'total' => $this->total(),
                'count' => $this->count(),
                'per_page' => $this->perPage(),
                'current_page' => $this->currentPage(),
                'total_pages' => $this->lastPage(),
            ],
            'links' => [
                'self' => $request->url(),
                'first' => $this->url(1),
                'last' => $this->url($this->lastPage()),
                'prev' => $this->previousPageUrl(),
                'next' => $this->nextPageUrl(),
            ],
        ];
    }
}

Adding Links

添加链接

php
public function toArray($request): array
{
    return [
        'id' => $this->id,
        'title' => $this->title,
        'links' => [
            'self' => route('posts.show', $this->id),
            'author' => route('users.show', $this->user_id),
            'comments' => route('posts.comments.index', $this->id),
        ],
    ];
}
php
public function toArray($request): array
{
    return [
        'id' => $this->id,
        'title' => $this->title,
        'links' => [
            'self' => route('posts.show', $this->id),
            'author' => route('users.show', $this->user_id),
            'comments' => route('posts.comments.index', $this->id),
        ],
    ];
}

Resource Response Customization

资源响应自定义

php
// In controller
public function store(Request $request)
{
    $post = Post::create($request->validated());
    
    return (new PostResource($post))
        ->response()
        ->setStatusCode(201)
        ->header('Location', route('posts.show', $post));
}
php
// In controller
public function store(Request $request)
{
    $post = Post::create($request->validated());
    
    return (new PostResource($post))
        ->response()
        ->setStatusCode(201)
        ->header('Location', route('posts.show', $post));
}

Pivot Data in Resources

资源中的中间表数据

php
public function toArray($request): array
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'assigned_at' => $this->whenPivotLoaded('role_user', function () {
            return $this->pivot->created_at;
        }),
        'expires_at' => $this->whenPivotLoadedAs('assignment', 'role_user', function () {
            return $this->assignment->expires_at;
        }),
    ];
}
php
public function toArray($request): array
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'assigned_at' => $this->whenPivotLoaded('role_user', function () {
            return $this->pivot->created_at;
        }),
        'expires_at' => $this->whenPivotLoadedAs('assignment', 'role_user', function () {
            return $this->assignment->expires_at;
        }),
    ];
}

Wrapping and Unwrapping

包裹与取消包裹

php
// Disable wrapping in AppServiceProvider
use Illuminate\Http\Resources\Json\JsonResource;

public function boot()
{
    JsonResource::withoutWrapping();
}

// Or per resource
public static $wrap = 'post';
php
// Disable wrapping in AppServiceProvider
use Illuminate\Http\Resources\Json\JsonResource;

public function boot()
{
    JsonResource::withoutWrapping();
}

// Or per resource
public static $wrap = 'post';

With Additional Data

添加额外数据

php
public function with($request): array
{
    return [
        'version' => '1.0.0',
        'timestamp' => now()->toISOString(),
    ];
}

public function withResponse($request, $response)
{
    $response->header('X-Value', 'True');
}
php
public function with($request): array
{
    return [
        'version' => '1.0.0',
        'timestamp' => now()->toISOString(),
    ];
}

public function withResponse($request, $response)
{
    $response->header('X-Value', 'True');
}

Best Practices

最佳实践

Always Use whenLoaded for Relationships

始终为关联使用whenLoaded

php
// ✅ Prevents N+1 queries
'author' => new UserResource($this->whenLoaded('user')),

// ❌ Will cause N+1 queries
'author' => new UserResource($this->user),
php
// ✅ Prevents N+1 queries
'author' => new UserResource($this->whenLoaded('user')),

// ❌ Will cause N+1 queries
'author' => new UserResource($this->user),

Use Type Hints

使用类型提示

php
use Illuminate\Http\Request;

public function toArray(Request $request): array
{
    // ...
}
php
use Illuminate\Http\Request;

public function toArray(Request $request): array
{
    // ...
}

Keep Resources Focused

保持资源专注

php
// ✅ Create separate resources for different contexts
class PostResource extends JsonResource { }
class PostListResource extends JsonResource { }
class PostDetailResource extends JsonResource { }

// ❌ Don't make one resource do everything
php
// ✅ Create separate resources for different contexts
class PostResource extends JsonResource { }
class PostListResource extends JsonResource { }
class PostDetailResource extends JsonResource { }

// ❌ Don't make one resource do everything

Use Resource Collections

使用资源集合

php
// ✅ Use collection class
return new PostCollection(Post::paginate());

// ✅ Or collection method
return PostResource::collection(Post::all());
php
// ✅ Use collection class
return new PostCollection(Post::paginate());

// ✅ Or collection method
return PostResource::collection(Post::all());

Controller Usage

控制器使用示例

php
class PostController extends Controller
{
    public function index()
    {
        $posts = Post::with(['user', 'category'])
            ->withCount('comments')
            ->paginate(15);
        
        return new PostCollection($posts);
    }
    
    public function show(Post $post)
    {
        $post->load(['user', 'comments.user', 'tags']);
        
        return new PostResource($post);
    }
    
    public function store(StorePostRequest $request)
    {
        $post = Post::create($request->validated());
        
        return (new PostResource($post))
            ->response()
            ->setStatusCode(201);
    }
}
php
class PostController extends Controller
{
    public function index()
    {
        $posts = Post::with(['user', 'category'])
            ->withCount('comments')
            ->paginate(15);
        
        return new PostCollection($posts);
    }
    
    public function show(Post $post)
    {
        $post->load(['user', 'comments.user', 'tags']);
        
        return new PostResource($post);
    }
    
    public function store(StorePostRequest $request)
    {
        $post = Post::create($request->validated());
        
        return (new PostResource($post))
            ->response()
            ->setStatusCode(201);
    }
}

Checklist

检查清单

  • Resources transform models consistently
  • Relationships loaded with whenLoaded()
  • Conditional attributes use when()
  • Collections include pagination metadata
  • Links included for HATEOAS
  • Type hints used
  • Proper HTTP status codes
  • No N+1 queries
  • Consistent date formatting
  • Appropriate wrapping strategy
  • 资源一致地转换模型
  • 关联使用whenLoaded()加载
  • 条件属性使用when()
  • 集合包含分页元数据
  • 包含HATEOAS链接
  • 使用类型提示
  • 正确的HTTP状态码
  • 无N+1查询问题
  • 一致的日期格式
  • 合适的包裹策略