首页 > 编程 > JavaScript > 正文

深入浅析JSONAPI在PHP中的应用

2019-11-19 14:39:33
字体:
来源:转载
供稿:网友

现在服务端程序员的主要工作已经不再是套模版,而是编写基于 JSON 的 API 接口。可惜大家编写接口的风格往往迥异,这就给系统集成带来了很多不必要的沟通成本,如果你有类似的困扰,那么不妨关注一下 JSONAPI ,它是一个基于 JSON 构建 API 的规范标准,一个简单的 API 接口大致如下所示:

JSONAPI

简单说明一下:根节点中的 data 用来放置主对象的内容,其中 type 和 id 是必须要有的字段,用来表示主对象的类型和标识,其它简单的属性统统放置到 attributes 里,如果主对象存在一对一、一对多等关联对象,那么放置到 relationships 里,不过只是通过 type 和 id 字段放置一个链接,关联对象的实际内容统统放置在根接点中的 included 里。

有了 JSONAPI,数据解析的过程变得规范起来,节省了不必要的沟通成本。不过如果要手动构建 JSONAPI 数据还是很麻烦的,好在通过使用 Fractal 可以让实现过程相对自动化一些,上面的例子如果用 Fractal 实现大概是这个样子:

<?phpuse League/Fractal/Manager;use League/Fractal/Resource/Collection;$articles = [  [    'id' => 1,    'title' => 'JSON API paints my bikeshed!',    'body' => 'The shortest article. Ever.',    'author' => [      'id' => 42,      'name' => 'John',    ],  ],];$manager = new Manager();$resource = new Collection($articles, new ArticleTransformer());$manager->parseIncludes('author');$manager->createData($resource)->toArray();?>

如果让我选最喜爱的 PHP 工具包,Fractal 一定榜上有名,它隐藏了实现细节,让使用者完全不必了解 JSONAPI 协议即可上手。不过如果你想在自己的项目里使用的话,与直接使用 Fractal 相比,可以试试 Fractalistic ,它对 Fractal 进行了封装,使其更好用:

<?phpFractal::create()  ->collection($articles)  ->transformWith(new ArticleTransformer())  ->includeAuthor()  ->toArray();?>

如果你是裸写 PHP 的话,那么 Fractalistic 基本就是最佳选择了,不过如果你使用了一些全栈框架的话,那么 Fractalistic 可能还不够优雅,因为它无法和框架本身已有的功能更完美的融合,以 Lavaral 为例,它本身内置了一个 API Resources 功能,在此基础上我实现了一个 JsonApiSerializer,可以和框架完美融合,代码如下:

<?phpnamespace App/Http/Serializers;use Illuminate/Http/Resources/MissingValue;use Illuminate/Http/Resources/Json/Resource;use Illuminate/Http/Resources/Json/ResourceCollection;use Illuminate/Pagination/AbstractPaginator;class JsonApiSerializer implements /JsonSerializable{  protected $resource;  protected $resourceValue;  protected $data = [];  protected static $included = [];  public function __construct($resource, $resourceValue)  {    $this->resource = $resource;    $this->resourceValue = $resourceValue;  }  public function jsonSerialize()  {    foreach ($this->resourceValue as $key => $value) {      if ($value instanceof Resource) {        $this->serializeResource($key, $value);      } else {        $this->serializeNonResource($key, $value);      }    }    if (!$this->isRootResource()) {      return $this->data;    }    $result = [      'data' => $this->data,    ];    if (static::$included) {      $result['included'] = static::$included;    }    if (!$this->resource->resource instanceof AbstractPaginator) {      return $result;    }    $paginated = $this->resource->resource->toArray();    $result['links'] = $this->links($paginated);    $result['meta'] = $this->meta($paginated);    return $result;  }  protected function serializeResource($key, $value, $type = null)  {    if ($type === null) {      $type = $key;    }    if ($value->resource instanceof MissingValue) {      return;    }    if ($value instanceof ResourceCollection) {      foreach ($value as $k => $v) {        $this->serializeResource($k, $v, $type);      }    } elseif (is_string($type)) {      $included = $value->resolve();      $data = [        'type' => $included['type'],        'id' => $included['id'],      ];      if (is_int($key)) {        $this->data['relationships'][$type]['data'][] = $data;      } else {        $this->data['relationships'][$type]['data'] = $data;      }      static::$included[] = $included;    } else {      $this->data[] = $value->resolve();    }  }  protected function serializeNonResource($key, $value)  {    switch ($key) {      case 'id':        $value = (string)$value;      case 'type':      case 'links':        $this->data[$key] = $value;        break;      default:        $this->data['attributes'][$key] = $value;    }  }  protected function links($paginated)  {    return [      'first' => $paginated['first_page_url'] ?? null,      'last' => $paginated['last_page_url'] ?? null,      'prev' => $paginated['prev_page_url'] ?? null,      'next' => $paginated['next_page_url'] ?? null,    ];  }  protected function meta($paginated)  {    return [      'current_page' => $paginated['current_page'] ?? null,      'from' => $paginated['from'] ?? null,      'last_page' => $paginated['last_page'] ?? null,      'per_page' => $paginated['per_page'] ?? null,      'to' => $paginated['to'] ?? null,      'total' => $paginated['total'] ?? null,    ];  }  protected function isRootResource()  {    return isset($this->resource->isRoot) && $this->resource->isRoot;  }}?>

对应的 Resource 基本还和以前一样,只是返回值改了一下:

<?phpnamespace App/Http/Resources;use App/Article;use Illuminate/Http/Resources/Json/Resource;use App/Http/Serializers/JsonApiSerializer;class ArticleResource extends Resource{  public function toArray($request)  {    $value = [      'type' => 'articles',      'id' => $this->id,      'name' => $this->name,      'author' => $this->whenLoaded('author'),    ];    return new JsonApiSerializer($this, $value);  }}?>

对应的 Controller 也和原来差不多,只是加入了一个 isRoot 属性,用来识别根:

<?phpnamespace App/Http/Controllers;use App/Article;use App/Http/Resources/ArticleResource;class ArticleController extends Controller{  protected $article;  public function __construct(Article $article)  {    $this->article = $article;  }  public function show($id)  {    $article = $this->article->with('author')->findOrFail($id);    $resource = new ArticleResource($article);    $resource->isRoot = true;    return $resource;  }}?>

整个过程没有对 Laravel 的架构进行太大的侵入,可以说是目前 Laravel 实现 JSONAPI 的最优解决方案了,有兴趣的可以研究一下 JsonApiSerializer 的实现,虽然只有一百多行代码,但是我却费了好大的力气才实现,可以说是行行皆辛苦啊。

总结

以上所述是小编给大家介绍的JSONAPI在PHP中的应用,希望对大家有所帮助,如果大家有任何疑问欢迎给我留言,小编会及时回复大家的!

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表