Laravel5.5+ 使用API Resources快速输出自定义JSON方法详解


Posted in PHP onApril 06, 2020

从Laravel 5.5+开始,加入了API Resources这个概念。

我们先来看一下官网如何定义这个概念的:

When building an API, you may need a transformation layer that sits between your Eloquent models and the JSON responses that are actually returned to your application's users. Laravel's resource classes allow you to expressively and easily transform your models and model collections into JSON.

可能看完这个概念之后,你仍然有点不明白,毕竟这个定义说的有点含糊。

如果你熟悉使用API进行输出,构架前后端分离的网络应用,那么你应该会发现,当我们使用Eloquent从数据库中取出数据后,如果想以JSON格式进行输出,那么我们可以使用->toJson()这个方法,这个方法可以直接将我们的model序列化(这个方法从Laravel 5.1+开始就可以使用了):

$user = App\User::find(1);

return $user->toJson();

使用多了,我们会发现,在model较为复杂,或者model中有很多我们API输出可能用不到的字段的情况下,toJson()仍然会忠实地帮我们把这些字段序列化出来。

这个时候,我们会想,如何将model中的某些字段隐藏起来,不输出到JSON中。另外一种情况,比如字段是password等一些敏感信息的时候,我们不希望JSON数据里包含这样的敏感信息。

要解决这个问题,我们可以在model里定义$hidden或者$visible这两个数组来进行字段的隐藏或者显示:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
  /**
   * 不希望在序列化中出现的字段放入该数组中
   * 
   * @var array
   */
  protected $hidden = ['password', 'some', 'secret'];
}
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
  /**
   * 只有在以下数组中出现的字段会被序列化
   *
   * @var array
   */
  protected $visible = ['first_name', 'last_name'];
}

那么你可能会想,我们已经有了可以自动序列化的方法,以及可以隐藏或者显示指定字段的方法,这样不就足够了吗?

现在我们来假设一个简单的应用场景。假设我们在输出一个客户列表,里面包含了客户名字和送货地址。我们使用Customer这个model定义客户,使用ShippingAddress这个model进行定义送货地址。为了简化场景,我们的客户只有一个送货地址,所以只会出现一一对应的情况。

那么在ShippingAddress对应的数据库表shipping_addresses中,我们可能会有如下定义:

| id | country_id | province_id | city_id | address |

字段类型我就不赘述了,其中country_id、province_id以及city_id这三个外键分别对应了国家、省份以及城市表中的id。

而Customer对应的customers表中,会有shipping_address_id这个外键指向shipping_addresses表中的id。

那么我们要输出顾客和送货地址,我们需要先在model中定义好relationship:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Customer extends Model
{
  public function shippingAddress()
  {
    return $this->belongsTo(ShippingAddress::class);
  }
}
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class ShippingAddress extends Model
{
  public function country()
  {
    return $this->belongsTo(Country::class);
  }
  
  public function province()
  {
    return $this->belongsTo(Province::class);
  }
  
  public function city()
  {
    return $this->belongsTo(City::class);
  }
}

在我们的控制器中,我们拉取出所有客户:

<?php

namespace App\Http\Controllers;

use App\Customer;
use App\Http\Controllers\Controller;

class CustomerController extends Controller
{
  /**
   * Simple function to fetch all customers with their shipping addresses
   *
   * @return String
   */
  public function index()
  {
    $customers = Customer::with(['shippingAddress', 'shippingAddress.country', 'shippingAddress.province', 'shippingAddress.city'])->get();
    
    //这里可以直接返回Eloquent Collections或Objects,toJson()将自动被调用
    return $customers;
  }
}

那么输出的JSON将会包含了多个层级的关系,那么在我们前端调用的时候,将会非常麻烦,因为我们需要一层一层剥开Object关系。

但是如果你熟悉Laravel,你可能会说,慢着!这个情况我可以用accessor不就完事儿了吗?

是的,我们确实可以使用accessor来简化我们的数据层级:

/**
 * Get the customer's full shipping address
 *
 * @return string
 */
public function getFullShippingAddressAttribute()
{
  return "{$this->shippingAddress->country->name} {$this->shippingAddress->province->name} {$this->shippingAddress->city->name} {$this->shippingAddress->address}";
}

但是我们还需要一步操作。由于customers这张表本身没有full_shipping_address这个字段,要使我们的JSON输出包含full_shipping_address,我们需要添加$appends数组:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Customer extends Model
{
  /**
   * The accessors to append to the model's array form.
   *
   * @var array
   */
  protected $appends = ['full_shipping_address'];
}

对于每一个我们想自定义的JSON字段,我们都需要进行上面两部的操作。这样一来其实非常麻烦,并且不利于代码的维护,因为这会让原本简洁的model显得很复杂。

基于以上原因,我们需要一个中间层,在我们输出model成为JSON的时候,可以进行一次信息的过滤及加工。

那么还是使用我们上面的应用场景。要输出自定义的字段再简单不过了。我们不需要在model里定义各种accessor,也不需要使用黑白名单过滤字段,只需要新建一个Resource类:

$ php artisan make:resource Customer

然后我们可以看到,在app/Http文件夹下,多出了一个名为Resources文件夹下,其中含有一个名为Customer.php的文件:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class Customer extends JsonResource
{
  /**
   * Transform the resource into an array.
   *
   * @param \Illuminate\Http\Request $request
   * @return array
   */
  public function toArray($request)
  {
    return parent::toArray($request);
  }
}

这里我们看到其中有且仅有一个名为toArray的方法。这就是我们要自定字段的地方:

public function toArray($request)
  {
    return [
      'fullName' => $this->first_name . $this->last_name,
      'fullShippingAddress'  => $this->shippingAddress->country->name . $this->shippingAddress->province->name . $this->shippingAddress->city->name . $this->shippingAddress->address,
    ];
  }

注意到,无论是fullName还是fullShippingAddress,都是不存在于customers表中的字段。

接着,我们只需要简单修改一下我们的控制器:

<?php

namespace App\Http\Controllers;

use App\Customer;
use App\Http\Resources\Customer as CustomerResource;
use App\Http\Controllers\Controller;

class CustomerController extends Controller
{
  /**
   * Simple function to fetch all customers with their shipping addresses
   *
   * @return String
   */
  public function index()
  {
    $customers = Customer::with(['shippingAddress', 'shippingAddress.country', 'shippingAddress.province', 'shippingAddress.city'])->get();
    
    //这里我们使用了新的Resource类
    return CustomerResource::collection($customers);
  }
}

这样就OK了!我们输出的JSON数据中,将会仅仅含有以上两个字段,即fullName和fullShippingAddress,非常干净,并且前端直接可用,不需要二次再加工。

唯一需要注意的是,这里由于我们拉取了多个Customer,所以我们用了每个Resource类都自带有的collection方法,将一个Collection中的所有对象都进行处理。而若要处理单个对象,我们需要使用以下代码:

public function show($id)
{
  $customer = Customer::findOrFail($id);
  return new CustomerResource($customer);
}

要了解更多关于API Resources的详情,请戳官网文档:

https://laravel.com/docs/5.7/eloquent-resources

本文主要讲解了Laravel5.5+ 使用API Resources快速输出自定义JSON方法详解,更多关于Laravel框架的使用技巧请查看下面的相关链接

PHP 相关文章推荐
Discuz! Passport 通行证整合
Mar 27 PHP
php allow_url_include的应用和解释
Apr 22 PHP
PHP发明人谈MVC和网站设计架构 貌似他不支持php用mvc
Jun 04 PHP
带你了解PHP7 性能翻倍的关键
Nov 19 PHP
PHP创建word文档的方法(平台无关)
Mar 29 PHP
php车辆违章查询数据示例
Oct 14 PHP
详解PHP中foreach的用法和实例
Oct 25 PHP
PHP简单留言本功能实现代码
Jun 09 PHP
PHP空值检测函数与方法汇总
Nov 19 PHP
PHP实现正则表达式分组捕获操作示例
Feb 03 PHP
PHP操作redis实现的分页列表,新增,删除功能封装类与用法示例
Aug 04 PHP
Laravel学习笔记之Artisan命令生成自定义模板的方法
Nov 22 PHP
Laravel 5+ .env环境配置文件详解
Apr 06 #PHP
Laravel5.3+框架定义API路径取消CSRF保护方法详解
Apr 06 #PHP
Laravel框架使用技巧之使用url()全局函数返回前一个页面的地址方法详解
Apr 06 #PHP
使用git迁移Laravel项目至新开发环境的步骤详解
Apr 06 #PHP
Laravel框架数据库迁移操作实例详解
Apr 06 #PHP
Laravel框架中队列和工作(Queues、Jobs)操作实例详解
Apr 06 #PHP
Laravel实现批量更新多条数据
Apr 06 #PHP
You might like
PHP获取mysql数据表的字段名称和详细信息的方法
2014/09/27 PHP
php实现读取手机客户端浏览器的类
2015/01/09 PHP
js绑定事件this指向发生改变的问题解决方法
2013/04/23 Javascript
jquery购物车实时结算特效实现思路
2013/09/23 Javascript
JavaScript 数组详解
2013/10/10 Javascript
javascript随机之洗牌算法深入分析
2014/06/07 Javascript
javascript实现在网页任意处点左键弹出隐藏菜单的方法
2015/05/13 Javascript
javascript自定义in_array()函数实现方法
2015/08/03 Javascript
js图片轮播手动切换效果
2015/11/10 Javascript
javaScript给元素添加多个class的简单实现
2016/07/20 Javascript
AngularJS 表达式详细讲解及实例代码
2016/07/26 Javascript
微信小程序 form组件详解
2016/10/25 Javascript
js使用Replace结合正则替换重复出现的字符串功能示例
2016/12/27 Javascript
Angular下H5上传图片的方法(可多张上传)
2017/01/09 Javascript
vue.js源代码core scedule.js学习笔记
2017/07/03 Javascript
JS实现多物体运动的方法详解
2018/01/23 Javascript
JS/jQuery实现简单的开关灯效果【案例】
2019/02/19 jQuery
JSX在render函数中的应用详解
2019/09/04 Javascript
vue实现吸顶、锚点和滚动高亮按钮效果
2019/10/21 Javascript
Python变量作用范围实例分析
2015/07/07 Python
Python使用装饰器进行django开发实例代码
2018/02/06 Python
python实现彩色图转换成灰度图
2019/01/15 Python
python 将对象设置为可迭代的两种实现方法
2019/01/21 Python
检测python爬虫时是否代理ip伪装成功的方法
2019/07/12 Python
如何在python中实现线性回归
2020/08/10 Python
python合并多个excel文件的示例
2020/09/23 Python
Python在centos7.6上安装python3.9的详细教程(默认python版本为2.7.5)
2020/10/15 Python
移动端解决悬浮层(悬浮header、footer)会遮挡住内容的3种方法
2015/03/27 HTML / CSS
Europcar葡萄牙:葡萄牙汽车和货车租赁
2017/10/13 全球购物
德国传统玻璃制造商:Cristalica
2018/04/23 全球购物
三星法国官方网站:Samsung法国
2019/10/31 全球购物
怎样从/向数据文件读/写结构
2014/11/23 面试题
经典商业广告词
2014/03/13 职场文书
2016年社区中秋节活动总结
2016/04/05 职场文书
Win11安全功能升级:内置防网络钓鱼功能
2022/04/08 数码科技
python和Appium的移动端多设备自动化测试框架
2022/04/26 Python