发布时间2019-06-17 22:09:50
9
0
0

安装

~# composer require dingo/api

然后把配置文件 publish 出来

~# php artisan vendor:publish --provider="Dingo\Api\Provider\LaravelServiceProvider"

配置

API_STANDARDS_TREE=prs
API_SUBTYPE=blog
API_PREFIX=api
API_VERSION=v1
API_DEBUG=true

版本分组

为了避免和 Laravel 主应用的路由混合在一起,Dingo API 使用了自己的路由器,正因为如此,我们首先需要获取对应的 API 路由器来创建 Endpoint: 在 routes/api.php 中定义基于 DIngo 路由器的路由:

$api = app(\Dingo\Api\Routing\Router::class);
//定义API的版本分组
$api->version('v1',function($api){});
//也可以同时响应多个版本,第一个参数传递多个版本号数组
$api->version(['v1','v2'],function($api){});
//支持传递第二个参数,可以传递应用到该分组的中间件,命名空间,子域名,路由前缀等信息:
$api->version('v1',['niddleware'=>'foo'],function($api){});

//也可以嵌套分组
$api->version('v1',function($api){
    $api->group(['middleware'=>'foo'],function(){});
});

Dingo 中如果基于控制器方法定义 api 路由,需要指定完整的控制器命名空间

$api->get('tesk/{id}',\App\Http\Controllers\TaskController::class.'@show');

访问路由

请求头通过 Accept 字段指定 API 接口对应的响应实体格式及 API 版本信息,默认使用 v1 作为版本号,这个在配置里面可以修改;

Accept:application/API_STANDARDS_TREE.API_SUBTYPE.API_VERSION+json
  • API_STANDARDS_TREE 默认值为 x;
  • API_SUBTYPE 默认为空字符串; 指定 v2 版本的话可以写为:Accept => application/prs.blog.v3+json curl 命令行可以写为:
    curl -v -H "Accept: application/x..v1+json" http://todo.test/api/task/1

命名路由 & 生成 URL

$api->version('v1',function($api){
    $api->get('/tesk/{id}',function($id){
        return \App\Task::findOrFail($id);
    })->name('task.detail');
});
//调用时一定要添加一个版本号
$url = app(\Dingo\Api\Routing\UrlGenerator::class)
    ->version('v1')
    ->route('task.detail',['id'=>$id]);

响应

响应的格式多样,比如 JSON,XML,HTML,大多数是 JSON。 我们前面已经说过,返回 json ,最简单的,可以直接返回 数组 或者 对象,response 底层会自动转换成 json 格式,并设置 Content-Type 响应头为 application/json。

//Task 类继承自 Illuminate、Database\Eloquent\Model,意味着返回的是可以被格式化为数组的数据

use ...

class TaskController extends Controller
{
    public function index()
    {
        return Task::all();
    }
}

响应构建器

可以通过 Dingo 扩展包提供的响应构建器 构建不同形式的响应。 如果与 转换器( Transformer) 配合的话,还可以对响应数据按照需要进行格式化。

使用 响应构建器,控制器就需要引用 Dingo\Api\Routing\Helpers trait,我们可以将其放置在API 基类控制器 ApiController 中:

namespace App\Http\Controllers;

use Dingo\Api\Routing\Helpers;

class ApiContrller Extends Controller
{
    use Helpers;
}

创建子控制器 就可以通过 $this->response 属性访问 DingoAPI 响应构建器实例

通过响应构建器来构建 JSON 响应

调用构建器 array 方法

新创建一个 Dingo API Endpoint ,定义一个 task 资源路由,版本号设置为 v3;

$api->version('v3',function($api){
    $api->resource('articles',\App\Http\Controller\Api\ArticleController::class);
});
~# php artisan api:routes
+------+-----------+-------------------------+------------------+----------------------------------------------------+-----------+------------+----------+------------+
| Host | Method    | URI                     | Name             | Action                                             | Protected | Version(s) | Scope(s) | Rate Limit |
+------+-----------+-------------------------+------------------+----------------------------------------------------+-----------+------------+----------+------------+
|      | GET|HEAD  | /api/articles           | articles.index   | App\Http\Controllers\Api\ArticleController@index   | No        | v3         |          |            |
|      | POST      | /api/articles           | articles.store   | App\Http\Controllers\Api\ArticleController@store   | No        | v3         |          |            |
|      | GET|HEAD  | /api/articles/{article} | articles.show    | App\Http\Controllers\Api\ArticleController@show    | No        | v3         |          |            |
|      | PUT|PATCH | /api/articles/{article} | articles.update  | App\Http\Controllers\Api\ArticleController@update  | No        | v3         |          |            |
|      | DELETE    | /api/articles/{article} | articles.destroy | App\Http\Controllers\Api\ArticleController@destroy | No        | v3         |          |            |
+------+-----------+-------------------------+------------------+----------------------------------------------------+-----------+------------+----------+------------+
class ArticleController extends ApiController
{
    public function show($id)
    {
        $article = Article::findOrFail($id);
        return $this->response->array($article->toArray());
    }
}
//这种调用方式 和 直接返回 $article 实例一样,底层会转化为实例化 Illuminate\Http\Response 并将响应数据格式化为 json 数据。

构建器中使用转化器

转换器有点类似 Laravel 自带的 API 资源类。Dingo 转化器是基于 Fractal 实现的

//API 资源器
~# php artisan make:resource Article

通过转换器,可以实现将对象转化为 数组,并支持整型和布尔型之间的转化,以及分页嵌套关联。

Dingo Api 只是在 Fractal 基础上做了一层封装

单个资源响应

public function show()
{
    $article = Article::findOrFail();
    return $this->response->item($article,new ArticleTransformer());
}

关联模型

和 Fractal 一样,需要在 transformer 中添加 indlude* 以及修改 $availableIncludes 参数,在调用的时候 可以直接在 URL 地址中使用 include 参数,代码不需要任何更改。

https://blog.test/api/article/2?include=user

资源集合响应

public function index()
{
    $article = Article::all();
    return $this->response->collection($article,new ArticleTransformer());
}

collection: 第一个参数传入模型实例,第二个参数传入转化器实例

分页响应

public function index()
{
    $article = Article::paginate();
    return $this->response->paginator($article,new ArticleTransformer());
}

设置响应头

Dingo 提供的是链式接口,所以可以通过方法链的方式追加额外的信息

return $this->response->item($article,new ArticleTransformer())->withHeader('Foo','Bar');
//多个响应头,用withHeaders:
return $this->response->item($article,new ArticleTransformer())->withHeaders([
    'Foo'   =>  'Bar',
    'Hello' =>  'World'
]);

添加cookie

$cookie = new \Symfony\Component\HttpFoundation\Cookie('Foo','Bar');
return $this->response->item($article,new ArticleTransformer())->withCookie($cookie);

传入的 cookie 必须是一个 Symfony\Comoponent\HttpFoundation\Cookie 的实例

设置响应状态码

return $this->response->item($article,new ArticleTransformer())->setStatusCode(200);

添加元数据

某些转化层可能会使用 元数据( meta data ),在需要提供额外与资源关联的数据时很有用。

return $this->response->item($article,new ArticleTransformer())->addMeta('foo','bar');
//多个元数据,使用 setMeta
$meta = ['foo'=>'bar','hello'=>'world'];
return $this->response->item($article,new ArticleTransformer())->setMeta($meta);

转化器 高级功能

资源键

可以在 响应构建器 的 item、collection、paninator 方法传入一个参数数组作为第三个参数,在这个数组中,可以通过 key 传入 Fractal 资源的类型标识:

return $this->response->item($article,new ArticleTransformer(),['key'=>'article']);

这个功能只能在Fractal 的序列化器 为 JsonApiSerializer 时才有效

使用回调

Fractal 转化层允许你注册一个创建资源之后触发的回调,这个回调接受 League\Fractal\Resource\Item 或者 League\Fractal\Resource\Collection 资源作为第一个参数,League\Fractal\Manager 实例作为第二个参数,在这个回调函数中我们可以自定义序列化器,设置游标,以及其他与 Fractal 交互的功能

设置序列化器

默认序列化器为 DataArraySerializer。

//引入命名空间
use League\Fractal\Serializer\JsonApiSerializer;

...
$article = Article::findOrFail(1);
return $this->response
    ->item($article,new ArticleTransformer(),['key'=>'article'],function($resource,$fractal){
        $fractal->setSerializer(new JsonApiSerializer());
    })

同时也会用第三个参数设置的资源键值作为返回资源的数据类型

设置游标

public functionn index(Request $request)
{
    $current = $request->input('current');
    $previoue = $request->input('previous');
    $limit = $request->input('limit',10);

    if($current){
        $article = Article::where('id','>',$current)->take($limit)->get();
    }else{
        $article = Article::take($limit)->get();
    }

    $next = $article->last()->id;
    $cursor = new Cursor($current,$previous,$next,$article->count);

    return $this->response->collection($article,new ArticleTransformer(),[],function($resource,$fractal){
        $resource->setCursor($cursor);
    });
}

自定义转化层

Dingo Api 默认基于 Fractal 转化曾实现转化器。可以在 config/api.php中看到这个配置:

'transfromer'   =>  env('API_TRANSFORMER',Dingo\Api\Transformer\Adapter\Fractal::class),

如果不想使用 fractal 作为转化层,可以在 Dingo Api 中创建自己的转化层。需要创建一个继承自 Dingo\Api\Contract\Trandformer\Adapter 基类的子类,并实现 transform 方法:

use Dingo\Api\Http\Request;
use Dingo\Api\Transformer\Binding;
use Dingo\Api\Contract\Transformer\Adapter;

class MyCustomTransformerAdapter implements Adapter
{
    public function transform($response, $transformer,Binding $binding, Request $request)
    {
        //调用转化层对响应数据进行转化
    }
}

transform 方法的目的是 获取 $response ,然后和 $response 一起传递给转化层处理,转化层处理之后会返回一个数组,最终这个数组被 transform 方法返回。 $binding 参数用于支持更多特性,比如添加元数据或者允许开发者通过回调与转化层交互。 $request 参数 是当前的 HTTP 请求,当转化层需要查询字符串参数或者其他相关数据时会用到 $request。

自定义响应数据格式

默认情况下 Dingo API 返回的是Json 格式的数据,并且设置相应的 Content-Type头。 Dingo 支持 JSONP 格式,该格式会将相应封装到一个回调中。

//config/api.php
'defatultFormat'        =>  env('API_DEFAULT_FORMAT','json'),
'format'        =>  [
    'json'  =>  Dingo\Api\Http\Response\Format\Jsonp::class,
],

默认情况下回调参数查询字符串是 callback,可以通过修改 Jsonp 构造器的第一个参数来设置:

https://blog.test/api/article/1?callback=func

Morphing 和 Morphed 事件

在 Dingo API 发送响应前会对数据进行转化(morph),这个过程包括运行所有转化器以及 通过配置的响应数据格式发送响应。 如果需要控制响应数据如何被转化可以使用 Dingo 提供的 ResponseIsMorphing 和 ResponseWasMorphed 事件。

//在app/Listeners 目录下为上述时间创造监听器:
~# php artisan make:listener AddPaginationLinksToResponse
//app/Listeners/AddPaginationLinksToResponse.php
<?php

namespace App\Listeners;

use Dingo\Api\Event\ResponseWasMorphed;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;

class AddPaginationLinksToResponse
{
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Handle the event.
     *
     * @param  object  $event
     * @return void
     */
    public function handle(ResponseWasMorphed $event)
    {
        if (isset($event->content['meta']['pagination'])) {
            $links = $event->content['meta']['pagination']['links'];
            $next = isset($links['next']) ? $links['next'] : null;
            $previous = isset($links['previous']) ? $links['previous'] : null;
            $event->response->headers->set(
                'link',
                sprintf('<%s>; rel="next", <%s>; rel="prev"', $next, $previous)
            );
        }
    }
}
//然后通过在 EventServiceProvider 中注册事件及其对应监听器之间的映射关系来监听该事件:
use App\Listeners\AddPaginationLinksToResponse;
use Dingo\Api\Event\ResponseWasMorphed;

protected $listen = [
    ...
    ResponseWasMorphed::class => [
        AddPaginationLinksToResponse::class
    ]
];
//现在所有包含分页链接的响应也会将这些链接添加到 Link 响应头。修改 Api\TaskController 的 index 方法实现代码如下:
public function index(Request $request)
{
    $limit = $request->input('limit') ? : 10;
    $tasks = Task::paginate($limit);
    return $this->response->paginator($tasks, new TaskTransformer());
}
//在 Postman 中访问该方法对应路由,就可以看到相应结果: