第 5 部分 – WordPress 和面向对象编程:一个 WordPress 示例 – 实现:管理菜单
已发表: 2022-02-04在我们之前关于面向对象编程的文章中,我们讨论了我们最终为我们的面向对象插件提出的设计。
现在,我们将进入最激动人心的部分,我们将更深入地研究我们是如何实现它的!
我们将一步一步地引导您完成实现的某些部分,讨论面向对象编程的基础知识、PHP 语法、一些核心概念,我们甚至会浏览 SOLID 原则。
在本文结束时,您有望对 OOP 有更好的理解,并对编写自己的面向对象插件感到兴奋!
入门
我们假设您通常熟悉 WordPress 插件开发,因此我们将专注于插件的面向对象方面。 如果您不熟悉插件开发或需要复习,您应该首先学习如何构建您的第一个 WordPress 插件。
让我们像往常一样开始,在我们的插件目录(即/wp-content/plugins/prsdm-limit-login-attempts)下创建一个新的prsdm-limit-login-attempts.php 文件。
主插件文件将包含您已经熟悉的插件标头:
/** * Plugin Name: PRSDM Limit Login Attempts * Plugin URI: https://pressidium.com * Description: Limit rate of login attempts, including by way of cookies, for each IP. * Author: Pressidium * Author URI: https://pressidium.com * Text Domain: prsdm-limit-login-attempts * License: GPL-2.0+ * Version: 1.0.0 */
还有一个简单的 if 语句来防止直接访问它。
if ( ! defined( 'ABSPATH' ) ) { exit; }
这就是我们现在所需要的。 我们稍后会重新访问此文件!
建立管理菜单
当你开发一个插件时,你经常需要为你的用户提供一种配置它的方法。 这就是设置页面的用武之地。为了构建一个,我们将添加一个使用 WordPress 设置 API 的管理菜单。
所以,让我们开始思考我们的面向对象API 的外观。
理想情况下,我们希望实例化我们的Pressidium_LLA_Settings_Page
并完成它。 要创建类的实例,必须使用new
关键字。
new Pressidium_LLA_Settings_Page();
现在,让我们考虑一下Pressidium_LLA_Settings_Page
类的外观。
我们将首先使用class
关键字创建一个新类:
class Pressidium_LLA_Settings_Page {}
我们的类名必须以唯一标识符Pressidium_LLA_
为前缀,以防止与其他 WordPress 插件发生任何命名冲突。 前缀可防止其他插件覆盖和/或意外调用我们的类。 只要我们的类名是唯一的——或者我们使用命名空间——就不会与其他插件发生任何冲突。
构造函数
现在,我们将连接到 admin_menu 和 admin_init。 为了简单起见,我们将在构造函数中调用 add_action() (剧透警告:我们稍后会更改)。
class Pressidium_LLA_Settings_Page { /** * Settings_Page constructor. */ public function __construct() { add_action( 'admin_menu', array( $this, 'add_page' ) ); add_action( 'admin_init', array( $this, 'register_sections' ) ); } }
具有构造函数的类在实例化对象时调用此方法。 因此, __construct()
方法非常适合我们可能想要执行的任何初始化。
让我们仔细看看我们的add_action()
调用。 如果您过去开发过 WordPress 插件,您可能会期待这样的事情:
add_action( 'admin_menu', 'my_plugin_prefix_add_page' );
但相反,我们有:
add_action( 'admin_menu', array( $this, 'add_page' ) );
您可能会对这里使用数组感到困惑。 每当我们想将实例化对象的方法作为回调/可调用传递时,我们可以使用一个数组,该数组包含索引 0 处的对象和索引 1 处的方法名称。
$this 是什么?
它是一个伪变量,当从对象上下文中调用方法时可用。 $this
是调用对象的值。 在这种情况下, $this
是Pressidium_LLA_Settings_Page
的一个实例。
另外,我们所有的“函数”现在都是方法,封装在一个类中,所以不需要在方法名称前加上前缀。
命名空间
PHP 中的命名空间允许我们对相关的类、接口、函数等进行分组,防止我们的代码与内部 PHP 或第三方类/函数之间的命名冲突。
让我们继续使用它们,这样我们就不必为我们的任何类添加前缀。
我们将使用namespace
关键字声明一个命名空间。
namespace Pressidium;
可以使用子级别定义命名空间。
namespace Pressidium\Limit_Login_Attempts;
由于我们正在构建一个设置页面,我们将声明一个“页面”子命名空间来将与管理页面相关的任何内容组合在一起。
namespace Pressidium\Limit_Login_Attempts\Pages;
我们终于可以摆脱Pressidium_LLA_
前缀了!
namespace Pressidium\Limit_Login_Attempts\Pages; class Settings_Page { // ...
另一个包含Settings_Page
类的 WordPress 插件不再是问题,因为它的类和我们的类将不在同一个命名空间中。
在同一个命名空间中实例化我们的Settings_Page
时,我们可以省略它:
namespace Pressidium\Limit_Login_Attempts\Pages; $settings_page = new Settings_Page();
在其命名空间之外实例化我们的Settings_Page
时,我们必须像这样指定它:
namespace Another\Namespace; $settings_page = new \Pressidium\Limit_Login_Attempts\Pages\Settings_Page();
或者,我们可以use
运算符导入我们的类:
use Pressidium\Limit_Login_Attempts\Pages\Settings_Page; $settings_page = new Settings_Page();
添加挂钩回调
现在,让我们声明这些add_page()
和register_sections()
方法。
class Settings_Page { /** * Settings_Page constructor. */ public function __construct() { add_action( 'admin_menu', array( $this, 'add_page' ) ); add_action( 'admin_init', array( $this, 'register_sections' ) ); } /** * Add this page as a top-level menu page. */ public function add_page() { // TODO: Implement this method. } /** * Register sections. */ public function register_sections() { // TODO: Implement this method. } }
我们的 add_page() 方法只会调用 add_menu_page() WordPress 函数。
public function add_page() { add_menu_page( __( 'Limit Login Attempts Settings', 'prsdm-limit-login-attempts' ), __( 'Limit Login Attempts', 'prsdm-limit-login-attempts' ), 'manage_options', 'prsdm_limit_login_attempts_settings', array( $this, 'render' ), 'dashicons-shield-alt', null ); }
这似乎是开发 WordPress 插件的一种复杂方式。 它只是调用 WordPress 函数,需要额外的步骤。
好吧,这并不完全是“可重用的”,我们仍然必须为我们想要添加的每个管理菜单/页面编写所有这些额外的代码。
重构
让我们继续对我们的代码进行一些重构,以利用面向对象的编程并使我们的代码可重用。 我们将首先用一些方法替换add_page()
中的硬编码值,如下所示:
public function add_page() { add_menu_page( $this->get_page_title(), // page_title $this->get_menu_title(), // menu_title $this->get_capability(), // capability $this->get_slug(), // menu_slug array( $this, 'render' ), // callback function $this->get_icon_url(), // icon_url $this->get_position() // position ); }
我们将这些方法定义为protected
,因此它们只能在类本身及其子/父类中访问。
protected function get_page_title() { /* ... */ } protected function get_menu_title() { /* ... */ } protected function get_capability() { /* ... */ } protected function get_slug() { /* ... */ } protected function get_icon_url() { /* ... */ } protected function get_position() { /* ... */ }
伟大的! 我们现在可以使用这个类作为一个可重用的泛型类来扩展。

重新设计
我们告诉过你,这可能最终会发生。 在这里,我们正在重新思考我们班级的设计,同时构建它。
由于这将是我们的基类,我们将把它重命名为更通用的名称,例如Admin_Page
。 到目前为止,它看起来像这样:
class Admin_Page { /** * Admin_Page constructor. */ public function __construct() { add_action( 'admin_menu', array( $this, 'add_page' ) ); add_action( 'admin_init', array( $this, 'register_sections' ) ); } /** * Add this page as a top-level menu page. */ public function add_page() { add_menu_page( $this->get_page_title(), // page_title $this->get_menu_title(), // menu_title $this->get_capability(), // capability $this->get_slug(), // menu_slug array( $this, 'render' ), // callback function $this->get_icon_url(), // icon_url $this->get_position() // position ); } /** * Register sections. */ public function register_sections() { // TODO: Implement this method. } protected function get_page_title() { /* ... */ } protected function get_menu_title() { /* ... */ } protected function get_capability() { /* ... */ } protected function get_slug() { /* ... */ } protected function get_icon_url() { /* ... */ } protected function get_position() { /* ... */ } }
我们现在可以创建一个单独的Settings_Page
来扩展该Admin_Page
基类。
class Settings_Page extends Admin_Page { // ... }
这是继承的一个很好的例子,它是面向对象编程的核心概念之一。 扩展类时,子类(在本例中为Settings_Page
)继承父类的所有公共和受保护方法、属性和常量。
我们可以利用它并设置一些默认值。 例如,我们将为所有菜单页面设置一个通用图标,通过像这样定义我们的get_icon_url()
方法:
class Admin_Page { // ... /** * Return the menu icon to be used for this menu. * * @link https://developer.wordpress.org/resource/dashicons/ * * @return string */ protected function get_icon_url() { return 'dashicons-admin-generic'; } }
除非一个类覆盖这些方法,否则它们将保留其原始功能。 因此,默认情况下,所有子类都将使用该通用图标。
但是,如果我们想为特定菜单页面设置另一个图标,我们可以简单地覆盖子类中的get_icon_url()
方法,如下所示:
class Settings_Page extends Admin_Page { protected function get_icon_url() { return 'dashicons-shield-alt'; } }
但是,对于每个子类,有些值必须不同。 例如,菜单 slug( add_menu_page()
的第四个参数)对于每个菜单页面应该是唯一的。
如果我们在Admin_Page
基类中定义这个方法,我们需要一种方法来确保每个子类都覆盖这个方法。 好吧,我们可以做得更好。 我们可以声明方法的签名并完全跳过它的实现。
输入抽象方法!
抽象类和方法
定义为抽象的方法只是声明方法的签名,它们不能定义它的实现。
/** * Return page slug. * * @return string */ abstract protected function get_slug();
任何包含至少一个抽象方法的类也必须是抽象的。 这意味着,我们的 Admin_Page 类也应该被定义为抽象类。
abstract class Admin_Page { // ...
在这里指出定义为抽象的类不能被实例化也很重要。 因此,我们不能再直接实例化Admin_Page
。
这也是该类的可视化:

从抽象类继承时,子类必须在其父类的声明中定义所有标记为抽象的方法。 这意味着,我们的Settings_Page
必须实现get_slug()
方法。
class Settings_Page extends Admin_Page { // ... protected function get_slug() { return 'prsdm_limit_login_attempts_settings'; } // ... }
同样,我们应该实现add_page()
需要的其余受保护方法。
在继续我们将如何注册管理页面的部分和字段并呈现它们的内容之前,让我们先谈谈 WordPress 中的设置。
设置 API
我们假设您已经熟悉 Settings API。 但是,以防万一,这里是它的要点:
- settings_fields() — 输出设置页面的 nonce、action 和 option_page 字段。 基本上,隐藏的表单字段。
- do_settings_sections() — 打印出添加到特定设置页面的所有设置部分(及其字段)。
- add_settings_section() — 将新部分添加到设置页面。
- add_settings_field() — 将新字段添加到设置页面的一部分。
- register_setting() — 注册一个设置及其数据。
如果您对此还不熟悉,可以暂停阅读本文并查看我们的相关文章,了解如何为自定义插件构建设置页面。
现在我们在同一页面上,让我们回到我们的register_sections()
方法。 再一次,我们必须退后一步,想想我们的 API。
由于我们已经在Admin_Page
类中定义了add_page()
方法,我们还将在那里定义render()
方法。 我们将把其他方法的返回值作为参数传递给 WordPress 函数。
abstract class Admin_Page { // ... /** * Render this admin page. */ public function render() { ?> <div class="wrap"> <form action="options.php" method="post"> <h1><?php echo esc_html( $this->get_page_title() ); ?></h1> <?php settings_fields( $this->get_slug() ); do_settings_sections( $this->get_slug() ); submit_button( __( 'Change Options', 'prsdm-limit-login-attempts' ) ); ?> </form> </div> <?php } }
这样,我们就不必再直接使用这些 WordPress 功能了。 那是因为我们将来可能添加的任何管理页面都将像Settings_Page
一样通过子类构建,并且其渲染将通过Admin_Page
父类的继承的render()
方法完成。
结论
伟大的! 我们创建了负责注册管理菜单和添加设置页面的类。
在本系列的下一篇文章中,我们将继续构建我们的设置页面并注册其部分、字段和元素。
单击此处阅读面向对象编程系列的第 6 部分
也可以看看
- WordPress 和面向对象的编程——概述
- 第 2 部分 – WordPress 和面向对象编程:一个真实世界的示例
- 第 3 部分 – WordPress 和面向对象编程:A WordPress 示例 – 定义范围
- 第 4 部分 – WordPress 和面向对象编程:一个 WordPress 示例 – 设计