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。需要转换的数据有两种格式,单个的资源,以及资源集合。
$this->response->item($foo, new FooTransformer)
;$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);
}
.
.
.
集合资源也可以很方便的转换,比如现在需要获取整个用户列表,增加一个接口 /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);
}
.
.
.
通常情况下对于一个集合数据都会使用分页查询,当然也就需要分页相关的数据,处理起来也很方便。
app/Http/Controllers/Api/AuthController.php
.
.
.
public function userIndex(UserTransformer $transformer)
{
$users = User::paginate();
return $this->response->paginator($users, $transformer);
}
.
.
.
再次访问你会发现多了一段 meta 的信息,其中的 pagination 就是分页数据。
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);
}
.
.
.
我们需要让数据中嵌套用户的数据,先来定义一下模型关系。
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
返回。
现在访问接口时增加 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);
});
.
.
.
有三种格式供我们选择:
测试一下 ArraySerializer:
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');
}
,
,
,
jsonapi 针对每个资源都会分配一个 key,理解这种结构需要多看一下子对应的文档,https://jsonapi.org/ ,总之如果你使用 fractal,就可以很方便的切换到这种结构上。
$ git add -A
$ git commit -m 'fractal'