灵活的 API 设计:为您的 PHP API 管道创建挂钩
设计应用程序编程接口 (API) 可能是一项具有挑战性的工作。 好的 API 具有简单易用的简单接口。 在这个简单的界面背后可能有许多复杂的系统交互,而这些交互确实会使原本明确定义的端点任务变得混乱。 随着时间的推移,开发人员可能会被要求为现有端点“处理”额外的业务逻辑。 然后在您不知不觉中,单个 API 调用正在与十几个系统进行交互,作为其主要流程的一部分。
如果我们可以开发一个简单明了但能够在以后添加额外任务而不混淆主要流程的管道,那不是很好吗? 本文将向您展示如何采用 WordPress 的想法和一般编程,使您的 API 能够进行更强大的交互。
什么是钩子/动作?
钩子(又名动作/过滤器)是 WordPress 社区为事件及其相关回调提供的名称。 如果您有任何编程经验,您可能熟悉回调和发布者-订阅者模式。 在处理过程中,系统可能会触发一个事件,该事件调用零到订阅该事件的许多函数。 例如,为响应加载页面,WordPress 调用函数来加载标题、加载标题、列出帖子或寻找正确的模板。 这些任务的运行不会扰乱生成页面的主要过程。
hooks 背后的想法并不是什么新鲜事,也不是 WordPress 发明的。 然而,WordPress 在服务器端页面处理生命周期中很好地实现了它们。 在我看来,这种挂钩的使用可能是该平台拥有的最重要的功能。 有了这些钩子,用户可以编写自己的功能——无论是插件还是主题——绑定到 WordPress 并在需要时运行你想要的任何代码。 您需要更改发送给用户的标头吗? 没问题:钩入 wp_headers
事件,您可以根据需要更改标题。
为什么在 API 中使用 Hooks?
钩子对很多事情都有好处,包括触发一些辅助任务、通过 PHP cURL 命令调用另一个系统、构建一个对象并将其放入任务队列以便稍后由另一个系统接收、发送电子邮件等等。 这一切都可以在不需要混淆给定端点的主要流程的情况下完成(并且可能在流程中强制使用新的 API 版本)。
如果端点用于创建用户,我们可以专注于在数据库中创建该用户记录,并在此过程中呼叫正在收听的任何人。 也许在创建用户记录之后,我们发送一个事件说“任何人听到这个,我刚刚创建了一个用户,这是他们的信息”。 也许一些回调函数已经订阅了事件并且正在监听或者可能没有。 该事件并不真正关心。
有了这个系统,我们可以让我们的 API 调用可能稍后编写的代码。 我们可以在不需要接触 API 端点代码本身的情况下做到这一点。 为了演示它是如何工作的,让我们换个方式展示我们如何在 PHP API 中启动它的基本机制。 请记住,当我们在这里使用 PHP 时,我们实际上可以在使用其他语言的 Web 应用程序中实现类似的逻辑。
建立基本机制
首先,我们需要能够添加一个挂钩/动作(从现在起我将其称为“挂钩”)。 我们还需要能够移除钩子并最终触发钩子。 一旦我们定义了这些机制,我们只需要确保它们包含在 API 中,然后在我们的 API 中找到我们可能想要调用这些挂钩的地方。 下面是我们可能想要设置它的一种方法。
这是 hooks.php
:
// Global array which will hold all of our hooks
// We will reference this array in each function to add/remove/call our hooks
// The code below should also be seen by any callbacks we write for the system later.
$hooks = [];
// Below are global functions that can be seen from our API code
// The add_hook method will allow us to attach a function (callback) to a given event name
function add_hook($event_name, $callback) {
global $hooks;
if ($callback !== null) {
if ($callback) {
// We can set up multiple callbacks under a single event name
$hooks[$event_name][] = $callback;
}
}
}
// Super easy to implement, we remove the given hook by its name
function remove_hook($event_name) {
global $hooks;
unset($hooks[$event_name]);
}
// When we want to trigger our callbacks, we can call this function
// with its name and any parameters we want to pass.
function do_hook($event_name, ...$params) {
global $hooks;
if (isset($hooks[$event_name])) {
// Loop through all the callbacks on this event name and call them (if defined that is)
// As we call each callback, we given it our parameters.
foreach ($hooks[$event_name] as $function) {
if (function_exists($function)) {
call_user_func($function, ...$params);
}
}
}
}
现在我们有了我们的 hooks.php
创建文件后,我们只需将其包含到我们的 API 中,以便可以看到这些功能。 完成后,只需使用以下方法将钩子插入我们的 API 即可 do_hook
.
举个简单的例子,假设我们有一个 API 用于向我们的系统注册新用户。 我们可能有一个名为 /addUser
. 为了简单起见,我们还假设这里的目标是简单地将新用户的姓名和年龄插入到我们的数据库中 users
桌子。 很简单,对吧?
// POST endpoint for adding a user (part of a larger API class)
public function addUser($name, $age) {
if ($this->request->method === 'post') {
try {
$this->db->insert('users', ['name' => $name, 'age' => $age]);
return new Response(200, 'User created successfully!');
} catch (Exception $e) {
// Oops, something went wrong.
// Do some logging or whatever.
}
}
// If not a POST request, return http status 400
return new Response(400, 'Bad request');
}
上面的代码是我们如何添加新用户的过于简单和概括的视图。 这个想法是,如果有人要调用我们的 API /addUser
端点,他们最终会到达这个函数,从发布的数据中提取用户的姓名和年龄。 我们首先检查以确保他们正在发帖(按照正确的 REST 规则规定),然后尝试将用户插入到 users
桌子。
接下来,如果用户已成功插入,我们要调用一个钩子让任何代码监听用户已创建(这类似于在其他语言中引发事件)。
需求变更时怎么办
几个月后,我们的营销部门坚持要求,当创建新用户时,应发送一封包含用户详细信息的电子邮件。 我们可能倾向于在 API 中编写一个辅助函数,然后从这个端点代码中调用它。 太好了……如果这就是所要求的全部。 但是,如果支持团队稍后来找您并希望您也将用户添加到他们的 Zendesk 系统中,该怎么办? 因此,您编写了另一个函数并将该调用也添加到该端点。
接下来你知道,这个端点不仅将用户添加到我们的网站数据库,而且还调用发送电子邮件的功能,将用户添加到 Zendesk、Jira 和 Microsoft 云,然后处理他们的成功/失败结果。 所有这些额外的东西确实使将用户添加到我们的数据库的明确点消失了。 我们希望调用一个事件,让其他代码只监听何时创建用户并执行他们自己的事情——而我们不需要修改这个端点。 也许没有其他服务关心添加新用户,因此没有人被要求做任何事情。 端点不必关心。 非常棒,对吧?
让我们继续我们的示例,为我们的钩子命名。 这是所有回调代码需要用来侦听的名称。 同样,其他语言将此设置称为“事件”,因此请务必以您给定的语言查找它以了解更多信息。
我们会打电话给我们的钩子 added_user
. 简单明了,你不觉得吗? 一旦我们有了名字,我们就可以决定在哪里调用这个钩子。 我认为在成功插入之后立即执行是个好主意:
public function addUser($name, $age) {
if ($this->request->method === 'post') {
try {
$this->db->insert('users', ['name' => $name, 'age' => $age]);
// Call our new hook and give it the name and age of the user. Anyone listening can then access these params.
do_hook('added_user', $name, $age);
return new Response(200, 'User created successfully!');
} catch (Exception $e) {
// Oops, something went wrong.
// Do some logging or whatever.
}
}
return new Response(400, 'Bad request');
}
现在我们可以有几十个回调函数监听 added_user
钩或根本没有。 也许我们有一个回调负责将用户插入 Zendesk,另一个负责获取姓名和年龄并生成一封电子邮件给营销部门。 这个“订阅者”代码可以存在于代码库的其他地方,只要它能看到 hooks.php
API 项目中的代码。 我通常将回调函数放在一个单独的文件中,并将该文件也包含到 API 中。 下面是一个回调示例,它现在可以利用我们创建的这个新钩子:
function sendContactToMarketing($name, $age) {
// Do email stuff
}
add_hook('added_user', 'sendContactToMarketing');
我们可以在哪里放置这些挂钩?
在上面的代码中,我们演示了在单个端点中使用钩子。 这个钩子只有在 /addUser
端点被调用,并且只有在插入成功之后。 但这不是您可以使用这些挂钩的唯一地方。 也许您的 API 类中有一些路由代码,这些代码通过检查 API 密钥是否有效或者您是否收到某种类型的请求来运行。
您可以在 API 的全局级别放置一个挂钩,以便每个请求都会触发它。 然后,在稍后的某个时候,有人可以编写一个记录器,将其附加到 api_init
你创建的钩子突然开始记录所有对 API 的请求——同样,不触及主要的 API 类代码。 同一个钩子可能还附加了一个额外的回调,用于检查 API 滥用情况,并在发现有人用请求攻击您的 API 时将其报告给您的信息技术部门。
下图是这一切在架构上的外观图。
由于您可以在多个位置使用此机制,您甚至可以在 API 端点的开头、中间和结尾调用挂钩。 您还可以在处理请求的整个 API 生命周期的各个点设置挂钩。 插入这些的位置完全取决于您的设计 do_hook
电话。
最佳实践
现在让我们介绍一些供您和您的开发人员遵循的最佳实践。
提示 1:保持你的钩子精简和平均
要记住的一件事是,这些挂钩仍然会调用将在单线程中执行的代码。 除非您在回调中触发某些东西将任务踢到某个后台进程或其他服务,否则 API 仍将运行此额外代码(当挂钩被触发时)。 这意味着我们应该尽最大努力保持任何回调代码精简。 长时间运行的回调代码会降低端点或整个 API 的速度。
技巧 2:使每个回调独立且易于调试
然而,按照这种结构的设计方式,为你的钩子添加和删除回调函数很容易,调试也很容易。 找出哪个回调是违规者(也许每个回调都记录一些数据)并找出它被卡住的地方。 然后,在修复错误或处理回调代码之前,不要让它订阅挂钩,同样不要触及端点/API 代码中的任何内容,也不要阻止您的 API 代码在生产环境中运行。
技巧 3:考虑性能,不要滥用钩子
同样重要的是要注意有多少回调附加到你的钩子上。 少量快速回调没问题,但是单个挂钩上的 100 个回调,每个回调都需要一秒钟的时间来执行,这确实会拖累您的 API。 我们想要快速的 API 调用,而每个回调都可以很容易地拖延响应时间。 同样,如果您发现回调速度很慢,请将任务交给后台进程或队列系统,以便稍后由其他服务接收。 我经常使用 Laravel 等系统中的作业来处理此类任务。 也许将用户任务添加到 Laravel 作业队列并继续进行 API 处理。
提示 4:与您的开发社区保持联系
最后,请确保您与可能使用这些的开发人员保持联系……