052. API 数据转换层—— league/fractal(配合 dingo/api)

API 数据转换层 —— league/fractal(配合 dingo/api)

league/fractal 是一个数据转换层,与 Laravel 5.5 新增的功能 eloquent-resources 做了同样的事情,就是把模型数据转换成我们希望的样子,放回给客户端,对于代码复用是一件非常好的事情。

当然也因为有了 eloquent-resources 你的数据转换层也多了一个选择,但是无论你用不用这个扩展包都可以了解一下,因为 fractal 也是一个老扩展包了,使用的人非常多,有的功能比 eloquent-resources 还要更好,可以深入了解一下。

这个扩展包并不是提供给 Laravel 的,所以如果你要单独的使用它,找到一个适合 Laravel 的封装,用起来会更加方便,当然了如果你使用了 dingo/api 那么 dingo 就是一个很好的封装,默认已经依赖了 fractal,前面两节课已经安装了 dingo ,所以已经可以直接开始使用了,并不需要额外的安装和配置。

使用

扩展包提供了一个数据转换层,将某个资源,转换成对应的数据,这样非常有利于代码复用,资源嵌套也变得特别方便,如果你了解 Laravel 的 eloquent-resources 那么应该能想明白。可以先看看 fractal 的 文档

Transformer 就是用来转换资源的类,每个模型可以对应一个 Transformer。需要转换的数据有两种格式,单个的资源,以及资源集合。

  • Fractal\Resource\Item —— 转换单个资源,经过 dingo 的整合直接使用 $this->response->item($foo, new FooTransformer)
  • Fractal\Resource\Collection—— 转换集合资源,经过 dingo 的整合直接使用 $this->response->collection($foos, new FooTransformer);。

我们下面通过一个例子了解一下。

单个数据转换

先来转换一下用户数据,上节课中有一个接口,用来获取当前用户的信息。首先就需要一个 UserTransformer

$ mkdir app/Transformers
$ touch app/Transformers/UserTransformer.php

创建一个 app/Transformers 专门用来存放 Transformer。

app/Transformers/UserTransformer.php

<?php

namespace App\Transformers;

use App\User;
use League\Fractal\TransformerAbstract;

class UserTransformer extends TransformerAbstract
{
    public function transform(User $user)
    {
        return [
            'id' => (int) $user->id,
            'name' => $user->name,
            'email' => $user->email,
            'created_at' => (string) $user->created_at,
            'updated_at' => (string) $user->updated_at,
        ];
    }
}

修改一下 Controller,我们通过 UserTransformer 来格式化一下用户数据:

app/Http/Controllers/Api/AuthController.php

use App\Transformers\UserTransformer;
.
.
.
    public function me(UserTransformer $transformer)
    {
        return $this->response->item(auth('api')->user(), $transformer);
    }
.
.
.

file

集合数据转换

集合资源也可以很方便的转换,比如现在需要获取整个用户列表,增加一个接口 /api/users

routes/api.php

.
.
.
    $api->get('users', 'AuthController@userIndex');
.
.
.

为了简单我们就都写在 AuthController 中了。

app/Http/Controllers/Api/AuthController.php

.
.
.
use App\User;
.
.
.
    public function userIndex(UserTransformer $transformer)
    {
        $users = User::all();
        return $this->response->collection($users, $transformer);
    }
.
.
.

file

分页数据转换

通常情况下对于一个集合数据都会使用分页查询,当然也就需要分页相关的数据,处理起来也很方便。

app/Http/Controllers/Api/AuthController.php

.
.
.
    public function userIndex(UserTransformer $transformer)
    {
        $users = User::paginate();
        return $this->response->paginator($users, $transformer);
    }
.
.
.

再次访问你会发现多了一段 meta 的信息,其中的 pagination 就是分页数据。

file

data ,meta 是我们常用的一种结构,data 中是资源的数据,meta 中存放资源之外的一些其他数据,例如分页等。

数据嵌套

资源有很多,不可能所有的资源你都单独请求,合理的数据嵌套可以让接口更加的好用,利用 fractal 其实处理起来很方便,还是以话题为例,每个话题都有发布人,那么话题数据中应该嵌套用户的数据。

创建一个话题的模型 Topic,同时创建 migration 和 factory。

$ php artisan make:model Topic -mf

database/migrations/< your_date >_create_topics_table.php

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateTopicsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('topics', function (Blueprint $table) {
            $table->increments('id');
            $table->unsignedInteger('user_id')->index();
            $table->string('title');
            $table->string('content');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('topics');
    }
}

执行 migrate 把 topics 表创建出来。

database/factories/TopicFactory.php

<?php

use Faker\Generator as Faker;

$factory->define(App\Topic::class, function (Faker $faker) {
    return [
        'title' => $faker->sentence(),
        'content' => $faker->text(),
        'user_id' => App\User::inRandomOrder()->first()->id,
    ];
});

打开 tinker 创建几个话题;

factory(App\Topic::class, 10)->create();

创建一个 TopicTransformer。

app/Transformers/TopicTransformer.php

<?php

namespace App\Transformers;

use App\Topic;
use League\Fractal\TransformerAbstract;

class TopicTransformer extends TransformerAbstract
{
    public function transform(Topic $topic)
    {
        return [
            'id' => (int) $topic->id,
            'user_id' => (int) $topic->user_id,
            'title' => $topic->title,
            'content' => $topic->content,
            'created_at' => (string) $topic->created_at,
            'updated_at' => (string) $topic->updated_at,
        ];
    }
}

routes/api.php

.
.
.
    $api->get('topics', 'AuthController@topicIndex');
.
.
.

app/Http/Controllers/Api/AuthController.php

.
.
.
use App\Topic;
use App\Transformers\TopicTransformer;

.
.
.
    public function topicIndex(TopicTransformer $transformer)
    {
        $topics = Topic::paginate();
        return $this->response->paginator($topics, $transformer);
    }
.
.
.

file

我们需要让数据中嵌套用户的数据,先来定义一下模型关系。

app/Topic.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Topic extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}
.
.
.
    protected $availableIncludes = ['user'];
.
.
.
    public function includeUser(Topic $topic)
    {
        return $this->item($topic->user, new UserTransformer());
    }
.
.
.

定义 TopicTransformer 允许 include 的数据为 user,同时定义一个 includeUser 方法,返回一个需要格式化的数据,如果是集合数据需要使用 $this->collection 返回。

file

现在访问接口时增加 include=user 就可以看到嵌套的数据了,可以把 availableIncludes 改成 defaultIncludes,那么久不需要增加参数,所有经过 TopicTransformer 转换的资源你都会自动增加用户数据。

转换成其他格式

默认情况下资源里数据总是以 data, meta 的结构返回 meta 可以不存在,所以数据会被放在 data 中,如果你想将单个资源的 data 去掉,只需要调整一下配置。

app/Providers/AppServiceProvider.php

.
.
.
    public function register()
    {
        app('Dingo\Api\Transformer\Factory')->setAdapter(function ($app) {
            $fractal = new \League\Fractal\Manager;
            // 自定义的和fractal提供的
            //$serializer = new \League\Fractal\Serializer\DataArraySerializer();
            $serializer = new \League\Fractal\Serializer\ArraySerializer();
            // $serializer = new \League\Fractal\Serializer\JsonApiSerializer();
            $fractal->setSerializer($serializer);
            return new \Dingo\Api\Transformer\Adapter\Fractal($fractal);
        });
.
.
.

有三种格式供我们选择:

  • DataArraySerializer —— 默认的格式,数据始终在 data 中;
  • ArraySerializer —— 单个资源的数据没有 data 包裹;
  • JsonApiSerializer —— jsonapi 结构。

测试一下 ArraySerializer:

file

JsonApiSerializer 需要是一种不同的结构,每个 Transformer 需要传入一个 key,修改一下代码:

app/Http/Controllers/Api/AuthController.php

.
.
.
    public function topicIndex(TopicTransformer $transformer)
    {
        $topics = Topic::paginate();
        return $this->response->paginator($topics, $transformer, ['key' => 'topic']);
    }
.
.
.

app/Transformers/TopicTransformer.php

.
.
.
    public function includeUser(Topic $topic)
    {
        return $this->item($topic->user, new UserTransformer(), 'user');
    }

,
,
,

file

jsonapi 针对每个资源都会分配一个 key,理解这种结构需要多看一下子对应的文档,https://jsonapi.org/ ,总之如果你使用 fractal,就可以很方便的切换到这种结构上。

代码版本控制

$ git add -A
$ git commit -m 'fractal'

转: https://learnku.com/courses/laravel-package/conversion-of-api-data-to-leaguefractal-using-dingoapi/2500

tags: laravel,dingo