appkr/api

Laravel或Lumen项目RESTful HTTP API开发工具

v3.0.4 2021-04-12 08:15 UTC

README

Latest Stable Version Total Downloads Latest Unstable Version License

韩语手册

索引

1. 关于

为Laravel或/和Lumen项目提供轻量级的RESTful API构建器。

2. 功能

  1. league/fractal提供Laravel/Lumen服务提供者。
  2. 为库提供配置功能。
  3. 提供制作转换/序列化API响应的简单方法。
  4. 提供make:transformer artisan命令。
  5. 提供示例,以便用户可以快速复制并粘贴到其项目中。

3. LARAVEL/LUMEN实现示例(如何使用)

3.1. API端点

以Laravel的方式定义RESTful资源路由。

<?php // app/Http/routes.php OR routes/web.php OR routes/api.php

Route::group(['prefix' => 'v1'], function () {
    Route::resource(
        'books',
        'BooksController',
        ['except' => ['create', 'edit']]
    );
});

Lumen不支持RESTful资源路由。您必须逐个定义它们。

<?php // app/Http/routes.php OR routes/web.php OR routes/api.php

$app->group(['prefix' => 'v1'], function ($app) {
    $app->get('books', [
        'as'   => 'v1.books.index',
        'uses' => 'BooksController@index',
    ]);
    $app->get('books/{id}', [
        'as'   => 'v1.books.show',
        'uses' => 'BooksController@show',
    ]);
    $app->post('books', [
        'as'   => 'v1.books.store',
        'uses' => 'BooksController@store',
    ]);
    $app->put('books/{id}, [
       'as'   => 'v1.books.update',
       'uses' => 'BooksController@update',
   ]);
    $app->delete('books/{id}', [
       'as'   => 'v1.books.destroy',
       'uses' => 'BooksController@destroy',
   ]);
});

3.2. 控制器

以下代码块是针对/v1/books/{id}端点的控制器逻辑。注意以下代码块中json()辅助函数和转换器的用法。

<?php // app/Http/Controllers/BooksController.php

namespace App\Http\Controllers\V1;

use App\Http\Controllers\Controller;
use App\Book;
use App\Transformers\BookTransformer;
use Illuminate\Http\Request;

class BooksController extends Controller
{
    public function index()
    {
        return json()->withPagination(
            Book::latest()->paginate(5),
            new BookTransformer
        );
    }

    public function store(Request $request)
    {
        // Assumes that validation is done at somewhere else
        return json()->created(
            $request->user()->create($request->all())
        );
    }

    public function show($id)
    {
        return json()->withItem(
            Book::findOrFail($id),
            new BookTransformer
        );
    }

    public function update(Request $request, $id)
    {
        $book = Book::findOrFail($id);

        return ($book->update($request->all()))
            ? json()->success('Updated')
            : json()->error('Failed to update');
    }

    public function destroy($id)
    {
        $book = Book::findOrFail($id);

        return ($book->delete())
            ? json()->success('Deleted')
            : json()->error('Failed to delete');
    }
}

4. 安装方法

4.1. Composer。

$ composer require "appkr/api: 1.*"

4.2. 添加服务提供者。

<?php // config/app.php (Laravel)

'providers' => [
    Appkr\Api\ApiServiceProvider::class,
];
<?php // boostrap/app.php (Lumen)

$app->register(Appkr\Api\ApiServiceProvider::class);

4.3. [可选]发布资产。

# Laravel only
$ php artisan vendor:publish --provider="Appkr\Api\ApiServiceProvider"

配置文件位于config/api.php

在Lumen中,我们可以手动创建config/api.php文件,然后在bootstrap/app.php中激活配置,如下所示。

<?php // bootstrap/app.php (Lumen)

$app->register(Appkr\Api\ApiServiceProvider::class);
$app->configure('api');

完成!

5. 配置

查看config/api.php,它具有内联注释。

6. 转换器

6.1. 什么?

有关转换器的更多信息,您可以做什么,以及为什么它是必需的,请参阅此页面。1个转换器对应1个模型是一个最佳实践(例如,为Book模型提供BookTransformer)。

6.2. 转换器模板生成器

幸运的是,这个包提供了一个 artisan 命令,可以方便地生成转换器类。

$ php artisan make:transformer {subject} {--includes=}
# e.g. php artisan make:transformer "App\Book" --includes="App\\User:author,App\\Comment:comments:true"
  • subject - 模型类的字符串名称。

  • includes - 与主题模型相关的子资源。通过提供此选项,您的API客户端可以控制响应体。请参阅嵌套子资源部分。

    选项的签名是--include=Model,eloquent_relationship_methods[,isCollection]

    如果可包含的子资源是集合类型,如示例中的BookComment关系,我们提供true作为选项的第三个值。

注意

在artisan命令中传递命名空间时,我们应该始终使用双反斜杠(\\),无需引号。

$ php artisan make:transformer App\\Book --includes=App\\User:author,App\\Comment:comments:true

生成的文件将如下所示

<?php // app/Transformers/BookTransformer.php

namespace App\Transformers;

use App\Book;
use Appkr\Api\TransformerAbstract;
use League\Fractal;
use League\Fractal\ParamBag;

class BookTransformer extends TransformerAbstract
{
    /**
     * List of resources possible to include using url query string.
     *
     * @var  array
     */
    protected $availableIncludes = [
        'author',
        'comments'
    ];

    /**
     * Transform single resource.
     *
     * @param  \App\Book $book
     * @return  array
     */
    public function transform(Book $book)
    {
        $payload = [
            'id' => (int) $book->id,
            // ...
            'created' => $book->created_at->toIso8601String(),
            'link' => [
                 'rel' => 'self',
                 'href' => route('api.v1.books.show', $book->id),
            ],
        ];

        return $this->buildPayload($payload);
    }

    /**
     * Include author.
     * This method is used, when an API client request /v1/books?include=author
     *
     * @param  \App\Book $book
     * @param \League\Fractal\ParamBag|null $params
     * @return  \League\Fractal\Resource\Item
     */
    public function includeAuthor(Book $book, ParamBag $params = null)
    {
        return $this->item(
            $book->author,
            new \App\Transformers\UserTransformer($params)
        );
    }

    /**
     * Include comments.
     * This method is used, when an API client request /v1/books??include=comments
     *
     * @param  \App\Book $book
     * @param  \League\Fractal\ParamBag|null $params
     * @return  \League\Fractal\Resource\Collection
     */
    public function includeComments(Book $book, ParamBag $params = null)
    {
        $transformer = new \App\Transformers\CommentTransformer($params);

        $comments = $book->comments()
            ->limit($transformer->getLimit())
            ->offset($transformer->getOffset())
            ->orderBy($transformer->getSortKey(), $transformer->getSortDirection())
            ->get();

        return $this->collection($comments, $transformer);
    }
}

7. 嵌套子资源

API客户端可以请求带有其子资源的资源。以下示例请求authors列表。同时,它还请求每个作者的books列表。它还具有其他参数,表示“我需要为这个作者按最新顺序获取总共3本书,没有任何跳过”。

GET /authors?include=books:limit(3|0):sort(id|desc)

当包含多个子资源时

GET /authors?include[]=books:limit(2|0)&include[]=comments:sort(id|asc)

# or alternatively

GET /authors?include=books:limit(2|0),comments:sort(id|asc)

在深度递归嵌套的情况下,使用点(.)。在以下示例中,我们假设发布者模型与somethingelse模型有关联。

GET /books?include=author,publisher.somethingelse

8. API

以下是Appkr\Api\Http\Response提供的完整响应方法列表。在控制器中制作json响应时非常实用。

8.1. Appkr\Api\Response - 可用方法

<?php

// Generic response.
// If valid callback parameter is provided, jsonp response can be provided.
// This is a very base method. All other responses are utilizing this.
respond(array $payload);

// Respond collection of resources
// If $transformer is not given as the second argument,
// this class does its best to transform the payload to a simple array
withCollection(
    \Illuminate\Database\Eloquent\Collection $collection,
    \League\Fractal\TransformerAbstract|null $transformer,
    string|null $resourceKey // for JsonApiSerializer only
);

// Respond single item
withItem(
    \Illuminate\Database\Eloquent\Model $model,
    \League\Fractal\TransformerAbstract|null $transformer,
    string|null $resourceKey // for JsonApiSerializer only
);

// Respond collection of resources with pagination
withPagination(
    \Illuminate\Contracts\Pagination\LengthAwarePaginator $paginator,
    \League\Fractal\TransformerAbstract|null $transformer,
    string|null $resourceKey // for JsonApiSerializer only
);

// Respond json formatted success message
// api.php provides configuration capability
success(string|array $message);

// Respond 201
// If an Eloquent model is given at an argument,
// the class tries its best to transform the model to a simple array
created(string|array|\Illuminate\Database\Eloquent\Model $primitive);

// Respond 204
noContent();

// Respond 304
notModified();

// Generic error response
// This is another base method. Every other error responses use this.
// If an instance of \Exception is given as an argument,
// this class does its best to properly format a message and status code
error(string|array|\Exception|null $message);

// Respond 401
// Note that this actually means unauthenticated
unauthorizedError(string|array|null $message);

// Respond 403
// Note that this actually means unauthorized
forbiddenError(string|array|null $message);

// Respond 404
notFoundError(string|array|null $message);

// Respond 405
notAllowedError(string|array|null $message);

// Respond 406
notAcceptableError(string|array|null $message);

// Respond 409
conflictError(string|array|null $message);

// Respond 410
goneError(string|array|null $message);

// Respond 422
unprocessableError(string|array|null $message);

// Respond 500
internalError(string|array|null $message);

// Set http status code
// This method is chain-able
setStatusCode(int $statusCode);

// Set http response header
// This method is chain-able
setHeaders(array $headers);

// Set additional meta data
// This method is chain-able
setMeta(array $meta);

8.2. Appkr\Api\TransformerAbstract - 可用方法

<?php

// We can apply this method against an instantiated transformer,
// to get the parsed query parameters that belongs only to the current resource.
//
// e.g. GET /v1/author?include[]=books:limit(2|0)&include[]=comments:sort(id|asc)
//      $transformer = new BookTransformer;
//      $transformer->get();
// Will produce all parsed parameters:
//      // [
//      //     'limit'  => 2 // if not given default value at config
//      //     'offset' => 0 // if not given default value at config
//      //     'sort'   => 'created_at' // if given, given value
//      //     'order'  => 'desc' // if given, given value
//      // ]
// Alternatively we can pass a key.
//      $transformer->get('limit');
// Will produce limit parameter:
//      // 2
get(string|null $key)

// Exactly does the same function as get.
// Was laid here, to enhance readability.
getParsedParams(string|null $key)

8.3. helpers.php - 可用函数

<?php

// Make JSON response
// Returns Appkr\Api\Http\Response object if no argument is given,
// from there you can chain any public apis that are listed above.
json(array|null $payload)

// Determine if the current framework is Laravel
is_laravel();

// Determine if the current framework is Lumen
is_lumen();

// Determine if the current request is for API endpoints, and expecting API response
is_api_request();

// Determine if the request is for update
is_update_request();

// Determine if the request is for delete
is_delete_request();

9. 包含示例

该包捆绑了一组遵循最佳实践的示例。它包括

  • 数据库迁移和播种器
  • 路由定义、Eloquent 模型和相应的控制器
  • FormRequest (仅 Laravel)
  • 转换器
  • 集成测试

按照指南激活和测试示例。

9.1. 激活示例

取消注释该行。

<?php // vendor/appkr/api/src/ApiServiceProvider.php

$this->publishExamples();

9.2. 迁移和播种表

执行以下操作以创建测试表并播种测试数据。强烈建议使用 SQLite,以避免污染您的数据库。

$ php artisan migrate --path="vendor/appkr/api/src/example/database/migrations" --database="sqlite"
$ php artisan db:seed --class="Appkr\Api\Example\DatabaseSeeder" --database="sqlite"

9.3. 查看效果

启动服务器。

$ php artisan serve

前往 GET /v1/books,您应该看到一个格式良好的 JSON 响应。尝试每个路由以熟悉,例如 /v1/books=include=authors/v1/authors=include=books:limit(2|0):order(id|desc)

9.4. [可选] 运行集成测试

# Laravel
$ vendor/bin/phpunit vendor/appkr/api/src/example/BookApiTestForLaravel.php
# Lumen
$ vendor/bin/phpunit vendor/appkr/api/src/example/BookApiTestForLumen.php

注意

如果您完成了示例的评估,请记住回滚迁移并在 ApiServiceProvider 中重新注释不必要的行。

10. 许可证 & 贡献

MIT 许可证。问题和 PR 总是受到欢迎。

11. 更新日志

v3.0.1

  • 支持 Laravel 5.5 的自动包发现(无需在 config/app.php 中添加 ServiceProvider)

v3.0.0

  • API 没有变化。
  • league/fractal 版本更新到 0.16.0

v2.3.4

  • 添加了 jsonEncodeOption 配置。

v2.3.3

  • 当包含查询参数无效时,将抛出 Appkr\Api\Http\UnexpectedIncludesParamException 而不是 UnexpectedValueException

v2.3.0

  • withCollection() 现在接受 Illuminate\Support\Collection
  • 修复了 SimpleArrayTransformer 中的错误。

v2.2.0

  • 字段名称根据配置转换为蛇形或驼峰式(config('api.convert.key'))。
  • 日期格式根据配置进行转换(config('api.convert.date'))。

v2.1.0

  • 添加了 TransformerAbstract::buildPayload 方法以过滤响应字段列表(向后兼容)。
  • Artisan 生成的转换器模板已更改(向后兼容)。

v2.0.0

  • TransformerAbstract 的 API 已更改。
  • 删除了通过查询字符串实现的分部分响应功能。相反,我们可以在转换器的 $visible$hidden 属性中显式设置要响应的属性列表。

v1.1.0 [有错误]

  • 添加了字段分组功能,以方便分部分响应。
  • 当 API 客户端传递的参数或值不可接受时,TransformerAbstract 现在抛出 UnexpectedValueException 而不是 Exception

12. 赞助商