ipunkt/laravel-json-api

此包已被弃用且不再维护。未建议替代包。

Laravel JSON Api 包

1.0.1 2017-09-07 12:52 UTC

This package is auto-updated.

Last update: 2023-06-23 04:31:27 UTC


README

Laravel JSON Api 包

Latest Stable Version Latest Unstable Version License Total Downloads

安装

composer require ipunkt/laravel-json-api

该包从 Laravel 5.5 开始支持包自动发现功能。对于旧版 Laravel,请遵循以下说明。

将服务提供者添加到 config/app.php

'providers' => [
	\Ipunkt\LaravelJsonApi\LaravelJsonApiServiceProvider::class,
]

将以下 Facades 添加到您的 config/app.php 文件中

'RelationshipFilterParser' => \Ipunkt\LaravelJsonApi\Services\RelationshipFilterParser\RelationshipFilterParserFacade::class,
'FilterApplier' => \Ipunkt\LaravelJsonApi\Services\FilterApplier\FilterApplierFacade::class,

中间件

我们提供了几个中间件 Ipunkt\LaravelJsonApi\Http\Middleware\ContentTypeGuardIpunkt\LaravelJsonApi\Http\Middleware\ETagMiddlewareIpunkt\LaravelJsonApi\Http\Middleware\GetUserFromToken

首先检查 content-typeaccept 头部是否具有正确的值且存在。其次,处理提供正确的 ETag 响应头部以支持您的缓存基础设施。最后,覆盖 JWT 认证中间件的错误响应。

app/Http/Kernel.php$routeMiddleware 部分中设置必要的中间件,如下所示

'jwt.auth' => \Ipunkt\LaravelJsonApi\Http\Middleware\GetUserFromToken::class,
'jwt.refresh' => \Tymon\JWTAuth\Middleware\RefreshToken::class,

'api-content-type' => \Ipunkt\LaravelJsonApi\Http\Middleware\ContentTypeGuard::class,
'etag' => \Ipunkt\LaravelJsonApi\Http\Middleware\ETagMiddleware::class,

请遵循此处的说明安装 tymon/auth 包。

对于 API 路由,我们需要名为 apisecure-api 的中间件组。这些可以配置如下

/**
 * The application's route middleware groups.
 *
 * @var array
 */
protected $middlewareGroups = [
	'api' => [
		'api-content-type',
		'etag',
	],

	'secure-api' => [
		'api-content-type',
		'etag',
	],
];

配置

默认情况下,该包自行配置所有路由。这是建议的选项。

您还可以配置 JSON API 响应。JSON API 1.0 标准中存在可选的响应元素。默认情况下,我们返回所有元素,但您可以选择关闭它们以节省响应字节数。

发布配置(可选步骤,但建议)

php artisan vendor:publish --provider="Ipunkt\LaravelJsonApi\LaravelJsonApiServiceProvider"

默认值部分

最大限制

默认情况下应返回多少结果(未设置 page[limit] 查询参数)。默认为 50

路由部分

配置

您想由包自身配置路由吗?在大多数情况下将其留为 true。

公开路由

该包默认包含公开和安全的路由。公开路由不检查认证。这是用于认证用户或公开可访问的 API 端点。

在此处您可以配置路由的 prefix 和处理请求的 controller

安全路由

安全路由会检查每个请求的认证。您需要 JWT 访问令牌才能访问这些资源。

在此处您可以配置路由的 prefix 和处理请求的 controller。您还可以定义 middleware。默认已配置名为 jwt.auth 的中间件。

响应部分

资源

资源可以有一个链接部分。是否应自动添加自链接?默认情况下会添加,但并非每个情况都需要。

资源项也可以有自链接。默认情况下,由包自动添加。如果您不需要,可以关闭它。

关系

关系和项目本身可以有一个链接部分,包含自链接和相关链接。这些都可以由包自动添加。如果您不需要,可以关闭它。

定义

设置

我们建议使用 app/Providers/AppServiceProvider 或创建自己的 ApiResourceServiceProvider

自定义您的 boot 方法,将 \Ipunkt\LaravelJsonApi\Resources\ResourceManager 作为参数进行类型提示,如下所示

public function boot(ResourceManager $resourceManager)

定义资源

JsonApiController 处理传入的 API 请求。它使用 ResourceManager 来了解所有定义的资源。因此,按如下方式定义您的资源

//  define in api version 1 a resource called 'posts'
$resourceManager->version(1)
    ->define('posts', function (ResourceDefinition $resource) {
        $resource->setRepository(PostRepository::class)
            ->setSerializer(PostSerializer::class);
    });

每个 API 有各种版本,至少有 1 个。

对于每个版本,您可以定义资源(URL /public/v1/posts),并且作为回调,您可以定义各种类型:至少需要一个用于获取资源模型的存储库和一个序列化器,将获取的模型数据转换为所需的格式。

此外,您可以定义一个自定义请求处理器。在那里,您可以自行处理整个请求,直到返回响应。这样,您就有完全的控制权。

Json Api 标准具有各种筛选选项。我们有一个筛选器工厂来支持这种类型的层。筛选器工厂处理给定的筛选器,并将其默认设置为存储库。因此,您可以通过属性筛选或通过请求参数在时间段内搜索。如果您想使用筛选器,您必须定义一个筛选器工厂。否则,您的筛选器将不会应用。

如果您想使用安全的 API 路由,您还必须设置请求处理器

//  define in api version 1 a resource called 'posts'
$resourceManager->version(1)
    ->define('posts', function (ResourceDefinition $resource) {
        $resource->setRepository(PostRepository::class)
            ->setSerializer(PostSerializer::class)
            ->setRequestHandler(PostRequestHandler::class);
    });

实现序列化器

class PostSerializer extends \Ipunkt\LaravelJsonApi\Serializers\Serializer
{
	/**
	 * resource type in response, can differ from requesting resource name
	 * @var string
	 */
	protected $type = 'posts';

	/**
     * returns links
     *
     * @param Model|Post $model
     * @return array
     */
    public function getLinks($model) : array
    {
        return [
            'comments' => 'https://localhost/api/v1/posts/1/comments',
        ];
    }

    /**
     * returns attributes for model
     *
     * @param Model|Post $model
     * @return array
     */
    protected function attributes($model) : array
    {
        return [
            'title' => $model->title,
            'slug' => str_slug($model->title),
            'content' => $model->content,
            'excerpt' => substr($model->content, 0, 200),
            'words' => count(explode(' ', $model->content)), //  example to show you can return more than only concrete model attributes
        ];
    }
}

实现存储库

遵循存储库模式,存储默认排序标准和参数请求到数据库字段名的映射。

class PostRepository extends \Ipunkt\LaravelJsonApi\Repositories\Repository
{
	/**
     * default sort criteria, when nothing given (can be empty)
     *
     * Format: 'fieldName' => 'asc', // or 'desc'
     *
     * @var array
     */
    protected $defaultSortCriterias = [
        'publish_datetime' => 'desc'
    ];

    /**
     * sort criterias (can be empty)
     *
     * Format: 'attributeNameInRequest' => 'field_name_in_database'
     * Example: 'date' in request will be 'publish_datetime' in sql query
     *
     * @var array
     */
    protected $sortCriterias = [
        'date' => 'publish_datetime',
    ];

    /**
     * constructor.
     * @param Model|Post $post
     * @param \Ipunkt\LaravelJsonApi\Repositories\Conditions\ConditionApplier $conditionApplier
     */
    public function __construct(Post $post, \Ipunkt\LaravelJsonApi\Repositories\Conditions\ConditionApplier $conditionApplier)
    {
        $this->model = $post;
        $this->conditionApplier = $conditionApplier;
    }
}

实现请求处理器

我们提供了一个请求处理器来处理检索请求(GET):DefaultRequestHandler

\Ipunkt\LaravelJsonApi\Contracts\RequestHandlers\NeedsAuthenticatedUser 接口控制您的资源可以通过安全路由访问。如果没有,您必须使用公共路由。

class PostRequestHandler extends \Ipunkt\LaravelJsonApi\Http\RequestHandlers\DefaultRequestHandler implements NeedsAuthenticatedUser
{

}

如果您需要更多的自由,请创建自己的请求处理器,并从 Ipunkt\LaravelJsonApi\Http\RequestHandlers\RequestHandler 继承。所有配置的操作都由命名空间 Ipunkt\LaravelJsonApi\Contracts\RequestHandlers 中的各种接口提供:HandlesCollectionRequestHandlesItemRequestHandlesRelationshipCollectionRequestHandlesRelationshipItemRequest 以及可修改的接口 HandlesPostRequestHandlesPatchRequestHandlesDeleteRequest 以及关系接口。请自行查看。

您可以通过在您自己的请求处理器中使用提供的特性之一来简化数据修改请求。这些特性可以在 Ipunkt\LaravelJsonApi\Http\RequestHandlers\Traits 下找到。请自行查看。

Laravel 中的错误处理

按照以下方式扩展您的 app/Exceptions/Handler.php

方法 render() 应该扩展以下代码

if ($request->expectsJson() ||
	$request->headers->contains('accept', ApiRequestHandler::CONTENT_TYPE)) {
	$error = new JsonApiError($exception->getMessage());

	if ($exception->getCode() > 100) {
		$error->setCode($exception->getCode());
	}

	if ($exception instanceof ModelNotFoundException || $exception instanceof NotFoundHttpException) {
		$error->setStatusCode(404)
			->setTitle('Resource not found');
	}

	if ($exception instanceof AuthorizationException) {
		$error->setStatusCode(403)
			->setTitle('Access forbidden');
	}

	if ($exception instanceof ValidationException) {
		$validationErrors = collect();
		foreach ($exception->validator->errors()->keys() as $key) {
			$validationErrors->push([
				'pointer' => $key,
				'message' => str_replace('attributes.', '', $exception->validator->errors()->first($key)),
			]);
		}
		$error->setSource($validationErrors);
	}

	if ($exception instanceof HttpExceptionInterface) {
		$error->setStatusCode($exception->getStatusCode());
	}

	if (app()->environment('local')) {
		$error->setException($exception);
	}

	return response()->json(['errors' => [$error]], $error->getStatusCode());
}

扩展您特殊的异常以匹配正确的代码和状态码。代码指的是异常代码,状态码是 HTTP 响应状态码。

方法 unauthenticated() 应该扩展以下代码

if ($request->expectsJson() ||
	$request->headers->contains('accept', ApiRequestHandler::CONTENT_TYPE)) {
	$error = new JsonApiError('Unauthenticated');
	$error->setStatusCode(401);

	return response()->json(['errors' => [$error]], $error->getStatusCode());
}

测试

我们在 tests/TestCase.php 中提供了一个 trait,用于添加 JSON API 测试的功能:\Ipunkt\LaravelJsonApi\Testing\ApiTestCaseTrait

此 TestCase trait 专为 Laravel 5.3 或 Laravel 5.4 BrowserKitTest 进行优化。

Laravel 5.4

由于 Laravel 5.4 有许多方便的方法来进行请求和断言,您只需要 Ipunkt\LaravelJsonApi\Testing\Concerns\PreparesRequestBody trait。只需将其添加到您的 Tests\TestCase 使用语句中。

PreparesRequestBody 提供了将模型转换为请求体的方法。

如果您想提供某种类型的登录或安全 API,那么您必须将 Ipunkt\LaravelJsonApi\Testing\Concerns\ModifiesRequestHeaders 添加到您的 Tests.TestCase 或您想存储令牌的地方。此 trait 提供了令牌存储和一个 ->headers() 方法来覆盖带有承载令牌的头部。

示例用户登录

如果您想创建用户登录以测试安全 API 路由,可以这样做

namespace Tests;

use App\User;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Ipunkt\LaravelJsonApi\Testing\Concerns\ModifiesRequestHeaders;
use Ipunkt\LaravelJsonApi\Testing\Concerns\PreparesRequestBody;

abstract class TestCase extends BaseTestCase
{
	use PreparesRequestBody,
		ModifiesRequestHeaders;
	use CreatesApplication;

	/**
	 * creates a user and logs him in
	 *
	 * @return User
	 */
	protected function createUserAndLogin(): User
	{
		$user = factory(\App\User::class)->create();

		$response = $this->postJson('/public/v1/tokens', $this->createRequestModel('credentials', [
			'email' => $user->email,
			'password' => 'secret',
		]), $this->headers());

		$json = $response->decodeResponseJson();
		$token = array_get($json, 'data.id');

		$this->setToken($token); // you are loggedin

		// you can now overwrite all requests headers with calling $this->headers().

		return $user;
	}
}

此代码在数据库中创建了一个新用户,获取了一个令牌(存储在响应 data.id 中)并将其设置为一个静态变量。之后,您可以在每个请求中使用 $this->headers() 方法来覆盖必要的头部。