制作一个使用服务 API 的 WordPress 插件,“从汤到坚果”
已发表: 2022-03-10越来越多的公开可用的 API 提供了强大的服务来扩展我们应用程序的功能。 WordPress 是一个令人难以置信的动态和灵活的 CMS,它支持从小型个人博客到主要电子商务网站以及介于两者之间的所有内容。 使 WordPress 如此通用的部分原因在于其强大的插件系统,这使得添加功能变得异常容易。
我们将介绍我如何制作 GitHub Pipeline,这是一个允许您使用短代码在 WordPress 页面上显示来自 GitHub API 的数据的插件。 我将给出具体的示例和代码片段,但将此处描述的技术视为如何使用插件使用任何服务 API 的蓝图。
关于 SmashingMag 的进一步阅读:
- WordPress Essentials:如何创建 WordPress 插件
- 如何使用瞬态在 GitHub 上部署 WordPress 插件
- 向插件添加可配置字段的三种方法
我们将从头开始,但假设您对 WordPress 和插件开发有一定程度的熟悉,并且我们不会花时间在初学者主题上,例如安装 WordPress 或 Composer。
你需要:
- PHP 环境,全新安装 WordPress;
- 一个 GitHub 帐户(或其他 API 提供商,如果您想即兴发挥);
- 作曲家(推荐)。
选择 API
编写这种插件的第一步是选择一个 API。 在本教程中,我们将使用 GitHub API。 如果您正在考虑使用其他 API,请考虑一些可能会影响您的项目回报的重要因素。
我首先要看的是 API 文档的彻底性和质量。 如果它稀疏或过时,请准备好花时间筛选源代码来回答您自己的问题。 此外,还要考虑 API 的成熟程度以及提供者对其版本的负责程度。 没有什么比花时间创造伟大的东西更糟糕的了,只是因为上游 API 的变化而破坏了它。 在端点 URL 中查找版本控制系统。
// good https://api.stable.com/v1/user/ // danger https://api.dodgy.com/user/
冒着明显的风险,针对第三方 API 进行编程涉及信任关系,并且并非所有 API 都是平等的。
购买图书馆
知名品牌通常会分发库或 SDK,以便更轻松地使用其 API。 如果不是这种情况,请记住在重新发明轮子之前研究其他人是否已经编写了库。 Google 和 GitHub 是您开始研究的两个好地方。 GitHub 上的星数或分叉数很好地表明了库的有效性。 最近提交的年龄和/或未解决问题的数量表明它的维护程度。 就我而言,我很幸运地找到了 KNP Labs 提供的可爱的 PHP GitHub API。
用 Guzzle 编写自己的
如果您的提供者没有令人满意的库,您仍然不应该从头开始,因为像 Guzzle 这样的工具可以更轻松地处理 HTTP 请求。 Guzzle 包装了 PHP 的 cURL 库,并消除了通常与手动配置和发出请求相关的许多令人头疼的问题。 即使您只发出一两个请求,我仍然建议您使用它,因为它会使您的代码更加健壮,并且使用 Composer 安装它是小菜一碟。
设置插件
我们将从一个最小的 WordPress 插件的基本框架开始,一个包含两个文件的目录。 选择一个描述性和唯一的文件夹名称对于避免与其他插件冲突很重要。 如果您的插件名称有些通用,请考虑添加唯一前缀。
github-api/ readme.txt github-api.php
readme.txt
包含您的插件的元数据,如果您决定将其发布在wordpress.org
上,该元数据将出现。 阅读文档中有关发布 WordPress 插件的信息,或查看综合示例 readme.txt。
PHP 文件的标题中还包含一些元数据,这些元数据将用于在仪表板中显示有关您的插件的信息。 从如下所示的标题开始:
<?php /** Plugin Name: GitHub API description: >- Add GitHub project information using shortcode Version: 1.0 Author: Your Name License: GPLv2 or later Text Domain: github-api */
出于安全原因,拒绝直接访问文件也是一个好主意,如下所示:
defined( 'ABSPATH' ) or die( 'No script kiddies please!' );
此时,插件不会做任何事情,但是当您将文件复制到wp-content/plugins
时,它应该会出现在插件列表中并且您应该能够激活它。

接下来,我们要包含处理 API 请求的库。 在下面的代码示例中,我们包含了 KNP Labs 的 PHP GitHub API,但您可以通过将knplabs/github-api
替换为您正在使用的包来包含任何依赖项。
$ cd wp-content/plugins/github-api $ composer require knplabs/github-api
现在,您的文件结构应如下所示:
github-api/ composer.json composer.lock github-api.php readme.txt vendor/
现在文件已经就位,我们需要安装它时创建的vendor/autoload.php
。
require_once 'vendor/autoload.php';
如果一切设置正确,您应该能够从库中实例化一个类,而不会引发致命错误。
$testing = new \Github\Client();
但这只是测试您的依赖项是否可用的一种快速方法。 我建议定义一个扩展库的新类。
class MyGithub extends \Github\Client {};
这并不重要,但它使您的代码在几个方面更加灵活。 最明显的方式是您可以根据需要更改或添加新功能。 但是,如果您将来想要切换库,它也会让生活变得更轻松,因为您只需要在一次地方进行更改,而不是在整个代码中进行更改。
WordPress 简码
现在是时候设置我们的第一个短代码了,它是一个特殊的片段,允许您通过将其放置在帖子或页面中来生成内容。 如果您对简码完全陌生,或者您想更深入地了解它们的工作原理,请查看官方简码文档。 在我的示例中,我将在作者放置此短代码的任何位置显示 GitHub 问题列表: [github_issues]
正如我们之前所做的那样,让我们从一个最小的示例开始,以确保在我们进行 API 调用之前正确注册短代码。
function github_issues_func( $atts ) { return "Hello world!"; } add_shortcode( "github_issues", "github_issues_func" );
现在,如果您发布一个包含[github_issues]
的页面,您应该在访问该页面时看到“Hello world”。 现在这可以正常工作了,让我们设置 API 调用。
在上一节中,我们为我们的 GitHub 库设置了自动加载,并定义了我们自己的类来扩展它。 现在,我们可以开始使用它来进行 API 调用了。 因此,我们将其添加到短代码的回调函数中。 作为概念证明,让我们获取 GitHub Pipeline 存储库中的所有问题。 此方法的文档可用。
function github_issues_func( $atts ) { // Instantiate our class $gh = new MyGithub(); // Make the API call to get issues, passing in the GitHub owner and repository $issues = $gh->api('issue')->all('TransitScreen', 'wp-github-pipeline'); // Handle the case when there are no issues if ( empty($issues) ) return "<strong>" . __("No issues to show", 'githup-api') . "</strong>"; // We're going to return a string. First, we open a list. $return = "<ul>"; // Loop over the returned issues foreach( $issues as $issue ) { // Add a list item for each issue to the string // (Feel free to get fancier here) // Maybe make each one a link to the issue issuing $issue['url] ) $return .= "<li>{$issue['title']}</li>"; } // Don't forget to close the list $return .= "</ul>"; return $return; } add_shortcode( 'github_issues', 'github_issues_func' );
现在您应该能够查看我们上面用来测试简码的同一页面,这一次您应该看到一个无序列表的问题。 可是等等! 在继续之前,让我们进行一项优化,以便我们的代码更容易测试。 (你是在测试,对吧?!)不要在我们的函数中使用new
来实例化 GitHub 库类,让我们将它作为参数传入并仅在必要时实例化它。
function github_issues_func( $atts, $gh=null ) { // Conditionally instantiate our class $gh = ( $gh ) ? $gh : new MyGithub(); …
这种修改类似于依赖注入模式,这使得我们的函数更容易测试。 WordPress 单元测试超出了本教程的范围,但很容易看出这个新版本如何让我们轻松地将“假”API 数据传递给函数,以便我们的测试断言准确地知道会发生什么。
到目前为止,我们所做的对于仅在单个存储库上使用的内部项目来说非常好,但如果用户可以配置从哪个存储库获取问题,它将更加有用。 让我们设置一下。
首先,我们将使用 WordPress 挂钩来注册我们的新设置页面,回调函数将定义菜单的标签和标题。 我们将使用相同的增量方法来使其工作。
// Register the menu. add_action( "admin_menu", "gh_plugin_menu_func" ); function gh_plugin_menu_func() { add_submenu_page( "options-general.php", // Which menu parent "GitHub", // Page title "GitHub", // Menu title "manage_options", // Minimum capability (manage_options is an easy way to target administrators) "github", // Menu slug "gh_plugin_options" // Callback that prints the markup ); } // Print the markup for the page function gh_plugin_options() { if ( !current_user_can( "manage_options" ) ) { wp_die( __( "You do not have sufficient permissions to access this page." ) ); } echo "Hello world!"; }
现在您应该能够登录仪表板并在“设置”下看到新的 GitHub 菜单,然后单击它以查看带有“Hello world!”的空白设置页面

接下来,我们将用表单的实际标记替换Hello word
。 我会给你一个准系统的例子,你可以随心所欲地设计。
?> <form method="post" action="<?php echo admin_url( 'admin-post.php'); ?>"> <input type="hidden" name="action" value="update_github_settings" /> <h3><?php _e("GitHub Repository Info", "github-api"); ?></h3> <p> <label><?php _e("GitHub Organization:", "github-api"); ?></label> <input class="" type="text" name="gh_org" value="<?php echo get_option('gh_org'); ?>" /> </p> <p> <label><?php _e("GitHub repository (slug):", "github-api"); ?></label> <input class="" type="text" name="gh_repo" value="<?php echo get_option('gh_repo'); ?>" /> </p> <input class="button button-primary" type="submit" value="<?php _e("Save", "github-api"); ?>" /> </form> <?php
我包含的 CSS 类与 WordPress 核心使用的类相匹配,这是一种无需额外努力即可让您的自定义选项页面看起来不错的好方法。


关于这个表格,有两件重要的事情需要理解。 首先,它提交的端点必须是/wp-admin/admin-post.php
。 您可以硬编码此路径,但如果网站安装在子目录中,它将无法工作。 因此,我们使用内置的admin_url()
来动态创建它。
其次,注意名为action
的隐藏输入。 这个字段是 WordPress 知道如何处理表单提交的发布请求的方式。 该value
对应于我们将用于设置回调函数的操作挂钩的名称。 我们将挂钩的操作的名称将是这个值,前缀为admin post
。 所以,在我们的例子中,我们需要添加这个:
add_action( 'admin_post_update_github_settings', 'github_handle_save' );
我发现这是创建自定义管理菜单的一个更古怪、更不直观的方面,但是一旦你习惯了它,它就会变得非常轻松。 正如您可能已经猜到的那样, add_action()
的第二个参数是我们的回调函数的名称,它实际上会将值保存到数据库中。
function github_handle_save() { // Get the options that were sent $org = (!empty($_POST["gh_org"])) ? $_POST["gh_org"] : NULL; $repo = (!empty($_POST["gh_repo"])) ? $_POST["gh_repo"] : NULL; // Validation would go here // Update the values update_option( "gh_repo", $repo, TRUE ); update_option("gh_org", $org, TRUE); // Redirect back to settings page // The ?page=github corresponds to the "slug" // set in the fourth parameter of add_submenu_page() above. $redirect_url = get_bloginfo("url") . "/wp-admin/options-general.php?page=github&status=success"; header("Location: ".$redirect_url); exit; }
这个例子也非常少。 在生产环境中,您可能想要添加一些验证。 此外,在此示例中,我们会自动将status=success
传递回 URL。 表单的标记实际上还没有使用它。 要有条件地显示成功消息,请在gh_plugin_options()
的表单上方添加如下内容:
if ( isset($_GET['status']) && $_GET['status']=='success') { ?> <div class="updated notice is-dismissible"> <p><?php _e("Settings updated!", "github-api"); ?></p> <button type="button" class="notice-dismiss"> <span class="screen-reader-text"><?php _e("Dismiss this notice.", "github-api"); ?></span> </button> </div> <?php }
如果您添加验证,请使用相同的模式传回不同的状态和消息,以便用户知道他们提交表单是否失败以及为什么失败。
现在您应该能够保存新的所有者和存储库值。 通过输入 GitHub 上任何公共项目的所有者和存储库来测试它。 最后一步是返回短代码回调函数github_issues_func()
并替换硬编码的所有者和存储库值。
… $issues = $gh->api("issue")->all(get_option("gh_org"), get_option("gh_repo")); …
一旦到位,请重新访问您添加短代码的页面。 您现在应该看到您设置的任何项目的问题。
奖励回合:OAuth 2.0 身份验证
我们上面使用的方法非常适用于公共存储库,但是如果我们想将此插件与需要身份验证的私有存储库一起使用怎么办? GitHub API 使用 OAuth 2.0 协议进行身份验证,这是您现在使用最流行的 API 时会遇到的。 基本的 OAuth 2.0 工作流程如下所示:
- 您向提供者注册一个应用程序(有时称为“客户端”)并收到一个唯一的 ID 和密钥。
- 您的应用程序向提供者的身份验证端点发出请求,传递上述凭据以及提供者用来将请求重定向回应用程序端点的重定向 URL。
- 然后提示用户接受您的访问请求。 如果他们这样做了,提供商会使用您随请求发送的 URL 以及临时代码将用户重定向回您的应用程序。
- 您的应用程序捕获此代码,然后发出第二个请求,将此代码传递回提供者。 提供者使用访问令牌进行响应,然后您的应用程序使用该令牌向提供者进行身份验证。
我们将从在 GitHub 上注册一个应用程序开始。 与大多数流行的 API 一样,此部分位于网站的开发人员区域。

这里最重要的是授权回调 URL。 第三步中传递的重定向 URL 必须与您在此处输入的内容相匹配,或者包含它。 所以,在开发过程中,我一般会进入网站的首页。 这样,我的应用程序可以重定向到任何路径。
接下来,我们将在gh_plugin_options()
中的设置页面中添加第二个表单,用于提交应用程序的客户端 ID 和密码。
<form method="post" action="<?php echo admin_url( 'admin-post.php'); ?>"> <input type="hidden" name="action" value="oauth_submit" /> <h3>Oauth 2.0</h3> <p> <label><?php _e("GitHub Application Client ID:", "github-api"); ?></label> <input class="" type="text" name="client_id" value="<?php echo get_option('client_id')?>" /> </p> <p> <label><?php _e("GitHub Application Client Secret:", "github-api"); ?></label> <input class="" type="password" name="client_secret" value="<?php echo get_option('client_secret')?>" /> </p> <input class="button button-primary" type="submit" value="<?php _e("Authorize", "github-api"); ?>" /> </form>
为了让生活更轻松,我将使用 The League of Extraordinary Packages 的 GitHub Provider for OAuth 2.0 Client。 所以,首先,让我们再次使用 Composer 来添加依赖:
composer require league/oauth2-github
请注意,来自 KNP Labs 的 GitHub 库也内置了 OAuth 2.0 支持。因此,在我的具体示例中,这有点多余。 但我想介绍这个库,因为它属于一组特定于提供商的 OAuth 2.0 客户端库,它们都扩展了由强大的非凡包联盟维护的相同框架。 您可以查看支持的提供程序的完整列表或阅读有关如何扩展框架以支持新提供程序的说明。
让我们创建一个函数来在用户提交表单时请求并保存令牌。 因为 GitHub 是已经支持的提供商之一,所以我可以将示例复制到其文档中,只需进行一些修改。
function handle_oauth() { // If the form was just submitted, save the values // (Step 1 above) if ( isset($_POST["client_id"]) && isset($_POST["client_secret"]) ) { update_option( "client_id", $_POST["client_id"], TRUE ); update_option("client_secret", $_POST["client_secret"], TRUE); } // Get the saved application info $client_id = get_option("client_id"); $client_secret = get_option("client_secret"); if ($client_id && $client_secret) { $provider = new League\OAuth2\Client\Provider\Github([ "clientId" => $client_id, "clientSecret" => $client_secret, "redirectUri" => admin_url("options-general.php?page=github"), ]); } // If this is a form submission, start the workflow // (Step 2) if (!isset($_GET["code"]) && $_SERVER["REQUEST_METHOD"] === "POST") { // If we don't have an authorization code, then get one $authUrl = $provider->getAuthorizationUrl(); $_SESSION["oauth2state"] = $provider->getState(); header("Location: ".$authUrl); exit; // Check given state against previously stored one to mitigate CSRF attack // (Step 3 just happened and the user was redirected back) } elseif (empty($_GET["state"]) || ($_GET["state"] !== $_SESSION["oauth2state"])) { unset($_SESSION["oauth2state"]); exit("Invalid state"); } else { // Try to get an access token (using the authorization code grant) // (Step 4) $token = $provider->getAccessToken("authorization_code", [ "code" => $_GET["code"] ]); // Save the token for future use update_option( "github_token", $token->getToken(), TRUE ); } }
而且,就像我们对其他表单所做的那样,我们需要添加操作挂钩,以便在保存表单时调用该函数。
add_action( "admin_post_oauth_submit", "handle_oauth" );
保存表单现在应该会将您发送到 API 提供者的授权页面。 授权后,您的应用程序可以使用保存的令牌从您有权访问的私有存储库中请求数据。 我在 KNP Labs 使用的库有一个方便的方法。
$gh = new MyGithub(); $gh->authenticate( get_option("github_token"), NULL, Github\Client::AUTH_HTTP_TOKEN);
库在处理身份验证的方式上会有所不同,但是通过一种或另一种方式,您将传递令牌,然后它将代表用户发出经过身份验证的请求。
结论
我们已经涵盖了很多领域,并且我尝试尽可能减少示例,以便整个工作流程保持清晰。 本教程的完整源代码可用。 我希望您现在对创建使用第三方服务 API 的 WordPress 插件所涉及的移动部分有一个清晰的了解,并希望您受到启发来编写自己的 WordPress API 插件。
最后的笔记
- WordPress 法典(文档)
- GitHub API(文档)
- OAuth 2.0(文档)
- 作曲家(文档)
- 非凡礼包联盟
- Guzzle 文档