[深入浅出]深入浅析JSONAPI在PHP中的应用

更新时间:2024-03-25    来源:php应用    手机版     字体:

【www.bbyears.com--php应用】

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

JSONAPI

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

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

 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 进行了封装,使其更好用:

collection($articles)
  ->transformWith(new ArticleTransformer())
  ->includeAuthor()
  ->toArray();
?>

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

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 基本还和以前一样,只是返回值改了一下:

 "articles",
      "id" => $this->id,
      "name" => $this->name,
      "author" => $this->whenLoaded("author"),
    ];
    return new JsonApiSerializer($this, $value);
  }
}
?>

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

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 的实现,虽然只有一百多行代码,但是我却费了好大的力气才实现,可以说是行行皆辛苦啊。

本文来源:http://www.bbyears.com/jiaocheng/144142.html

热门标签

更多>>

本类排行