ark4ne/laravel-json-api
Composer 安装命令:
composer require ark4ne/laravel-json-api
包简介
A Lightweight JSON:API Resource for Laravel
README 文档
README
A Lightweight {JSON:API} Resource for Laravel.
Installation
composer require ark4ne/laravel-json-api
Config
| Path | Type | Description |
|---|---|---|
describer.nullable |
bool |
For describer notation, defined if a value is nullable by default. |
describer.date |
string datetime format |
For describer notation, defined default date time format. |
describer.precision |
int \ null |
For describer notation, decimal precision for float value. null for disable rounding. |
describer.when-has |
bool \ string[] |
For describer notation, Apply automatically whenHas condition on attributes. |
relationship.when-included |
bool |
Allow to disabled by default the loading of relationship data. |
Usage
This package is a specialisation of Laravel's JsonResource class.
All the underlying API's are still there, thus in your controller you can still interact
with JsonApiResource classes as you would with the base JsonResource class
Request
This package allows the reading and dynamic inclusion of resources that will be requested in the requests via the "include" parameter.
@see {json:api} fetching-includes
Resource attributes will also be filtered according to the "fields" parameter.
@see {json:api} fetching-fields
You can also very simply validate your requests for a given resource via the rules Rules\Includes and Rules\Fields.
Include validation
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 will validate the include to exactly match the UserResource schema (determined by the relationships).
Fields validation
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 will validate the fields to exactly match the UserResource schema (determined by the attributes and relationships).
Customize validation message
| Trans key | default |
|---|---|
validation.custom.jsonapi.fields.invalid |
The selected :attribute is invalid. |
validation.custom.jsonapi.fields.invalid_fields |
":resource" doesn ' t have fields ":fields". |
validation.custom.jsonapi.fields.invalid_resource |
":resource" doesn ' t exists. |
validation.custom.jsonapi.includes.invalid |
The selected :attribute is invalid. |
validation.custom.jsonapi.includes.invalid_includes |
":include" doesn ' t have relationship ":relation". |
Resource
Implementable methods :
protected function toType(Request $request): string; protected function toIdentifier(Request $request): int|string; public function toAttributes(Request $request): iterable; protected function toRelationships(Request $request): iterable; protected function toResourceMeta(Request $request): ?iterable; protected function toMeta(Request $request): ?iterable;
Example:
use Ark4ne\JsonApi\Resources\JsonApiResource; use Illuminate\Http\Request; class UserResource extends JsonApiResource { public 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
Returns resource type.
protected function toType(Request $request): string { return 'user'; }
Default returns model class in kebab case : App\Models\MyPost => my-post
toIdentifier
@see {json:api} resource-identifier
Returns resource identifier.
protected function toIdentifier(Request $request): int|string { return $this->id; }
Default returns model id.
toAttributes
@see {json:api} resource-attributes
Returns resource attributes.
public function toAttributes(Request $request): iterable { return [ 'name' => $this->name, 'email' => $this->email, ]; }
Laravel conditional attributes
@see laravel: eloquent-conditional-attributes
Support laravel conditional attributes.
public 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, ]), ]; }
Described attributes
@see described notation
public 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
Returns resource relationships.
All relationships must be created with ModelResource::relationship.
This allows the generation of the schema representing the resource and thus the validation of request includes.
If your relation should have been a collection created via the ::collection(...) method, you can simply use ->asCollection().
If you want the relation data to be loaded only when it is present in the request include, you can use the ->whenIncluded() method.
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 must returns an array, keyed by string, of JsonApiResource or JsonApiCollection.
Laravel conditional relationships
@see laravel: eloquent-conditional-relationships
Support laravel conditional relationships.
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(), ]; }
Described attributes
@see described notation
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()), ]), ]; }
Relation links and meta
@see {json:api}: relation-linkage
@see {json:api}: relation-meta
Returns links and meta for a relation.
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
Returns resource links.
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
Returns resource 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
Returns document meta.
protected function toMeta(Request $request): ?iterable { return [ "copyright": "Copyright 2022 My Awesome Api", ]; }
Collection
@see laravel: resource-collection
Collection are implemented in JsonApiCollection.
Usage is the same as laravel collections.
UserResource::collection(User::all()); // => JsonApiCollection
Described notation
Value methods
| Method | Description |
|---|---|
bool |
Cast to boolean |
integer |
Cast to integer |
float |
Cast to float |
string |
Cast to string |
date |
Cast to date, allow to use custom format |
array |
Cast to array, supports typed arrays with ->of() |
arrayOf |
Helper method for typed arrays (alternative to array()->of()) |
mixed |
Don't cast, return as is |
enum |
Get enum value |
struct |
Custom struct. Accept an array of values |
Relation methods
| Method | Description |
|---|---|
one |
For relationship with a single value: HasOne, BelongsTo, ... |
many |
For relationship with many value: HasMany, BelongsToMany, ... |
Enum
Method enum allow to get enum value for backed enum or name for unit enum.
According to structure:
/// 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, ]; }
The following attributes resource:
// UserResource.php public function toAttributes(Request $request): array { return [ 'status' => $this->enum(), 'role' => $this->enum(), ]; }
Will return:
[
"status": 1,
"role": "ADMIN"
]
Typed Arrays
The array descriptor supports typed arrays to ensure all elements are cast to a specific type. This is useful when you need to guarantee type consistency across array elements.
Basic Usage
// UserResource.php public function toAttributes(Request $request): array { return [ // Array of strings - all values will be cast to string 'tags' => $this->array('tags')->of($this->string()), // Array of integers - all values will be cast to integer 'scores' => $this->array('scores')->of($this->integer()), // Array of floats 'prices' => $this->array('prices')->of($this->float()), // Array of booleans 'flags' => $this->array('flags')->of($this->bool()), ]; }
Using Class References
You can also use class references instead of descriptor instances:
use Ark4ne\JsonApi\Descriptors\Values\ValueString; use Ark4ne\JsonApi\Descriptors\Values\ValueInteger; public function toAttributes(Request $request): array { return [ 'tags' => $this->array('tags')->of(ValueString::class), 'scores' => $this->array('scores')->of(ValueInteger::class), ]; }
Alternative Syntax
You can also use the arrayOf() helper method:
public function toAttributes(Request $request): array { return [ 'tags' => $this->arrayOf($this->string(), 'tags'), 'scores' => $this->arrayOf($this->integer(), 'scores'), ]; }
Nested Typed Arrays
For multi-dimensional arrays, you can nest array()->of() calls:
public function toAttributes(Request $request): array { return [ // 2D array (matrix) of integers 'matrix' => $this->array('matrix')->of( $this->array()->of($this->integer()) ), ]; }
With Closures and Transformations
Combine typed arrays with closures for data transformation:
public function toAttributes(Request $request): array { return [ // Transform and type cast 'doubled' => $this->array(fn() => array_map(fn($n) => $n * 2, $this->numbers)) ->of($this->integer()), // Access nested properties 'user_ids' => $this->array(fn() => $this->users->pluck('id')) ->of($this->integer()), ]; }
With Conditions
Typed arrays support all conditional methods:
public function toAttributes(Request $request): array { return [ // Only include if not null 'tags' => $this->array('tags')->of($this->string())->whenNotNull(), // Only include if array is not empty 'scores' => $this->array('scores')->of($this->integer())->whenFilled(), // Conditional based on closure 'admin_notes' => $this->array('notes')->of($this->string()) ->when(fn() => $request->user()->isAdmin()), ]; }
⚠️ Important Note: Conditions applied to the item type (inside
of()) are not evaluated per-item. They apply to the entire array descriptor, not individual elements.// ❌ This will NOT filter individual items 'even-numbers' => $this->array('numbers')->of( $this->integer()->when(fn($request, $model, $attr) => $attr % 2 === 0) ) // All items will be included, the when() doesn't filter per item // ✅ To filter items, do it before passing to the array 'even-numbers' => $this->array( fn() => array_filter($this->numbers, fn($n) => $n % 2 === 0) )->of($this->integer())
Example
Given a model with mixed-type arrays:
$user = new User([ 'tags' => ['php', 'laravel', 123, true], 'scores' => [95.5, '87', 92, '78.9'], ]);
The resource will ensure type consistency:
public function toAttributes(Request $request): array { return [ 'tags' => $this->array('tags')->of($this->string()), 'scores' => $this->array('scores')->of($this->integer()), ]; }
Output:
{
"tags": ["php", "laravel", "123", "1"],
"scores": [95, 87, 92, 78]
}
ark4ne/laravel-json-api 适用场景与选型建议
ark4ne/laravel-json-api 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 21.62k 次下载、GitHub Stars 达 7, 最近一次更新时间为 2022 年 04 月 05 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「laravel」 「JSON-API」 「jsonapi.org」 「laravel-json」 「laravel-resource」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 ark4ne/laravel-json-api 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 ark4ne/laravel-json-api 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 ark4ne/laravel-json-api 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
PHP-code generator (based on OAS) for Laravel framework, with complete support of JSON-API data format
The jsonapi-querifier-bundle offers the Querifer filter syntax for the paknahad/jsonapi-bundle.
JSON API (jsonapi.org) support for Laravel applications.
Framework agnostic JSON API (jsonapi.org) implementation
JSON API (jsonapi.org) support for Laravel applications.
Laravel JSON:API integration for the tenantcloud/laravel-boolean-softdeletes package
统计信息
- 总下载量: 21.62k
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 7
- 点击次数: 13
- 依赖项目数: 1
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2022-04-05