在本系列中,我们专注于构建可维护的 WordPress 元框。我的意思是,我们一直在努力创建一个组织良好、遵循 WordPress 编码标准的 WordPress 插件,并且随着项目的不断进展,可以轻松调整和维护。
尽管我们已经实施了一些好的实践,但仍然有重构的空间。对于这个系列,这是设计使然的。每当您为客户或大公司开发项目时,您必须维护现有代码库的可能性相当高。因此,我希望我们能够返回到我们的代码库,以便改进我们编写的一些代码。
请注意,本文不会按照其他文章的格式编写 - 也就是说,不会采用“首先我们这样做,然后我们这样做”的开发方法。相反,我们将突出显示需要重构的几个领域,然后独立于我们正在进行的其他更改来处理它们。
需要明确的是,重构行为(按照维基百科的定义)是:
重构改进了软件的非功能属性。优点包括提高代码可读性和降低复杂性以提高源代码可维护性,并创建更具表现力的内部架构或对象模型以提高可扩展性。
简而言之,它使代码更具可读性、更简单、更易于遵循,并且从最终用户的角度来看,这一切都无需改变代码的行为。
这可以通过多种不同的方式来实现,每种方式对于给定的项目都是独特的。在我们的例子中,我们将考虑重构我们的构造函数、一些保存方法、一些辅助方法等等。
最终,我们的目标是展示一些可以在您未来的 WordPress 工作中使用的策略。我的目标是在本文中尽可能详细地介绍;但是,请注意,可能存在未涵盖的其他重构机会。
如果是这样的话,那就太好了!请随意在您自己的代码库实例上制作它们。话虽如此,让我们开始吧。
如果您看一下我们的构造函数:
<?php public function __construct( $name, $version ) { $this->name = $name; $this->version = $version; $this->meta_box = new Authors_Commentary_Meta_Box(); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_styles' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) ); }
请注意,它当前正在执行两件事:
在 WordPress 插件的构造函数上下文中查看钩子设置是常见的做法,但这并不是一个很好的地方。
应该使用构造函数来初始化与给定类相关的所有属性,以便当用户实例化一个类时,他/她拥有使用该类所需的一切。
由于他们可能不想在初始化类时注册钩子,因此我们需要将其抽象为自己的 initialize_hooks
方法。我们的代码现在应该如下所示:
<?php public function __construct( $name, $version ) { $this->name = $name; $this->version = $version; $this->meta_box = new Authors_Commentary_Meta_Box(); } public function initialize_hooks() { add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_styles' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) ); }
之后,我们需要确保更新authors-commentary.php的核心代码,以便它正确实例化并注册钩子。
<?php function run_author_commentary() { $author_commentary = new Author_Commentary_Admin( 'author-commentary', '1.0.0' ); $author_commentary->initialize_hooks(); } run_author_commentary();
这里,主要区别在于我们更新了传递给主类的版本号,并且我们还在 initialize_hooks
函数“内联”>run_author_commentary 函数“内联”>run_author_commentary。
如果您现在执行代码,一切都应该与重构之前完全一样。
我还想补充一点,您可以有一个单独的类负责协调挂钩和回调,以便该责任位于单独的类中。尽管我很喜欢这种方法,但这超出了本文的范围。
接下来,让我们对 class-authors-commentary-meta-box.php
执行相同的操作。我们可以简单地重命名构造函数,而不是创建一个新函数,因为构造函数实际上并不执行任何操作。这意味着我们的代码应该如下所示:
<?php public function __construct() { add_action( 'add_meta_boxes', array( $this, 'add_meta_box' ) ); add_action( 'save_post', array( $this, 'save_post' ) ); }
对此:
<?php public function initialize_hooks() { add_action( 'add_meta_boxes', array( $this, 'add_meta_box' ) ); add_action( 'save_post', array( $this, 'save_post' ) ); }
我们需要做的最后一个更改是更新主类中的构造函数,以便它现在读取我们在主插件类中创建的 initialize_hooks
函数的内部。
<?php public function initialize_hooks() { $this->meta_box->initialize_hooks(); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_styles' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) ); }
再次刷新页面,您的插件应该仍然可以像重构之前一样正常运行。
在 Authors_Commentary_Meta_Box
类中,我们在 save_post
函数中有许多非常多余的条件。发生这种情况时,通常意味着大部分功能可以抽象为辅助函数,然后从最初放置它们的函数内调用。
让我们看一下现在的代码:
<?php public function save_post( $post_id ) { /* If we're not working with a 'post' post type or the user doesn't have permission to save, * then we exit the function. */ if ( ! $this->is_valid_post_type() || ! $this->user_can_save( $post_id, 'authors_commentary_nonce', 'authors_commentary_save' ) ) { return; } // If the 'Drafts' textarea has been populated, then we sanitize the information. if ( ! empty( $_POST['authors-commentary-drafts'] ) ) { // We'll remove all white space, HTML tags, and encode the information to be saved $drafts = trim( $_POST['authors-commentary-drafts'] ); $drafts = esc_textarea( strip_tags( $drafts ) ); update_post_meta( $post_id, 'authors-commentary-drafts', $drafts ); } else { if ( '' !== get_post_meta( $post_id, 'authors-commentary-drafts', true ) ) { delete_post_meta( $post_id, 'authors-commentary-drafts' ); } } // If the 'Resources' inputs exist, iterate through them and sanitize them if ( ! empty( $_POST['authors-commentary-resources'] ) ) { $resources = $_POST['authors-commentary-resources']; $sanitized_resources = array(); foreach ( $resources as $resource ) { $resource = esc_url( strip_tags( $resource ) ); if ( ! empty( $resource ) ) { $sanitized_resources[] = $resource; } } update_post_meta( $post_id, 'authors-commentary-resources', $sanitized_resources ); } else { if ( '' !== get_post_meta( $post_id, 'authors-commentary-resources', true ) ) { delete_post_meta( $post_id, 'authors-commentary-resources' ); } } // If there are any values saved in the 'Published' input, save them if ( ! empty( $_POST['authors-commentary-comments'] ) ) { update_post_meta( $post_id, 'authors-commentary-comments', $_POST['authors-commentary-comments'] ); } else { if ( '' !== get_post_meta( $post_id, 'authors-commentary-comments', true ) ) { delete_post_meta( $post_id, 'authors-commentary-comments' ); } } }
除了开始的方法太长之外,我们还可以清理一些东西:
not
和逻辑 OR
运算符的初始条件$_POST
数组中是否存在信息的条件因此,让我们分别看看其中的每一个,并致力于重构此函数。
第一个条件检查的目的是确保当前用户能够将数据保存到给定的帖子。现在,我们实际上是在检查当前帖子类型是否是有效的帖子类型,以及用户是否有权保存给定 WordPress 传递的当前随机数值。
现在,代码如下:
如果这不是有效的帖子类型或用户没有保存权限,则退出此功能。
这并不是很糟糕,但绝对可以改进。让我们将其合并到单个评估中,而不是使用 OR
,使其显示为:
如果用户没有保存权限,则退出此功能。
幸运的是,这是一个相对容易的修复。由于保存的帖子类型有助于确定用户是否有权保存帖子,因此我们可以将该逻辑移至 user_can_save
函数中。
因此,让我们将 is_valid_post_type
函数移至 user_can_save
函数中:
<?php private function user_can_save( $post_id, $nonce_action, $nonce_id ) { $is_autosave = wp_is_post_autosave( $post_id ); $is_revision = wp_is_post_revision( $post_id ); $is_valid_nonce = ( isset( $_POST[ $nonce_action ] ) && wp_verify_nonce( $_POST[ $nonce_action ], $nonce_id ) ); // Return true if the user is able to save; otherwise, false. return ! ( $is_autosave || $is_revision ) && $this->is_valid_post_type() && $is_valid_nonce; }
现在,负责确定用户是否可以保存帖子元数据的所有逻辑都封装在专门设计用于精确评估的函数中。
我们从这个开始:
<?php if ( ! $this->is_valid_post_type() || ! $this->user_can_save( $post_id, 'authors_commentary_nonce', 'authors_commentary_save' ) ) { return; }
现在我们有这个:
<?php if ( ! $this->user_can_save( $post_id, 'authors_commentary_nonce', 'authors_commentary_save' ) ) { return; }
阅读起来容易多了,不是吗?
接下来,在开始清理、验证和保存(或删除)元数据之前,我们将检查 $_POST
集合以确保数据确实存在。
我们可以编写一个小的辅助函数来为我们处理这个评估。虽然我们本质上是编写了一些代码,使我们的评估更加冗长,但与我们直接保留它们相比,条件语句读起来会更清晰一些。
首先,引入以下函数(注意它接受一个参数):
<?php /** * Determines whether or not a value exists in the $_POST collection * identified by the specified key. * * @since 1.0.0 * * @param string $key The key of the value in the $_POST collection. * @return bool True if the value exists; otherwise, false. */ private function value_exists( $key ) { return ! empty( $_POST[ $key ] ); }
接下来,重构最初调用 的所有调用!空( $_POST[ ... ] )
以便他们利用此功能。
例如,函数调用应如下所示:
if ( $this->value_exists( 'authors-commentary-comments' ) ) { // ... } else { // ... }
请注意,在该函数中放置的整个条件中,如果值不存在,则每次删除帖子元数据的评估看起来都完全相同。
例如,我们每次都会看到这样的东西:
<?php if ( '' !== get_post_meta( $post_id, 'authors-commentary-comments', true ) ) { delete_post_meta( $post_id, 'authors-commentary-comments' ); }
这显然是重构代码的机会。因此,让我们创建一个名为 delete_post_meta
的新函数,并让它封装所有这些信息:
<?php /** * Deletes the specified meta data associated with the specified post ID * based on the incoming key. * * @since 1.0.0 * @access private * @param int $post_id The ID of the post containing the meta data * @param string $meta_key The ID of the meta data value */ private function delete_post_meta( $post_id, $meta_key ) { if ( '' !== get_post_meta( $post_id, $meta_key, true ) ) { delete_post_meta( $post_id, '$meta_key' ); } }
现在我们可以返回并替换所有 else 条件评估以调用此单个函数,使其读取如下内容:
<?php // If the 'Drafts' textarea has been populated, then we sanitize the information. if ( $this->value_exists( 'authirs-commentary-drafts' ) ) { // We'll remove all white space, HTML tags, and encode the information to be saved $drafts = trim( $_POST['authors-commentary-drafts'] ); $drafts = esc_textarea( strip_tags( $drafts ) ); update_post_meta( $post_id, 'authors-commentary-drafts', $drafts ); } else { $this->delete_post_meta( $post_id, 'authors-commentary-drafts' ); }
此时,我们实际上只有这部分代码的另一个方面需要重构。
现在,保存帖子元数据的方式是通过评估 $_POST
集合中数据是否存在的过程来完成的,并根据信息类型对其进行清理,然后将其保存到帖子元数据中。
理想情况下,我们希望在自己的函数中清理数据,并将帖子元数据保存在自己的函数中。因此,我们需要引入新的功能。
首先,让我们进行消毒工作。因为我们正在处理 textareas
和数组,所以我们需要通过几种方法来处理清理调用。由于我们要么使用数组,要么不使用数组,所以我们可以创建一个函数,该函数接受一个可选参数,表示我们是否正在使用数组。
如果我们不使用数组,那么我们会将传入的数据视为文本;否则,我们会将其视为数组:
<?php /** * Sanitizes the data in the $_POST collection identified by the specified key * based on whether or not the data is text or is an array. * * @since 1.0.0 * @access private * @param string $key The key used to retrieve the data from the $_POST collection. * @param bool $is_array Optional. True if the incoming data is an array. * @return array|string The sanitized data. */ private function sanitize_data( $key, $is_array = false ) { $sanitized_data = null; if ( $is_array ) { $resources = $_POST[ $key ]; $sanitized_data = array(); foreach ( $resources as $resource ) { $resource = esc_url( strip_tags( $resource ) ); if ( ! empty( $resource ) ) { $sanitized_data[] = $resource; } } } else { $sanitized_data = ''; $sanitized_data = trim( $_POST[ $key ] ); $sanitized_data = esc_textarea( strip_tags( $sanitized_data ) ); } return $sanitized_data; }
接下来,我们可以更新清理调用以使用此方法。但在此之前,我们还需要编写一个小助手,负责使用经过净化的输入更新帖子元数据:
<?php private function update_post_meta( $post_id, $meta_key, $meta_value ) { if ( is_array( $_POST[ $meta_key ] ) ) { $meta_value = array_filter( $_POST[ $meta_key ] ); } update_post_meta( $post_id, $meta_key, $meta_value ); }
现在我们可以更新之前在函数中使用的所有条件,如下所示:
<?php public function save_post( $post_id ) { if ( ! $this->user_can_save( $post_id, 'authors_commentary_nonce', 'authors_commentary_save' ) ) { return; } if ( $this->value_exists( 'authors-commentary-drafts' ) ) { $this->update_post_meta( $post_id, 'authors-commentary-drafts', $this->sanitize_data( 'authors-commentary-drafts' ) ); } else { $this->delete_post_meta( $post_id, 'authors-commentary-drafts' ); } if ( $this->value_exists( 'authors-commentary-resources' ) ) { $this->update_post_meta( $post_id, 'authors-commentary-resources', $this->sanitize_data( 'authors-commentary-resources', true ) ); } else { $this->delete_post_meta( $post_id, 'authors-commentary-resources' ); } if ( $this->value_exists( 'authors-commentary-comments' ) ) { $this->update_post_meta( $post_id, 'authors-commentary-comments', $_POST['authors-commentary-comments'] ); } else { $this->delete_post_meta( $post_id, 'authors-commentary-comments' ); } }
请注意,我们实际上可以进一步重构这个特定的部分,这样就没有那么多的条件,但是考虑到文章的长度、时间的长度,并且还尝试引入一些其他策略,这将是留作练习,在您自己的时间完成。
到目前为止,我们已经完成了我们的插件。我们编写了一个插件,引入了一个元框,为撰写博客文章的作者提供选项。
此外,我们还采用了 WordPress 编码标准、一些强大的文件组织策略,并创建了许多辅助方法和抽象,这将帮助我们在未来的开发中维护这个特定的插件。
由于突出显示每一个重构机会并不容易,因此可能还需要进行其他更改。在您自己的时间里,请随意尝试自己实现其中一些。
总的来说,我希望您喜欢本系列并从中学到很多东西,并且我希望它能帮助您在未来基于 WordPress 的项目中编写更好、更易于维护的代码。
以上是重构:增强 WordPress 元框以实现长期维护的详细内容。更多信息请关注PHP中文网其他相关文章!