关于 API-First WordPress 的思考

Avatar of Eduardo Bouças
Eduardo Bouças

DigitalOcean 为您旅程的每个阶段提供云产品。 立即开始使用 200 美元的免费积分!

以下是由 Eduardo Bouças 撰写的客座文章。 我们都知道 WordPress 是一个 CMS,但 Eduardo 认为将它**仅用作内容的 API**。 根本没有前端,只有返回 JSON 的 URL 端点,可用于其他任何地方。 这并没有详细说明一个完整的解决方案来做到这一点,它只是一些想法,以及一些示例代码,可以让你开始创建自定义解决方案。 如果你想立即开始在这样的系统上进行开发,WP REST API 是最强大的项目,拥有最大的发展势头。

我最近被要求为一家数字代理商选择和实现一个 CMS 解决方案,以便在一个安装中管理多个网站。 出于很多原因,主要候选人是 WordPress。 它是免费的、开源的,拥有庞大的用户社区,易于使用,并且具有 多站点功能。 它无疑是经过商业验证的成熟产品。 但是,我对此有所保留。

以传统方式使用 WordPress 意味着进行安装、创建主题(或修改现有主题),并接受所有进一步的自定义必须在 CMS 创建的生态系统中找到自己的位置:编程语言和技术(PHP 和 MySQL)以及巧妙的(但相当复杂)的插件、主题、操作、过滤器等等的世界。

我一直将内容视为网站上一切的核心。 每个项目的性质和创意理念都应该决定什么媒介和技术能够最好地传递内容,而不是 CMS。

我不想让项目的技术选择仅仅局限于 PHP,因为 WordPress 是基于它构建的,我希望开发人员能够自由选择他们认为合适的任何技术栈来创建独立且自包含的网站,而不仅仅是 WordPress 插件和主题。

创建中间人

一个 API-First 解决方案,通过一个位于网站和 CMS 之间的中间层,可以解放 WordPress 不用处理任何前端业务,并将它只用于管理和交付内容。

这个“中间人”层能够使用通用的语言(我更喜欢 JSON),不同的终端平台可以理解它,并以适合项目的方式对其进行处理。

我正在考虑类似这样的东西

多站点 WordPress 安装向 API 提供数据的示例

调整 WordPress

在正常的 WordPress 世界中,人们会通过友好的域名访问网站,内容会从数据库中提取,然后主题会将其格式化并显示在 HTML 页面中。 该页面也很可能会有一个可视化界面,供用户浏览帖子和页面,并根据类别、标签或任何其他分类法过滤内容。

我们的 API-First WordPress 不会有任何这些东西。 我们将接受来自用户的唯一输入将来自他们发送的请求的 URL,我们必须解析它才能提取我们需要交付的数据类型、格式以及传递它的过滤器。

构建插件

在 WordPress 中添加和更改功能有好的方法,也有不好的方法。 简而言之,修改核心代码库不是好兆头,你应该创建插件

但是我们的插件究竟是如何工作的呢? 它如何改变 CMS 遵循的默认事件链,以读取请求,从数据库中获取内容,然后发送回内容? 这可以通过一个钩子(更确切地说是一个操作)来实现,它允许我们在工作中插入一个扳手,拦截请求,完全控制从那时起发生的事情。

所以让我们开始为我们的插件奠定基础。

class API {
  public function __construct() {
    add_action('template_redirect', array($this, 'hijackRequests'), -100);
  }

  public function hijackRequests() {
    $entries = get_posts($_GET['filter']);

    $this->writeJsonResponse($entries);
  }

  protected function writeJsonResponse($message, $status = 200) {
    header('content-type: application/json; charset=utf-8', true, $status);
    echo(json_encode($message) . "\n");
    exit;
  }
}

new API();

将插件包装在类构造中是一个好习惯,以避免在全局命名空间中使用松散函数,这可能会导致命名冲突。

然后,我们首先使用template_redirect操作注册一个函数,该操作在初始化例程执行后立即执行,并在 WordPress 决定使用哪个模板来呈现页面之前执行。

然后我们执行get_posts(),它接受一个过滤器数组作为参数,并返回一个匹配项数组(函数名可能具有误导性;它可以返回帖子和页面)。

所以,在保存文件并激活插件后,访问http://your-WP/index.php?filter[post_type]=post&filter[posts_per_page]=1应该可以获得最新帖子的 JSON 表示形式。 真是太棒了!

多路复用请求

在这一点上,我们有一个非常基本的 API,它允许我们根据一组过滤器从 WordPress 中提取条目,这对于非常简单的项目可能就足够了。 但是,当我们需要获取多组数据来呈现页面上的不同元素时会发生什么? 发送多个 HTTP 请求似乎并不合理。

以 CSS-Tricks 上的 论坛页面 为例。 除了我们可能需要的一些元数据之外,至少有三种不同的内容集需要从 CMS 中提取:导航栏上的项目、最新帖子和技巧。

CSS-Tricks 页面上的不同内容组

我们可以为 API 定义自己的自定义语法,以便它可以动态地接受“内容桶”的定义,并以 JSON 数组的形式将它们分隔在响应中返回。

与其将过滤器作为 URL 中的简单数组传递,不如为每个过滤器附加一个标签,说明它们属于某个桶。 回到上面的例子,多路复用请求的 URL 可以是这样的

?navigation:filter[category]=navigation
&latestPosts:filter[type]=post
&tips:filter[slug]=tips

这将返回类似这样的 JSON 结构

{
  "navigation": [
    {
      "ID": 1
      (...)
    },
    {
      "ID": 2
      (...)
    }
  ],
  "latestPosts": [
    (...)
  ],
  "tips": [
    (...)
  ]
}

这使 API 消费者能够轻松访问他们需要的不同内容部分,而无需任何额外的努力。

函数hijackRequests可以修改以实现此功能。

public function hijackRequests() {
  $usingBuckets = false;
  $buckets = array();
  $entries = array();

  foreach ($_GET as $variable => $value) {
    if (($separator = strpos($variable, ':')) !== false) {
      $usingBuckets = true;
      $bucket = substr($variable, 0, $separator);
      $filter = substr($variable, $separator + 1);
    } else {
      $bucket = 0;
      $filter = $variable;
    }

    $buckets[$bucket][$filter] = $value;
  }

  if ($usingBuckets) {
    foreach ($buckets as $name => $content) {
      $entries[$name] = get_posts($content['filter']);
    }
  } else {
    $entries = get_posts($buckets[0]['filter']);
  }

  $this->writeJsonResponse($entries);
}
编辑更新:Eduardo 为 WP-API 创建了一个扩展插件来实现这一点! 查看 GitHub 仓库

添加画廊和自定义字段

我们对帖子的 JSON 表示形式依赖于get_posts()返回的信息,但那里缺少一些你可能希望在提要中看到的信息,例如图像画廊和自定义字段。 我们可以借助get_post_galleries_images()get_post_meta()函数将这些信息附加到 JSON 提要中。

for ($i = 0, $numEntries = count($entries); $i < $numEntries; $i++) {
    $metaFields = get_post_meta($entries[$i]->ID);

  $galleriesImages = get_post_galleries_images($entries[$i]->ID);
  $entries[$i]->galleries = $galleriesImages;

  foreach ($metaFields as $field => $value) {
    // Discarding private meta fields (that begin with an underscore)
    if (strpos($field, '_') === 0) {
      continue;
    }

    if (is_array($value) && (count($value) == 1)) {
      $entries[$i]->$field = $value[0];
    } else {
      $entries[$i]->$field = $value;
    }
  }
}

最终想法

本文中描述的解决方案仅仅触及了构建 API 所涉及内容的皮毛。 我们还没有涉及诸如身份验证、写入访问的请求类型(POSTPUTDELETE)、不同内容类型的多个端点(用户、类别、设置)、API 版本控制或 JSONP 支持等方面。

此解决方案并不是为了提供一个可用于生产环境的产品,而是为了展示 WordPress API 的内部工作原理,希望能够激发人们创建自定义解决方案,或扩展现有解决方案,以满足他们特定的需求。

说实话,创建定制的 API 解决方案并不适合所有人。 WP REST API 是一款成熟的成熟产品,很快将成为 WordPress 核心的一部分,因此使用这样的东西可能是一个更明智的选择。

最重要的是,本文的目的是探讨将像 WordPress 这样的广泛使用、经商业验证的成熟产品用作 API-First 内容管理系统的想法。 这意味着要剥离 WordPress 的大部分功能,并失去 SEO 插件和简单主题化等功能带来的好处,但你将获得平台无关系统的自由。

你有什么想法?