tttptd/laravel-responder

这是一个用于API的Laravel包,它将Fractal库包装在一个优雅的Laravel API后面。

这个包的规范存储库似乎已经消失,因此该包已被冻结。


README

Latest Stable Version Packagist Downloads Software License Build Status Scrutinizer Code Quality

Laravel Responder

Laravel Responder是一个用于你的JSON API的包,将Fractal集成到Laravel和Lumen中。它可以转换你的Eloquent模型和序列化你的成功响应,但它也可以帮助你构建错误响应,处理异常,并集成测试你的API。

目录

哲学

在构建强大的API时,您想要确保您的端点对您的应用程序来说是一致的和易于使用的。Laravel非常适合您的API,但它缺乏对转换器和序列化器等常用工具的支持。另一方面,Fractal有一些非常好的工具用于构建API,并填补了Laravel的空白。

虽然Fractal解决了Laravel的许多不足,但将其集成到框架中通常有些繁琐。以控制器中的这个例子为例

 public function index()
 {
    $manager = new Manager();
    $resource = new Collection(User::get(), new UserTransformer(), 'users');

    return response()->json($manager->createData($resource)->toArray());
 }

我承认,Fractal管理器可以被移出控制器,并且您可以直接返回数组。然而,一旦您想要不同的状态码而不是默认的200,您可能仍然需要将其包装在response()->json()中。

重点是,我们都因为Laravel的魔法而有点被宠坏了。如果上面的内容可以写成以下这样,那岂不是很好

public function index()
{
    return responder()->success(User::all());
}

该包将在幕后调用Fractal,以自动转换和序列化数据。不再需要根据它是模型还是集合而实例化不同的Fractal资源,该包自动处理所有这些。

要求

此包需要

  • PHP 7.0+
  • Laravel 5.1+ 或 Lumen 5.1+

安装

通过Composer安装此包

composer require flugger/laravel-responder

Laravel

注册服务提供者

更新Composer后,将以下服务提供者添加到config/app.php中的providers

Flugg\Responder\ResponderServiceProvider::class

注册外观

如果您喜欢外观,也可以将Responder外观添加到aliases

'Responder' => Flugg\Responder\Facades\Responder::class

发布包资产

您还可以使用Artisan命令发布包配置和语言文件

php artisan vendor:publish --provider="Flugg\Responder\ResponderServiceProvider"

这将发布一个位于您的config文件夹中的responder.php配置文件。

它还会在您的lang/en文件夹中发布一个errors.php文件,用于存储您的错误消息。

卢门

注册服务提供商

通过在 app/bootstrap.php 中添加以下行来注册包服务提供商

$app->register(Flugg\Responder\ResponderServiceProvider::class);

注册门面

您还可以将以下行添加到 app/bootstrap.php 以注册可选门面

class_alias(Flugg\Responder\Facades\Responder::class, 'Responder');

请记住取消注释 $app->withFacades(); 以在卢门中启用门面。

发布包资产

卢门中没有 php artisan vendor:publish,因此如果您想配置该包,您必须自己创建 config/responder.php 文件。请注意,与 Laravel 不同,没有 resources/lang 文件夹。但是,您可以手动创建 resources/lang/en/errors.php,并且它将被包识别。

使用方法

该包有一个 Flugg\Responder\Responder 服务,该服务负责为您构建 API 的成功和错误响应。

访问响应器

在您开始制作 JSON 响应之前,您需要访问响应器服务。遵循良好的 Laravel 精神,您有多种方式来完成同一件事

选项 1:依赖注入

您可以将响应器服务直接注入到控制器中以创建成功响应

public function index(Responder $responder)
{
    return $responder->success(User::all());
}

您还可以创建错误响应

return $responder->error('invalid_user');

选项 2:门面

可选地,您可以使用 Responder 门面来创建响应

return Responder::success(User::all());
return Responder::error('invalid_user');

选项 3:辅助方法

此外,如果您喜欢 Laravel 的 response() 辅助方法,您可以使用 responder() 辅助方法

return responder()->success(User::all());
return responder()->error('invalid_user');

辅助方法和门面只是访问响应器服务的方式的不同,因此您可以使用相同的方法。

选项 4:特性

最后,该包还有一个您可以在基控制器中使用的 Flugg\Responder\Traits\RespondsWithJson 特性。

该特性为您提供了控制器中的 successResponse()errorResponse() 方法

return $this->successResponse(User::all());
return $this->errorResponse('invalid_user');

这些方法在幕后调用响应器服务。

如上所述,您可以用多种方式构建响应。您选择哪种方式取决于您,重要的是要保持一致性。为了简化,我们将使用门面继续文档的其余部分。

成功响应

响应器服务有一个 success() 方法,您可以使用它来快速生成成功的 JSON 响应

public function index()
{
    return Responder::success(User::all());
}

此方法返回一个 \Illuminate\Http\JsonResponse 实例,并在将其包装在 JSON 响应之前对其进行转换和序列化。

设置转换数据

success 方法的第一个参数是转换数据。如果设置了转换器,转换数据将被转换,并且必须是以下类型之一

Eloquent 模型

您可以传递模型作为转换数据

return Responder::success(User::first());
集合

您也可以传递模型集合

return Responder::success(User::all());
数组

您也可以传递模型数组

return Responder::success([User::find(1), User::find(2)]);

数组必须包含实际的模型实例,这意味着您不能使用 User::all()->toArray() 作为转换数据。

查询构建器

您可以直接传递查询构建器而不是将其转换为集合

return Responder::success(User::where('id', 1));

然后,该包将自动将分页信息添加到序列化器定义的响应中。

分页器

此外,您还可以通过传递分页器来限制项目数量

return Responder::success(User::paginate(5));

然后,该包将自动根据您使用的哪个 序列化器 将分页信息添加到响应中。

关系

您还可以传递 Eloquent 关系实例

return Responder::success(User::first()->roles());

包括关系

当使用 Fractal 时,您可以通过在管理器上调用 parseIncludes() 方法来包含关系,并在您的转换器中将可用的关系添加到 $availableIncludes 数组中。

使用 Laravel Responder,您不必做任何这些事情。它与 Eloquent 紧密集成,并自动解析模型加载的关系

return Responder::success(User::with('roles.permissions')->get());

您还可以让包自动解析并包括来自给定 GET 参数的关系,只需确保在配置中设置了 load_relations_from_parameter 键。

设置状态码

默认情况下,状态码设置为 200,但可以通过向 success() 方法添加第二个参数轻松更改。

return Responder::success(User::all(), 201);

有时您可能不希望返回任何内容。在这种情况下,您可以传递第一个参数为 null 或完全省略它。

return Responder::success(201);

添加元数据

您可能希望将额外的元数据传递到响应中,您可以通过添加第三个额外参数来实现。

return Responder::success(User::all(), 200, ['foo' => 'bar']);

如果您想发送默认的 200 响应,可以省略状态码。

return Responder::success(User::all(), ['foo' => 'bar']);

转换器

转换器负责将您的 Eloquent 模型转换为 API 的数组。转换器可以与模型关联,这意味着您的数据将自动转换,而无需指定转换器。

您可以在下面的几章中了解更多关于模型与转换器之间映射的信息。

转换数据

使用success()方法时,该包将尝试从转换数据中的模型解析转换器。如果没有找到转换器,将返回模型的toArray()字段。

如果您想明确指定要使用哪个转换器,可以在响应者服务上调用transform()方法。

return Responder::transform(User::all(), new UserTransformer)->respond();

除了使用完整的转换器类之外,您还可以传递一个闭包。

return Responder::transform(User::all(), function ($user) {
    return [
        'id' => (int) $user->id,
        'email' => (string) $user->email
    ];
})->respond();

如果没有传递转换器,它将像success()方法一样表现。

return Responder::transform(User::all())->respond();

success()方法不同,transform()方法返回一个Flugg\Responder\Http\SuccessResponseBuilder实例,这就是为什么我们要用respond()来链式调用,将其转换为Illuminate\Http\JsonResponse

您还可以使用respond()方法设置状态码或头信息。

return Responder::transform(User::all())->respond(201, ['x-foo' => 'bar']);

您还可以使用addMeta()方法添加任何元数据。

return Responder::transform(User::all())->addMeta(['foo' => 'bar'])->respond();

正如您所猜测的,Responder::success($data, $status, $meta)方法只是调用Responder::transform($data)->addMeta($meta)->respond($status)的快捷方式。

通过使用serializer()方法,您还可以显式设置序列化器。

return Responder::transform(User::all())->serializer(new JsonApiSerializer)->respond();

您还可以使用include()方法手动包含关系,这是Fractal自己的parseIncludes()方法的包装。

return Responder::transform(User::all())->include('roles.permissions')->toArray();

除了使用respond()之外,您还可以将其转换为其他几种类型。

return Responder::transform(User::all())->toArray();
return Responder::transform(User::all())->toCollection();
return Responder::transform(User::all())->toJson();

您还可以检索Fractal资源或管理器实例。

return Responder::transform(User::all())->getResource();
return Responder::transform(User::all())->getManager();

创建转换器

该包提供了一个Artisan命令,您可以使用它快速创建新的转换器。

php artisan make:transformer UserTransformer

这将创建一个位于app/Transformers文件夹中的新的UserTransformer.php

它将自动解析要注入的模型。例如,在上面的示例中,包将提取UserUserTransformer,并假设模型直接位于应用文件夹中(如Laravel的默认设置)。

如果您将模型存储在其他位置,您还可以使用--model选项指定模型路径。

php artisan make:transformer UserTransformer --model="App\Models\User"

您还可以使用--pivot选项包含一个额外的transformPivot()方法,用于转换模型的交叉表。

php artisan make:transformer UserTransformer --pivot

设置可用关系

就像您可以使用Fractal设置$availableIncludes一样,您在转换器上也有一个$relations属性。默认情况下,它将允许所有关系。

protected $relations = ['*'];

您还可以选择为转换器中的每个关系创建一个方法,如果您想根据解析的参数进行过滤。例如,如果您有一个roles包含,您可以创建一个roles()方法。

public function roles(User $user, ParamBag $paramBag)
{
    //
}

请注意,roles()方法接受一个User模型作为第一个参数,您可以在Fractal的文档中了解更多信息。

将转换器映射到模型

在许多情况下,您希望在每次引用模型时都使用相同的转换器。您不必为每个响应传递转换器,可以将转换器映射到模型,这样模型就会被自动转换。

要将转换器映射到模型,您的模型需要实现Flugg\Responder\Contracts\Transformable接口。该接口要求一个静态的transformer()方法,该方法应返回一个转换器。

class Role extends Model implements Transformable
{
    /**
     * The transformer used to transform the model data.
     *
     * @return Transformer|callable|string|null
     */
    public static function transformer()
    {
        return RoleTransformer::class;
    }
}

transformer()方法也可以返回一个闭包转换器。

public static function transformer()
{
    return function ($user) {
        return [
            'id' => (int) $user->id,
            'email' => (string) $user->email
        ];
    };
}

序列化器

在您的模型被转换之后,数据将使用设置的序列化器进行序列化。序列化器以某种方式结构化您的数据输出,但也可以添加额外的数据,如分页和元数据。

默认序列化器

该软件包自带默认序列化器,Flugg\Responder\Serializers\ApiSerailizer。以下是一个示例响应,假设有一个关联角色的用户

{
    "status": 200,
    "success": true,
    "data": {
        "id": 1,
        "email": "example@email.com",
        "role": {
            "name": "admin"
        }
    }
}

响应输出与Laravel的默认输出非常相似,除了它将数据包装在data字段中。它还包含一个success字段,以便快速告知用户请求是否成功。

status字段实际上不是默认序列化器的一部分,而是在数据序列化后由包添加的。您可以在配置文件中禁用此功能。

Fractal序列化器

如果默认序列化器不符合您的口味,您可以轻松将其替换为Fractal附带的三种序列化器之一。

ArraySerializer

使用League\Fractal\Serializers\ArraySerializer的上述示例将看起来如下所示

{
    "id": 1,
    "email": "example@email.com",
    "role": {
        "name": "admin"
    }
}
DataArraySerializer

您还可以使用League\Fractal\Serializers\DataArraySerializer添加data字段

{
    "data": {
        "id": 1,
        "email": "example@email.com",
        "role": {
            "data": {
                "name": "admin"
            }
        }
    }
}

请注意,在这种情况下,data字段也适用于每个关系,这与默认包序列化器不同。

JsonApiSerializer

Fractal还使用League\Fractal\Serializers\JsonApiSerializer来表示JSON-API标准

{
    "data": {
        "type": "users",
        "id": 1,
        "attributes": {
            "email": "example@email.com"
        },
        "relationships": {
            "role": {
                "data": {
                    "type": "roles",
                    "id": 1
                }
            }
        }
    },
    "included": {
        "role": {
            "type": "roles",
            "id": 1,
            "attributes": {
                "name": "admin"
            }
        }
    }
}

如您所见,它相当冗长,但确实有其用途。

自定义序列化器

如果上述任何序列化器都不符合您的口味,您可以自由创建自己的,并在配置文件中将serializer键设置为指向您的序列化器类。您可以在Fractal的文档中了解更多关于如何创建自己的序列化器的信息。

错误响应

就像成功响应一样,当事情没有按计划进行时,您同样可以轻松地生成错误响应,使用error()方法

public function index()
{
    return Responder::error();
}

就像成功响应一样,此方法返回一个\Illuminate\Http\JsonResponse实例,上面的示例将返回以下JSON

{
    "status": 500,
    "success": false,
    "error": null
}

设置错误代码

error()方法的第一个参数是错误代码,可以是任何字符串值

if (request()->has('bomb')) {
    return Responder::error('bomb_found');
}

上面的示例将包含一个带有设置错误代码的错误对象

{
    "status": 500,
    "success": false,
    "error": {
        "code": "bomb_found",
        "message": null
    }
}

设置状态代码

错误响应的默认状态代码为500。但是,您可以通过传入第二个参数来更改状态代码

return Responder::error('bomb_found', 400);

您也可以省略错误代码

return Responder::error(400);

设置错误消息

您可能还希望为响应提供更详细的错误消息。您可以通过向error()方法添加第三个参数来实现

return Responder::error('bomb_found', 500, 'No explosives allowed.');

这将输出以下JSON

{
    "status": 500,
    "success": false,
    "error": {
        "code": "bomb_found",
        "message": "No explosives allowed."
    }
}

您还可以选择在响应默认状态代码为500时省略第二个参数

return Responder::error('bomb_found', 'No explosives allowed.');

使用语言文件

您可能在多个地方返回相同的错误响应。与其为每个响应设置消息,您可以使用errors.php语言文件。在您发布供应商资源后,此文件应位于您的resources/lang/en文件夹中。

如果您使用Lumen,则需要手动创建resources/lang/en/errors.php文件。您可以简单地复制默认语言文件

语言文件包含以下默认错误消息

'resource_not_found' => 'The requested resource does not exist.',
'unauthenticated' => 'You are not authenticated for this request.',
'unauthorized' => 'You are not authorized for this request.',
'relation_not_found' => 'The requested relation does not exist.',
'validation_failed' => 'The given data failed to pass validation..',

这些消息用于Laravel的默认异常,该包可以捕获并将其转换为错误JSON响应。我们将在下一节异常中更详细地了解如何捕获这些异常。

让我们添加bomb_found错误代码并将其映射到相应的消息

'bomb_found' => 'No explosives allowed.',

然后您可以从错误响应中引用它

return Responder::error('bomb_found');

这将输出与上面相同的JSON,其中设置了错误消息

{
    "status": 500,
    "success": false,
    "error": {
        "code": "bomb_found",
        "message": "No explosives allowed."
    }
}

异常

当发生意外情况时,您可能更愿意抛出实际的异常,而不是使用error()方法。即使您不这样做,您可能也希望包捕获Laravel的默认异常,并自动将它们转换为JSON错误响应。

处理异常

如果您让包处理异常,该包将捕获所有扩展Flugg\Responder\Exceptions\Http\ApiException的异常并将它们转换为JSON响应。

要使用包异常处理器,您需要替换app/Exceptions/Handler.php中的以下行

use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;

使用包异常处理器

use Flugg\Responder\Exceptions\Handler as ExceptionHandler;

Lumen使用不同的基本异常处理器,与包异常处理器不兼容。您可以选择,简单地复制包异常处理器的内容,并将其粘贴到您的render()方法中,并使用Flugg\Responder\Traits\HandlesApiErrors特质。

捕获Laravel异常

当事情出错时,Laravel会抛出一些异常。例如,当使用findOrFail()方法找不到模型时,会抛出Illuminate\Database\Eloquent\ModelNotFoundException异常。如果您添加了包异常处理,这个异常和其他异常将被处理,如上段所述。

由于异常过于通用,从表单请求抛出的授权和验证异常无法自动捕获。但是,您可以在您的基请求类中使用Flugg\Responder\Traits\ThrowsApiErrors

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Flugg\Responder\Traits\ThrowsApiErrors;

abstract class Request extends FormRequest
{
    use ThrowsApiErrors;
}

此特质会抛出扩展Flugg\Responder\Exceptions\Http\ApiException的异常,因此它们会被包异常处理器捕获。

从Laravel 5.3开始,不再提供基请求类。您可以选择手动创建一个,或者在每个表单请求中使用特质。

创建自定义异常

包包含一些异常以处理Laravel的默认异常。但是,您可能希望为您的API创建自己的异常。如果您希望包自动将您的异常转换为JSON响应,它们需要扩展Flugg\Responder\Exceptions\Http\ApiException

<?php

namespace App\Exceptions;

use Flugg\Responder\Exceptions\Http\ApiException;

class CustomException extends ApiException
{
    /**
     * The HTTP status code.
     *
     * @var int
     */
    protected $statusCode = 400;

    /**
     * The error code used for API responses.
     *
     * @var string
     */
    protected $errorCode = 'custom_error';
}

您可以通过设置如上所示的$statusCode$errorCode属性来自定义从您的异常生成的响应。

测试助手

一旦开始转换数据,编写测试来测试数据就变得越来越困难。你可以使用类似Laravel的seeJsonseeJsonEquals的方法,然而,因为数据不会转换(或序列化),所以需要硬编码每个值。

该包提供了一个Flugg\Responder\Traits\MakesApiRequests特质,你可以在tests/TestCase.php文件中使用它,以获取访问一些辅助方法来轻松测试响应。

目前,只有在使用默认序列化器Flugg\Responder\Serializers\ApiSerializer时,成功响应方法才能正常工作。将来你可以使用所有序列化器进行测试。

断言成功响应

测试特质提供了一个seeSuccess()方法,你可以使用它来断言成功响应是成功的

$this->seeSuccess($user, 201);

这会将数据和序列化,就像响应器上的success()方法一样。它将对状态码运行seeStatusCode(),并断言响应具有正确的基结构并包含给定的数据。你也可以传递任何元数据作为第三个参数。

虽然上述方法只检查成功数据中的任何部分是否具有你指定的值,但你也可以断言精确匹配

$this->seeSuccessEquals($user, 201);

这和Laravel的seeJsonEquals几乎一样。

断言错误响应

就像你可以断言成功响应一样,你也可以使用seeError()方法验证你的应用程序是否发送了正确的错误响应

$this->seeError('invalid_user', 400);

这将检查状态码和错误响应结构。你也可以传递一个消息作为第三个参数。

获取成功数据

你也可以轻松地从响应中获取数据

$this->json('post', 'sessions', $credentials);
$data = $this->getSuccessData();

这将解码响应JSON,并将数据作为数组返回。

配置

如果你已经按照安装指南中所述发布了供应商资产,你将能够访问一个config/responder.php文件。你可以更改此文件中的值以更改包的运行方式。我们将详细介绍每个配置键。

序列化器类路径

此键表示用于生成成功JSON响应的序列化器类的完整类路径。你可以将其保留为默认的Flugg\Responder\Serializers\ApiSerializer,更改为Fractal的序列化器之一,或者创建一个自定义序列化器

包含状态码

该包将包括成功和错误响应的状态码。你可以通过将此键设置为false来禁用此功能。

贡献

贡献是受欢迎的,你可以在Github上自由创建一个pull request。你可以使用以下命令运行测试

vendor/bin/phpunit

如果你发现错误或对改进有建议,请自由在Github上提交一个问题。然而,如果这是一个安全问题,请发送电子邮件到flugged@gmail.com

许可协议

Laravel Responder 是一款免费软件,根据 MIT 许可协议进行分发。更多详细信息,请参阅 license.md