aivec/wordpress-router

WordPress 请求路由器。包含中间件、JWT 和 nonce 检查。

v7.3.0 2022-08-01 08:05 UTC

README

本包为 WordPress 提供了路由库,包含针对 WordPress 的特定包装,如 nonce 验证和用户角色检查。本包的核心使用 FastRoute,这是一个小型且简洁的路由解析器。《FastRoute》也是流行的微框架 Slim 所使用的路由解析器。

问题

WordPress 中的路由对插件作者来说是个头疼的问题。如果你采用 WordPress 传统方式通过 admin-ajax.php 注册 AJAX 处理器,它完全依赖于 $_POST 对象的键来解析路由。你可以使用 WordPress 的 REST API,但你无法控制路由何时解析。这对那些为其他插件创建扩展的开发者来说非常重要,因为这些开发者无法控制加载顺序。本包与 WordPress 的实现不同之处在于,它不提供 validatesanitize 回调,而是选择使用通用中间件。

特性

该库提供了许多功能来简化路由的提供,以及一些可选的默认中间件。主要特性如下

  • 基于角色的路由注册(编辑者、管理员等)
  • 自动 nonce 验证
  • URL 参数(非正则表达式 😁)
  • 透传路由(非 AJAX 路由)
  • 用于生成 HTML 表单的辅助函数
  • JWT 路由注册
  • JWT 设置页面用于自动生成密钥对

安装

使用 composer 安装

$ composer require aivec/wordpress-router

如果你计划在一个插件中使用此包,我们 强烈 建议使用 mozart 进行命名空间。如果不这样做,可能会出现难以调试的故障。 警告:已警告你

使用指南

公开路由

公开路由是指不需要 nonce 验证的路由。公开路由任何人都可以从任何地方访问。

use Aivec\WordPress\Routing\Router;
use Aivec\WordPress\Routing\WordPressRouteCollector;

// First, we declare our routes by extending the `Router` class:
class Routes extends Router {

    /**
     * This is where we define each route
     */
    public function declareRoutes(WordPressRouteCollector $r) {
        $r->addPublicRoute('GET', '/hamburger', function () {
            return 'Here is a public hamburger.';
        });
    }
}

// Next, we instantiate the `Routes` class with a unique namespace and listen for requests
$routes = new Routes('/mynamespace');
$routes->dispatcher->listen();

调用公开路由

你可以从命令行测试路由,如下所示

$ curl -X GET http://www.my-site.com/mynamespace/hamburger
'Here is a public hamburger.'

或者,你可以使用 jQueryajax 函数从加载到 WordPress 页面的脚本中发送请求

jQuery.ajax("http://www.my-site.com/mynamespace/hamburger", {
  success(data) {
    var response = JSON.parse(data);

    console.log(response); // Here is a public hamburger.
  },
});

私有路由

私有路由是指需要 nonce 验证的路由。

use Aivec\WordPress\Routing\Router;
use Aivec\WordPress\Routing\WordPressRouteCollector;

// First, extend the `Router` class:
class Routes extends Router {

    /**
     * This is where we define each route
     */
    public function declareRoutes(WordPressRouteCollector $r) {
        /**
         * `add` is the default way to register a route with nonce verification
         */
        $r->add('POST', '/hamburger', function () {
            return 'Here is a private hamburger.';
        });
    }
}

在声明了我们的路由之后,我们使用唯一的命名空间实例化 Routes 类。

这次,我们将 nonce 键和 nonce 名称作为第二个和第三个参数传入。

由于 nonce 处理需要 WordPress 核心函数,因此必须在核心函数加载之后实例化 Routes 类。你可以使用 init 钩子或任何其他适当的钩子来确保核心函数已加载。

$routes = null;
add_action('init', function () use ($routes) {
    $routes = new Routes('/mynamespace', 'nonce-key', 'nonce-name');
    $routes->dispatcher->listen();
});

调用私有路由

通常,私有路由是通过从 WordPress 网站上的 JavaScript 文件调用 AJAX 来调用的。为此,我们必须将 nonce 传递给我们要从中调用路由的脚本。

利用 wp_localize_script,我们可以从 Routes 类中调用一个辅助方法来注入 nonce 变量。

add_action('wp_enqueue_scripts', function () use ($routes) {
    wp_enqueue_script(
        'my-script',
        site_url() . '/wp-content/plugins/my-plugin/my-script.js',
        [],
        '1.0.0',
        false
    );

    wp_localize_script('my-script', 'myvars', $routes->getScriptInjectionVariables());
});

现在,my-script.js 将包含我们需要的 nonce 变量以进行调用。

// my-script.js
jQuery.ajax(`${myvars.endpoint}/hamburger`, {
  method: "POST",
  data: {
    [myvars.nonceKey]: myvars.nonce,
  },
  success(data) {
    var response = JSON.parse(data);

    console.log(response); // Here is a private hamburger.
  },
});

URL 参数

花括号用于定义 URL 参数。

URL 参数将被解析,然后插入到 $args 变量中,该变量总是作为处理函数的 第一个 参数传递。

$r->add('POST', '/hamburger/{burgername}', function (array $args) {
    return 'Here is a ' . $args['burgername'] . ' hamburger.';
});
// my-script.js
jQuery.ajax(`${myvars.endpoint}/hamburger/mushroom`, {
  method: "POST",
  data: {
    [myvars.nonceKey]: myvars.nonce,
  },
  success(data) {
    var response = JSON.parse(data);

    console.log(response); // Here is a mushroom hamburger.
  },
});

您可以定义任意多的参数。

$r->add('POST', '/hamburger/{burgername}/{count}', function (array $args) {
    return 'Here are ' . $args['count'] . ' ' . $args['burgername'] . ' hamburgers.';
});

您还可以限制接受的参数类型,以及提供自己的模式以进行更精细的控制。

// Matches /user/42, but not /user/xyz
$r->add('POST', '/user/{id:\d+}', .....);

// Matches /user/foobar, but not /user/foo/bar
$r->add('GET', '/user/{name}', .....);

// Matches /user/foo/bar as well
$r->add('GET', '/user/{name:.+}', .....);

路由定义有许多可能性。有关如何解析路由的详细信息,请参考这里

表单数据

路由器期望以 application/x-www-form-urlencoded 的内容类型发送 POST 请求。表单数据作为 JSON 编码的字符串发送,作为请求体中 payload 键的值。

// $payload contains the decoded JSON key-value array
$r->add('POST', '/hamburger', function (array $args, array $payload) {
    $ingredients = join(' and ', $payload['ingredients']);
    return 'I want ' . $ingredients . ' on my hamburger.';
});
// my-script.js
jQuery.ajax(`${myvars.endpoint}/hamburger`, {
  method: "POST",
  data: {
    [myvars.nonceKey]: myvars.nonce,
    payload: JSON.stringify({
      ingredients: ["pickles", "onion"],
    }),
  },
  success(data) {
    var response = JSON.parse(data);

    console.log(response); // I want pickles and onion on my hamburger.
  },
});

让一切变得更简单

如我们所见,私有路由需要在 POST 请求体中存在 nonce 键值对。您可能已经注意到我们在那些例子中排除了 GET 请求。这是因为 GET 请求没有请求体内容,这意味着 nonce 变量必须作为 URL 查询参数设置。整个过程很繁琐,我们可以做得更好。

对于正在将 JavaScript 转译的人,我们建议使用 axios 和我们的 辅助库。这完全抽象了 nonce 处理和 JSON 编码,并在请求方法(GETPOSTPUT 等)中自动设置 nonce 变量。

以下是用这些库重写的 表单数据 示例。

// my-script.js
import axios from "axios";
import { createRequestBody } from "@aivec/reqres-utils";

axios
  .post(
    `${myvars.endpoint}/hamburger`,
    createRequestBody(myvars, {
      ingredients: ["pickles", "onion"],
    })
  )
  .then(({ data }) => {
    console.log(data);
  });