serhiikamolov / laravel-jsonapi
一套接口和类,用于辅助使用Laravel框架构建高效的JSON:API应用程序。
Requires
- php: ^8.2
- ext-json: *
- illuminate/auth: ^11.0
- illuminate/database: ^11.1
- illuminate/http: ^11.1
- illuminate/pagination: ^11.0
- illuminate/routing: ^11.0
- illuminate/support: ^11.0
Requires (Dev)
- mockery/mockery: ^1.3
- phpunit/phpunit: ^10.0
This package is auto-updated.
Last update: 2024-09-26 19:03:13 UTC
README
一套接口和类,用于辅助使用Laravel框架构建高效的JSON:API应用程序。
目录
安装
通过Composer安装此包非常简单,只需在项目根目录下运行即可
composer require serhiikamolov/laravel-jsonapi
自定义错误处理器
为了使错误输出与json API格式兼容,请转到bootstrap/app.php
并注册自定义错误处理器。
$app->singleton( Illuminate\Contracts\Debug\ExceptionHandler::class, \JsonAPI\Exceptions\Handler::class );
或者直接从JsonAPI\Exceptions\Handler
类扩展默认的App\Exceptions\Handler
。
现在,在发生异常的情况下,您将得到以下响应
{ "links": { "self": "http://127.0.0.1/api/v1/auth/login" }, "errors": { "messages": [ "Some internal exception" ] }, "debug": { "message": "Some internal exception", "exception": "Exception", "file": "/code/app/Http/Controllers/AuthController.php", "line": 29, "trace": [...] } }
请求验证类
\JsonAPI\Contracts\Request
是FormRequest
类的一个简单扩展,它返回与json API格式兼容的验证错误
namespace App\Http\Requests\Auth; use \JsonAPI\Contracts\Request; class LoginRequest extends Request { public function messages() { return [ 'email.required' => 'Значення e-mail не може бути порожнім', 'email.email' => 'Значення e-mail не відповідає формату електронної пошти', 'email.max' => 'Значення e-mail не має бути таким довгим', ]; } /** * Get the validation rules that apply to the request. * * @return array */ public function rules():array { return [ 'email' => 'required|email|max:255', 'password' => 'required|string', ]; } }
具有请求验证的控制器示例
namespace App\Http\Controllers; use JsonAPI\Response\Response; use App\Http\Requests\Auth\LoginRequest; class AuthController extends Controller { /** * Request a JWT token * * @param LoginRequest $request * @param Response $response * @return JsonResponse */ public function login(LoginRequest $request, Response $response):Response { // validation is passed, you can check the user credentials now // and generate a JWT token } }
包含验证错误的响应
{ "links": { "self": "http://127.0.0.1/api/v1/auth/login" }, "errors": { "email": [ "The email field is required." ] } }
响应类
JsonAPI\Response\Response
是JsonResponse
类的一个扩展,具有一些附加方法。
向响应的links对象添加额外值
$response->links($array)
在响应中返回带有特定代码的错误
$response->error($statusCode, $message)
返回带有JWT令牌的响应
$response->token($token, $type = 'bearer', $expires = null)
在响应中返回自定义数据对象
$response->data($array)
将字段附加到响应的数据对象
$response->attach($key, $value)
向响应对象添加调试信息
$response->debug($array)
向响应对象添加元数据
$response->meta($array, $key = 'meta')
序列化Eloquent集合或数据模型
$response->serialize($collection, $serializer = new Serializer())
在响应中分页数据数组
$response->serialize($collection)->paginate()
向响应添加特定状态码
$response->code($statusCode)
Response
类实现了Builder模式,因此您可以连续使用不同的方法。
public function login(LoginRequest $request, Response $response): Response { ... return $response ->token((string)$token) ->attach('uuid', Auth::guard('api')->user()->uuid); }
响应结果
{ "links": { "self": "http://127.0.0.1/api/v1/auth/login" }, "data": { "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC8xMjcuMC4wLjFcL2FwaVwvdjFcL2F1dGhcL2xvZ2luIiwiaWF0IjoxNjA4Mzc0NTAyLCJleHAiOjE2MDgzNzQ1NjIsIm5iZiI6MTYwODM3NDUwMiwianRpIjoiUXRZWnpUeEhYajJyaGxHaCIsInN1YiI6MiwicHJ2IjoiY2U2ZTY0NDI4OTkwYjk4NWJjZTQ2OGZjNTlmZTUxYzNiZTljN2ZhZCJ9.PNF2-5nswNqaWS4WS1D_gemBD3IyPJVKXJohNF8mMUY", "token_type": "bearer", "expires_in": 60, "uuid": "40e831349e594d8e944478a243ff463f" } }
序列化类
序列化器允许将复杂的数据,如Eloquent集合和模型实例,转换为简单的数组,这些数组可以轻松地渲染为JSON。序列化器类为您提供了一种强大的、通用的方式来控制响应的输出。
声明序列化器
让我们创建一个简单的序列化器类,它扩展了JsonAPI\Response\Serializer
类。有一个公开的fields()
方法,它返回一个数组,并定义一组字段,这些字段将从给定的集合或模型中检索出来,并将其放入响应中。
namespace App\Http\Serializers; use JsonAPI\Response\Serializer; class UserSerializer extends Serializer { public function fields(): array { return [ 'id', // take data from $user->id 'name', // take data from $user->name 'email', // take data from $user->email 'uuid' // take data from the public method defined below ]; } /** * Define a custom field */ public function uuid(Model $item): string { return md5($item->id); } }
您会注意到,这里的uuid
不是从数据库中获取的,而是从模型数据中生成的。这样,您可以定义响应中需要的新字段,甚至可以覆盖现有字段的值。
使用模型序列化器
在JsonAPI\Response\Response
类中有一个serialize
方法,它接受一个序列化器实例作为第二个参数。
class UsersController extends Controller { /** * Get list of all users. * * @param Response $response * @return Response */ public function read(Response $response): Response { $users = User::all(); return $response->serialize($users, new UserSerializer()); } }
响应结果
{ "links": { "self": "http://127.0.0.1/api/v1/users" }, "data": [ { "id": 1, "name": "admin", "email": "user@email.com", "uuid": "40e831349e594d8e944478a243ff463f" } ] }
使用字段修饰符
字段修饰符可以应用于序列化器中定义的每个字段。尽管如此,还有一些预定义的修饰符:timestamp
、number
、trim
,您可以通过创建一个具有modifier
前缀的受保护方法来定义自己的修饰符。此外,您还可以使用另一个序列化类作为修饰符,这当您有一些与原始模型相关的相关数据时非常有用。
class UserSerializer extends Serializer { public function fields(): array { return [ 'id' => 'md5' // use custom modifier ... 'created_at' => 'timestamp', // use default modifier which // transforms a Carbon date object // into the unix timestamp number 'roles' => RoleSerializer::class // use a serializing class as a modifier // for the related data ]; } /** * Define custom modifier which transforms user id to md5 hash. * @param int|null $value * @return int */ protected function modifierMd5(?int $value): string { return md5($value); } }
使用查询日志扩展响应
启用 JsonAPI\Http\Middleware\JsonApiDebug
中间件,并将查询日志的信息扩展到响应的 debug
部分。
namespace App\Http; use Illuminate\Foundation\Http\Kernel as HttpKernel; class Kernel extends HttpKernel { protected $middlewareGroups = [ ... 'api' => [ ... \JsonAPI\Http\Middleware\JsonApiDebug::class ], ... ]; }
包含查询日志的响应。
{ ... "debug": { "queries": { "total": 2, "list": [ { "query": "select * from `users` where `id` = ? limit 1", "bindings": [ 2 ], "time": 20.81 }, { "query": "select `roles`.*, `role_user`.`user_id` as `pivot_user_id`, `role_user`.`role_id` as `pivot_role_id`, `role_user`.`created_at` as `pivot_created_at`, `role_user`.`updated_at` as `pivot_updated_at` from `roles` inner join `role_user` on `roles`.`id` = `role_user`.`role_id` where `role_user`.`user_id` = ? and `roles`.`deleted_at` is null", "bindings": [ 2 ], "time": 73.97 } ] } } }
测试API响应
将 JsonAPI\Traits\Tests\JsonApiAsserts
特性添加到您的默认 TestCase
类中,并使用一些有用的断言方法扩展您的测试以测试API响应。
namespace Tests; use Illuminate\Foundation\Testing\TestCase as BaseTestCase; use JsonAPI\Traits\Tests\JsonApiAsserts; abstract class TestCase extends BaseTestCase { use JsonApiAsserts; }
使用附加断言的示例
/** * Testing GET /api/v1/entries/<id> */ public function test_read() { $response = $this->get("/api/v1/entries/1"); // expecting to get response in JSON:API format and // find "id", "value", "type", "active" fields within // a response's data $this->assertJsonApiResponse($response, [ "id", "value", "type", "active", ]); }