ark4ne/laravel-json-api

为 Laravel 设计的轻量级 JSON:API 资源

v1.4.4 2024-08-07 14:02 UTC

README

为 Laravel 提供的轻量级 {JSON:API} 资源。

example branch parameter codecov

安装

composer require ark4ne/laravel-json-api

配置

使用

此包是 Laravel 的 JsonResource 类的专门化。所有底层的 API 仍然存在,因此您可以在控制器中像使用基类 JsonResource 一样与 JsonApiResource 类进行交互

请求

此包允许通过 "include" 参数读取和动态包含在请求中请求的资源。
@see {json:api} fetching-includes

资源属性也将根据 "fields" 参数进行过滤。
@see {json:api} fetching-fields

您还可以通过规则 Rules\IncludesRules\Fields 简单地验证给定资源的请求。

包含验证

use \Ark4ne\JsonApi\Requests\Rules\Includes;
use \Illuminate\Foundation\Http\FormRequest;

class UserFetchRequest extends FormRequest
{
    public function rules()
    {
        return [
            'include' => [new Includes(UserResource::class)],
        ]
    }
}

Rules\Includes 将验证 include 是否与用户资源模式(由关系确定)完全匹配。

字段验证

use \Ark4ne\JsonApi\Requests\Rules\Fields;
use \Illuminate\Foundation\Http\FormRequest;

class UserFetchRequest extends FormRequest
{
    public function rules()
    {
        return [
            'fields' => [new Fields(UserResource::class)],
        ]
    }
}

Rules\Fields 将验证 fields 是否与用户资源模式(由属性和关系确定)完全匹配。

自定义验证消息

资源

@see {json:api} resource-type

可实现的方法

protected function toType(Request $request): string;

protected function toIdentifier(Request $request): int|string;

protected function toAttributes(Request $request): iterable;

protected function toRelationships(Request $request): iterable;

protected function toResourceMeta(Request $request): ?iterable;

protected function toMeta(Request $request): ?iterable;

示例

use Ark4ne\JsonApi\Resources\JsonApiResource;
use Illuminate\Http\Request;

class UserResource extends JsonApiResource
{
    protected function toAttributes(Request $request): iterable
    {
        return [
            'name' => $this->name,
            'email' => $this->email,
        ];
    }

    protected function toResourceMeta(Request $request): ?iterable
    {
        return [
            'created_at' => $this->created_at->format(DateTimeInterface::ATOM),
            'updated_at' => $this->updated_at->format(DateTimeInterface::ATOM),
        ];
    }

    protected function toRelationships(Request $request): iterable
    {
        return [
            'posts' => PostResource::relationship(fn() => $this->posts, fn() => [
                'self' => "https://api.example.com/user/{$this->id}/relationships/posts",
                'related' => "https://api.example.com/user/{$this->id}/posts",
            ]),
            'comments' => CommentResource::relationship(fn() => $this->whenLoaded('comments')),
        ];
    }
}

toType

@see {json:api} resource-type

返回资源类型。

protected function toType(Request $request): string
{
    return 'user';
}

默认返回模型类在短横线格式中: App\Models\MyPost => my-post

toIdentifier

@see {json:api} resource-identifier

返回资源标识符。

protected function toIdentifier(Request $request): int|string
{
    return $this->id;
}

默认返回模型 id。

toAttributes

@see {json:api} resource-attributes

返回资源属性。

protected function toAttributes(Request $request): iterable
{
    return [
        'name' => $this->name,
        'email' => $this->email,
    ];
}

Laravel 条件属性

@see laravel: eloquent-conditional-attributes

支持 Laravel 条件属性。

protected function toAttributes(Request $request): array
{
    return [
        'name' => $this->name,
        'email' => $this->email,
        // with lazy evaluation
        'hash64' => fn() => base64_encode("{$this->id}-{$this->email}"),
        // Conditional attribute
        'secret' => $this->when($request->user()->isAdmin(), 'secret-value'),       
        // Merging Conditional Attributes
        // use applyWhen insteadof mergeWhen for keep fields
        // useful for fields request rules validation
        $this->applyWhen($request->user()->isAdmin(), [
            'first-secret' => 123,
            'second-secret' => 456.789,
        ]),
    ];
}

描述属性

@see 描述符号

protected function toAttributes(Request $request): array
{
    return [
        'name' => $this->string(),
        // pass key to describer
        $this->string('email'),
        // with lazy evaluation
        'hash64' => $this->string(fn() => base64_encode("{$this->id}-{$this->email}")),
        // Conditional attribute
        $this->string('secret')->when($request->user()->isAdmin(), 'secret-value'),       
        // Merging Conditional Attributes
        $this->applyWhen($request->user()->isAdmin(), [
            'first-secret' => $this->integer(fn() => 123),
            'second-secret' => $this->float(fn() => 456.789),
        ]),
    ];
}

toRelationships

@see {json:api} resources-relationships

返回资源关系。

所有关系 必须 使用 ModelResource::relationship 创建。这允许生成表示资源的模式,从而验证请求包含内容。

如果您的关联应该是通过 ::collection(...) 方法创建的集合,则可以使用 ->asCollection()

如果您希望仅在请求包含中存在时加载关联数据,则可以使用 ->whenIncluded() 方法。

protected function toRelationships(Request $request): array
{
    return [
        'avatar' => AvatarResource::relationship($this->avatar),
        // with conditional relationship
        'administrator' => $this->when($request->user()->isAdmin(), UserResource::relationship(fn() => $this->administrator),
        // as collection, with conditional value
        'comments' => CommentResource::relationship(fn() => $this->whenLoaded('comments'))->asCollection(),
        // with relationship (allow to include links and meta on relation)
        'posts' => PostResource::relationship(fn() => $this->posts)->withLinks(fn() => [
            'self' => "https://api.example.com/user/{$this->id}/relationships/posts",
            'related' => "https://api.example.com/user/{$this->id}/posts",
        ])->asCollection(),
    ];
}

toRelationships 必须返回一个数组,键为字符串,值为 JsonApiResourceJsonApiCollection

Laravel 条件关系

@see laravel: eloquent-conditional-relationships

支持Laravel条件关系。

protected function toRelationships(Request $request): array
{
    return [
        'avatar' => AvatarResource::relationship($this->avatar),
        // as collection, with condition
        'comments' => CommentResource::relationship(fn() => $this->whenLoaded('comments'))->asCollection(),
        // with relationship (allow to include links and meta on relation)
        'posts' => PostResource::relationship(fn() => $this->posts)
                ->asCollection(),
    ];
}

描述属性

@see 描述符号

protected function toRelationships(Request $request): array
{
    return [
        'avatar' => $this->one(AvatarResource::class),
        // custom relation name
        'my-avatar' => $this->one(AvatarResource::class, 'avatar'),
        // as collection, with condition
        'comments' => $this->many(CommentResource::class)
                           ->whenLoaded(),
        // with relationship (allow to include links and meta on relation)
        'posts' => $this->many(PostResource::class)
                ->links(fn() => [
                    'self' => "https://api.example.com/posts/{$this->resource->id}/relationships/posts",
                    'related' => "https://api.example.com/posts/{$this->resource->id}/posts",
                ])
                ->meta(fn() => [
                    'total' => $this->integer(fn() => $this->resource->posts()->count()),
                ]),
    ];
}

关系链接和元数据

@see {json:api}: relation-linkage
@see {json:api}: relation-meta

返回关系链接和元数据。

protected function toRelationships(Request $request): array
{
    return [
        'posts' => PostResource::relationship(fn() => $this->posts)->withLinks(fn() => [
            // links
            'self' => "https://api.example.com/user/{$this->id}/relationships/posts",
            'related' => "https://api.example.com/user/{$this->id}/posts",
        ])->withMeta(fn() => [
            // meta
            'creator' => $this->name,
        ])
        ->asCollection(),
    ];
}

toLinks

@see {json:api}: resource-linkage

返回资源链接。

protected function toLinks(Request $request): ?array
{
    return [
        'self' => route('api.user.show', ['id' => $this->id]),
    ];
}

toResourceMeta

@see {json:api}: resource-meta
@see {json:api}: document-meta

返回资源元数据。

protected function toResourceMeta(Request $request): ?iterable
{
    return [
        'created_at' => $this->created_at->format(DateTimeInterface::ATOM),
        'updated_at' => $this->updated_at->format(DateTimeInterface::ATOM),
    ];
}

toMeta

@see {json:api}: document-meta

返回文档元数据。

protected function toMeta(Request $request): ?iterable
{
    return [
        "copyright": "Copyright 2022 My Awesome Api",
    ];
}

集合

@see laravel: resource-collection

集合在JsonApiCollection中实现。

使用方式与Laravel集合相同。

UserResource::collection(User::all()); // => JsonApiCollection

描述性符号

值方法

关系方法

枚举

方法enum允许获取支持枚举的枚举值或单元枚举的名称。

根据结构

/// Role.php
enum Role {
    case ADMIN;
    case USER;
}
/// State.php
enum State:int {
    case ACTIVE = 1;
    case INACTIVE = 0;
}
/// User.php
class User extends Model
{
    $casts = [
        'role' => Role::class,
        'state' => State::class,
    ];
}

以下资源属性

// UserResource.php
protected function toAttributes(Request $request): array
{
    return [
        'status' => $this->enum(),
        'role' => $this->enum(),
    ];
}

将返回

[
    "status": 1,
    "role": "ADMIN"
]