如何设计一个简单的扩展
简介
构建一个为商家和购物者提供一流体验的 WooCommerce 扩展,需要一种混合的开发方法,结合 PHP 和现代 JavaScript。 PHP 处理扩展的生命周期和服务器端操作,而现代 JavaScript 允许您塑造其用户界面的外观和行为。
主插件文件
您的扩展的主要 PHP 文件是一个引导文件。 它包含有关您的扩展的重要元数据,WordPress 和 WooCommerce 使用这些元数据进行各种生态系统集成过程,并且它作为您的扩展功能的主要入口点。 虽然没有对命名此文件有特定的规定,但使用插件名称的连字符版本是一种常见的最佳实践。(例如:my-extension.php)
声明扩展元数据
您的扩展的主要插件文件应包含一个头部注释,其中包含有关您的扩展的重要元数据。 WordPress 有一个 头部要求 列表,所有插件都必须遵守,但对于 WooCommerce 扩展,还有一些额外的考虑因素:
-
Author和Developer字段是必需的,应设置为您的姓名或您的公司名称。 -
Developer URI字段应设置为您的官方网页 URL。 -
Plugin URI字段应包含扩展在 WooCommerce Marketplace 的产品页面 URL 或您网站上扩展的官方着陆页 URL。 -
Requires Plugins字段应列出任何必需的插件的别名,用逗号分隔。 WooCommerce 扩展应要求 WooCommerce 才能激活。 -
Woo字段用于在 WooCommerce Marketplace 中列出的扩展。 此代码在 WooCommerce.com 上销售的产品部署时会自动添加,以方便更新。 请勿手动添加,并且不要将其添加到在 WooCommerce Marketplace 之外提供的产品。 在其他版本中包含此头部将阻止更新正常工作。
以下是一个在 WooCommerce Marketplace 中列出的扩展的头部内容示例。
/**
* Plugin Name: My Great Extension for WooCommerce
* Plugin URI: https://woocommerce.com/products/my-extension-name/
* Description: 您的扩展的描述文本。
* Version: 1.0.0
* Author: 您的姓名
* Author URI: http://yourdomain.com/
* Developer: 您的姓名
* Developer URI: http://yourdomain.com/
* Text Domain: my-extension-name
* Domain Path: /languages
* Requires Plugins: woocommerce
*
* Woo: 12345:342928dfsfhsf8429842374wdf4234sfd
*
* License: GNU General Public License v3.0
* License URI: http://www.gnu.org/licenses/gpl-3.0.html
*/
预防数据泄露
作为最佳实践,您的扩展的 PHP 文件应该在顶部包含一个条件语句,用于检查 WordPress 的 ABSPATH 常量。如果该常量未定义,则脚本应退出。
defined( 'ABSPATH' ) || exit;
此检查可防止您的 PHP 文件通过直接的浏览器访问而被执行,而是仅允许它们在 WordPress 应用程序环境中执行。
管理扩展的生命周期
由于您的主要 PHP 文件是您的扩展与 WordPress 之间的主要连接点,因此您应该将其用作管理扩展生命周期的中心。从最基本层面来看,这意味着处理以下内容:
- 激活
- 执行
- 停用
从这三个广泛的生命周期领域开始,您可以进一步细分您的扩展的功能,以帮助保持良好的职责分离。
处理激活和停用
在 WooCommerce 扩展中,一种常见的模式是在您的主要 PHP 文件中创建专门的函数,用作激活和停用的钩子。然后,您使用适用的注册函数将这些钩子注册到 WordPress。这告诉 WordPress 在插件被激活或停用时调用该函数。请考虑以下示例:
function my_extension_activate() {
// 您的激活逻辑放在此处。
}
register_activation_hook( __FILE__, 'my_extension_activate' );
function my_extension_deactivate() {
// 您的停用逻辑放在此处。
}
register_deactivation_hook( __FILE__, 'my_extension_deactivate' );
保持职责分离
有许多方法可以组织您扩展中的代码。您可以在 WordPress 插件开发手册中找到有关最佳实践的良好概述。无论您使用哪种方法来组织您的代码,WordPress 的共享应用程序空间都要求您在构建时考虑到互操作性。有一些常见的原则可以帮助您优化您的扩展,并确保它与其他扩展相协调:
- 使用命名空间和前缀以避免与其他扩展发生冲突。
- 使用类来封装您的扩展的功能。
- 检查现有的声明、作业和实现。
核心扩展类
如上所述,使用类封装您扩展的不同部分的功能是一个重要的措施,这不仅有助于互操作性,而且还可以使您的代码更易于维护和调试。您的扩展可能有很多不同的类,每个类都承担着一部分功能。至少,您的扩展应该定义一个中心类,该类可以处理自身的一个实例的设置、初始化和管理。
实现单例模式
除非您有特定的理由在扩展运行时创建主类的多个实例,否则您应该确保在任何时候全局范围内只有一个实例存在。 实现此目的的常见方法是使用单例模式。 在 PHP 类中设置单例的方法有很多种。 以下是一个基本的单例示例,它还实现了上面提到的关于命名空间和预先声明检查的一些最佳实践:
if ( ! class_exists( 'My_Extension' ) ) :
/**
* My Extension 核心类
*/
class My_Extension {
/**
* 类的单个实例。
*/
protected static $_instance = null;
/**
* 构造函数。
*/
protected function __construct() {
// 初始化逻辑将放在此处。
}
/**
* 主扩展实例。
* 确保只加载一个扩展实例或可以加载一个实例。
*/
public static function instance() {
if ( is_null( self::$_instance ) ) {
self::$_instance = new self();
}
return self::$_instance;
}
/**
* 禁止克隆。
*/
public function __clone() {
// 覆盖此 PHP 函数以防止对您的实例进行不需要的复制。
// 实现您自己的错误处理或使用 `wc_doing_it_wrong()`
}
/**
* 禁止反序列化此类实例。
*/
public function __wakeup() {
// 覆盖此 PHP 函数以防止对您的实例进行不需要的复制。
// 实现您自己的错误处理或使用 `wc_doing_it_wrong()`
}
}
endif;
请注意,上面的示例类是设计为通过调用静态类方法 instance() 来进行实例化,该方法将返回类的现有实例,或者创建一个实例并将其返回。 为了完全防止不需要的实例化,还需要覆盖内置的魔术方法 __clone() 和 __wakeup()。 您可以在此处实现您自己的错误日志记录,或者使用类似于 _doing_it_wrong() 的函数,该函数可以为您处理错误日志记录。 您也可以在此处使用 WooCommerce 的包装函数 wc_doing_it_wrong()。 确保您的代码首先检查该函数是否存在。
构造函数
上面的示例包含一个空的构造函数,用于演示。然而,在实际的 WooCommerce 扩展中,此构造函数应该处理一些重要的任务:
-
检查是否已安装 WooCommerce 及其他相关的依赖项。
-
调用一个设置方法,该方法加载您的类所依赖的其他文件。
-
调用一个初始化方法,该方法使您的类及其依赖项准备就绪。
如果我们基于上面的示例,它可能如下所示:
protected function __construct() {
$this->includes();
$this->init();
// 您可能还可以在此处包含一些后续设置步骤,例如显示激活提示。
}
加载依赖项
上面的 includes() 函数是您加载其他类依赖项的地方,通常是通过 include 或 require 语句。管理和加载外部依赖项的一种常见方法是使用 Composer 的自动加载功能,但您也可以单独加载特定的文件。您可以在 Composer 的文档中了解更多关于如何自动加载外部依赖项的信息。以下是一个使用 Composer 和内部包含的设置方法的示例:
public function includes() {
$loader = include_once dirname( __FILE__ ) . '/' . 'vendor/autoload.php';
if ( ! $loader ) {
throw new Exception( 'vendor/autoload.php 缺失,请运行 `composer install`' );
}
require_once dirname( __FILE__ ) . '/' . 'includes/my-extension-functions.php';
}
初始化
上面的 init() 函数是您应该处理加载的类的任何设置的地方。在此步骤中,您通常会执行任何初始的注册,例如与相关的操作或过滤器进行注册。您还可以在此处注册并加载您的扩展的 JavaScript 和样式文件。
以下是您的初始化方法可能的样子示例:
private function init() {
// 设置缓存管理。
new My_Extension_Cache();
// 初始化 REST API。
new My_Extension_REST_API();
// 设置电子邮件管理。
new My_Extension_Email_Manager();
// 注册到 `some-action` 钩子
add_action( 'some-action', 'my-extension-function' );
}
您的核心类的初始化方法可能有许多不同的实现方式,这取决于您选择的扩展架构方式。这里重要的是,此函数作为处理您的扩展所需的所有初始注册和设置的中心点。
延迟初始化
我们之前使用 register_activation_hook() 设置的 WordPress 激活钩子,可能看起来是一个实例化扩展主类的理想位置,并且在某些情况下,它确实有效。但是,由于 WooCommerce 扩展通常是为 WooCommerce 构建的插件,因此为了正常运行,通常需要 WooCommerce 在其他插件加载之后再进行加载。因此,最好延迟实例化和初始化。
为此,不要将实例化挂接到扩展的激活钩子上,而是使用 WordPress 中的 plugins_loaded 动作来实例化扩展的核心类,并将它的单例添加到 $GLOBALS 数组中。
function my_extension_initialize() {
// 这是一个检查 WooCommerce 类是否存在的好地方
if ( ! class_exists( 'WooCommerce' ) ) {
// 您可以以多种方式处理这种情况,
// 但添加一个 WordPress 管理员通知通常是一个不错的策略。
return;
}
$GLOBALS['my_extension'] = My_Extension::instance();
}
add_action( 'plugins_loaded', 'my_extension_initialize', 10 );
在上面的示例中,WordPress 会在尝试实例化您的核心类之前,等待所有插件都加载完毕。 add_action() 中的第三个参数表示函数的优先级,它最终决定了挂接到 plugins_loaded 动作的函数的执行顺序。 在这里使用值 10,可确保其他与 WooCommerce 相关的功能在您的扩展被实例化之前运行。
处理执行
一旦您的扩展处于活动状态并已初始化,那么可能性就非常广泛。 这就是扩展中的“魔法”发生的地方,很大程度上取决于您来定义。 虽然实现特定功能超出了本指南的范围,但在您思考如何构建扩展的功能时,有一些最佳实践需要牢记。
-
保持事件驱动的思维方式。 使用您的扩展的商家和购物者将通过 Web 请求与 WooCommerce 交互,因此,将您的扩展与用户在 WooCommerce 中遵循的关键流程相关联可能很有帮助。
-
将业务逻辑和表示逻辑分开。 这可能很简单,例如,维护单独的类来处理后端处理和前端渲染。
-
尽可能,将功能分解为更小的部分,并将责任委托给专门的类,而不是构建臃肿的类和冗长的函数。
您可以在 WooCommerce 核心代码参考文档中找到类和钩子的详细文档,以及在 WooCommerce REST API 文档中找到 REST API 端点的其他文档。
处理停用
我们在主 PHP 文件中设置的 WordPress 停用钩子 (使用 register_deactivation_hook() 函数),是一个很好的地方,可以汇总任何需要在商家停用您的扩展时执行的清理操作。 除了您的扩展需要执行的任何与 WordPress 相关的停用任务之外,您还应该考虑与 WooCommerce 相关的清理,包括:
- 移除计划行动
- 移除管理员收件箱中的通知
- 移除管理员任务
卸载
虽然完全撤销您的扩展所创建的所有内容在商家停用它时是可能的,但这在大多数情况下并不建议也不实用。 相反,最好将这种行为保留用于卸载操作。
对于处理卸载,最好遵循 WordPress 插件手册中的指南。
整合所有内容
以下是一个非常简单的扩展的插件主文件的示例:
/**
* 插件名称: My Great Extension for WooCommerce
* 插件URI: https://woocommerce.com/products/my-extension-name/
* Description: 您的扩展的描述文本。
* Version: 1.0.0
* Author: Your Name
* Author URI: http://yourdomain.com/
* Developer: Your Name
* Developer URI: http://yourdomain.com/
* Text Domain: my-extension-name
* Domain Path: /languages
*
* Woo: 12345:342928dfsfhsf8429842374wdf4234sfd
*
* License: GNU General Public License v3.0
* License URI: http://www.gnu.org/licenses/gpl-3.0.html
*/
defined( 'ABSPATH' ) || exit;
/**
* WordPress 的激活和反激活钩子
*/
function myPrefix_extension_activate() {
// 您的激活逻辑放在这里。
}
register_activation_hook( __FILE__, 'myPrefix_extension_activate' );
function myPrefix_extension_deactivate() {
// 您的反激活逻辑放在这里。
// 别忘了:
// 移除计划的操作
// 移除管理收件箱中的通知
// 移除管理任务
}
register_deactivation_hook( __FILE__, 'myPrefix_extension_deactivate' );
if ( ! class_exists( 'My_Extension' ) ) :
/**
* My Extension 核心类
*/
class My_Extension {
/**
* 类的单个实例。
*/
protected static $_instance = null;
/**
* 构造函数。
*/
protected function __construct() {
$this->includes();
$this->init();
}
/**
* 主扩展实例。
*/
public static function instance() {
if ( is_null( self::$_instance ) ) {
self::$_instance = new self();
}
return self::$_instance;
}
/**
* 复制是被禁止的。
*/
public function __clone() {
// 覆盖此 PHP 函数以防止对您的实例进行不需要的复制。
// 实现您自己的错误或使用 `wc_doing_it_wrong()`
}
/**
* 反序列化此类实例是被禁止的。
*/
public function __wakeup() {
// 覆盖此 PHP 函数以防止对您的实例进行不需要的复制。
// 实现您自己的错误或使用 `wc_doing_it_wrong()`
}
/**
* 用于加载依赖项的函数。
*/
private function includes() {
$loader = include_once dirname( __FILE__ ) . '/' . 'vendor/autoload.php';
if ( ! $loader ) {
throw new Exception( 'vendor/autoload.php 缺失,请运行 `composer install`' );
}
require_once dirname( __FILE__ ) . '/' . 'includes/my-extension-functions.php';
}
/**
* 用于设置所有内容并准备运行的函数。
*/
private function init() {
// 示例包括:
# 扩展初始化
// 设置缓存管理。
// new My_Extension_Cache();
// 初始化 REST API。
// new My_Extension_REST_API();
// 设置邮件管理。
// new My_Extension_Email_Manager();
// 注册到 `some-action` 钩子
// add_action('some-action', 'my-extension-function');
}
}
endif;
/**
* 用于延迟扩展的初始化,直到 WooCommerce 加载完毕。
*/
function my_extension_initialize() {
// 这是一个检查 WooCommerce 类是否存在的好地方。
if ( ! class_exists( 'WooCommerce' ) ) {
// 您可以用多种方式处理这种情况,
// 但在 WordPress 管理界面添加一个提示通常是一个不错的策略。
return;
}
$GLOBALS['my_extension'] = My_Extension::instance();
}
add_action( 'plugins_loaded', 'my_extension_initialize', 10 );