在上一教程中,我们介绍了一些概念,所有这些概念对于全面了解我们在本教程中所做的工作都是必不可少的。
具体来说,我们涵盖了以下主题:
- 面向对象的接口
- 单一责任原则
- 这些在PHP中的外观
- 我们的插件的方向
在某些系列中,可以轻松跳过可能无法相互构建的教程。 但是,本系列并非如此。 相反,它应按顺序阅读,并以每个先前教程的内容为基础。
话虽如此,我想你们都被赶上了。
入门
即使我可能在第一个教程中已经提到了这一点,但我仍然想确保我们在每个教程中所做的工作以及所需的软件都在同一页上。
我们的路线图
因此,在本教程中,计划如下:
- 检查到目前为止我们编写的代码。
- 确定我们如何能够使用面向对象的技术对其进行重构。
- 提供我们实施的概要。
最终,在本教程中我们不会编写太多代码,但是我们会编写一些代码。 但是,它是实用的教程,因为我们正在执行面向对象的分析和设计。 对于许多大型项目(这是小型项目应该发生的),这是必不可少的阶段。
你需要什么
如果您一直在遵循,则应该已经进行了设置。 但是要确保,这是您需要的所有内容的简短版本:
- 适用于您的操作系统的本地开发环境
- WordPress 4.6.1所在的目录
- 文本编辑器或IDE
- WordPress插件API的知识
有了所有这些之后,我们就可以处理上一教程中共享的代码了。 因此,让我们开始吧。
分析代码
我们要做的第一件事是分析自动装带器的当前状态。 似乎将很多代码粘贴到单个代码块中,但是它本身向我们表明了我们还有一些工作要做。
话虽如此,这是自动装带器的当前状态:
<?php
function tutsplus_namespace_demo_autoload( $class_name ) {
// If the specified $class_name does not include our namespace, duck out.
if ( false === strpos( $class_name, 'Tutsplus_Namespace_Demo' ) ) {
return;
}
// Split the class name into an array to read the namespace and class.
$file_parts = explode( '\\', $class_name );
// Do a reverse loop through $file_parts to build the path to the file.
$namespace = '';
for ( $i = count( $file_parts ) - 1; $i > 0; $i-- ) {
// Read the current component of the file part.
$current = strtolower( $file_parts[ $i ] );
$current = str_ireplace( '_', '-', $current );
// If we're at the first entry, then we're at the filename.
if ( count( $file_parts ) - 1 === $i ) {
/* If 'interface' is contained in the parts of the file name, then
* define the $file_name differently so that it's properly loaded.
* Otherwise, just set the $file_name equal to that of the class
* filename structure.
*/
if ( strpos( strtolower( $file_parts[ count( $file_parts ) - 1 ] ), 'interface' ) ) {
// Grab the name of the interface from its qualified name.
$interface_name = explode( '_', $file_parts[ count( $file_parts ) - 1 ] );
$interface_name = $interface_name[0];
$file_name = "interface-$interface_name.php";
} else {
$file_name = "class-$current.php";
}
} else {
$namespace = '/' . $current . $namespace;
}
}
// Now build a path to the file using mapping to the file location.
$filepath = trailingslashit( dirname( dirname( __FILE__ ) ) . $namespace );
$filepath .= $file_name;
// If the file exists in the specified path, then include it.
if ( file_exists( $filepath ) ) {
include_once( $filepath );
} else {
wp_die(
esc_html( "The file attempting to be loaded at $filepath does not exist." )
);
}
}
在这一点上,请记住单一责任原则规定如下:
一个班级只有一个改变的理由。
现在,我们甚至都没有一个类,更不用说只有一个改变理由的多个单独方法。
尽管将这种自动加载器方法分解为较小的单个方法可能会很有意义,但让我们从更高的层次开始,并开始考虑接口方面的自动加载器。 然后,我们将深入研究创建一个或多个类。
面向对象的分析:职责
回想一下以前的教程,PHP手册定义了一个接口,如下所示:
对象接口使您可以创建代码,该代码指定类必须实现的方法,而不必定义如何处理这些方法。
给定上面的代码和定义,让我们从更加模块化的角度考虑自动装带器需要做什么。 具体来说,让我们将其细分为代表可能足以更改的点。 不,我们可能不会使用所有这些要点,但这就是为什么将其称为分析。 稍后我们将进行设计。
该代码执行以下操作:
- 验证我们是否在使用命名空间。
- 将传入的类名拆分为多个部分,以确定它是类还是接口(因此
$class_name
是不良的变量名)。 - 检查我们是否正在使用接口文件。
- 检查我们是否正在使用类文件。
- 检查我们是否正在使用接口。
- 根据上述条件的结果,生成文件名。
- 根据生成的文件名构建文件路径。
- 如果文件以生成的名称存在,则将其包括在内。
- 否则,代码将产生错误。
因此,上面的代码在完成工作之前会做九件事 -也就是说, 至少有九个原因需要更改。
不用说,但是这个特殊的功能是一个完美的例子,我们可以重构它来演示面向对象的分析,设计,接口和实现。
这就提出了一个问题:我们从哪里开始?
面向对象的分析
在这一点上,可以很公平地说,鉴于上面列出的所有内容,我们可以开始进行面向对象的分析,即查看可能具有的潜在类以及它们之间的交互方式。 请记住,我们还希望单一责任原则可以帮助指导我们进行决策。
在这一点上,我们并不十分担心类之间如何通信。 取而代之的是,我们更加专注于创建具有更改唯一原因的类。
话虽如此,我将提供一些我认为可能有用的类示例。 在继续之前,请查看我们已完成的工作并尝试提出您自己的列表。 然后我们可以比较笔记。
关于技能的话
请注意,您可能比下面列出的有更好的主意,或者您可能会从我们共享的内容中删除一些东西。 无论如何,这是一个学习练习。 我们正在尝试改善我们的代码,我们的组织,并最终成为更好的程序员。
我们的潜在课程
鉴于上面列出的内容,我提出了以下课程:
- 自动装带器 。 这是负责最终包括我们的类,名称空间或接口的主要类。 我们称这堂课。 其余的类将负责这一类需要包括文件的必要工作。
- NamespaceValidator 。 该文件将查看传入的类,接口或您所拥有的内容,并将确定其是否有效。 如果我们可以继续进行其余的代码,这将为我们提供决定性因素,否则我们将继续。
- FileInvestigator 。 此类查看正在传递到自动加载器中的文件的类型。 它将确定它是类,接口还是名称空间,并将完全限定的路径名返回到文件,以便将其包括在内。
- FileRegistry 。 这将使用最终从其他类返回的标准文件路径,并将其包含在插件中。
就是这样。 现在,我们插件中的第三方类仅需要了解autoloader类,但是autoloader将需要其他类的知识,而其他类则需要其他类的知识。
有办法解决这个(使用依赖注入容器,但是这超出了本项目的范围)。 但是,我们将通过代码来实现的目标是,使有多少个类彼此了解。
面向对象设计
在这一点上,不同的开发人员,公司,代理和团队将采用不同的方法来设计工作系统。
执行此操作的最常见方法之一是使用一种称为UML图的方法。 尽管它很有用,但在本教程的范围内并不是值得做的事情,因为它将需要一个完整的其他教程来解释所有内容。
因此,出于本教程的目的,并且由于我们仅使用少量的代码,因此在实现它们之前,我们将尝试弄清楚上述每个类的工作方式。 这样,我们将了解如何组织代码。
请注意,我们暂时不会命名任何这些代码,并且这些代码中的任何一个都不应针对WordPress实施或测试。 我们将在下一个教程中进行介绍。
让我们从Autoloader
带器开始,然后从那里开始工作。
自动装带器
请记住,此类负责包含必要的文件。 这是将用spl_autoload_register
函数注册的文件。
<?php
class Autoloader {
private $namespace_validator;
private $file_registry;
public function __construct() {
$this->namespace_validator = new NamespaceValidator();
$this->file_registry = new FileRegistry();
}
public function load( $filename ) {
if ( $this->namespace_validator->is_valid( $filename ) ) {
$this->file_registry->load( $filename );
}
}
}
请注意,此类取决于NamespaceValidator
和FileRegistry
类。 我们稍后将详细介绍其中的每一个。
命名空间验证器
该文件将查看传入的文件名并确定其是否有效。 这是通过查看文件名中的名称空间来完成的。
<?php
class NamespaceValidator {
public function is_valid( $filename ) {
return ( 0 === strpos( $filename, 'Tutsplus_Namespace_Demo' ) );
}
}
如果文件确实属于我们的命名空间,那么我们可以假定加载文件是安全的。
FileInvestigator
此类的工作量很大,尽管其中一部分是通过非常简单,非常小的帮助程序方法完成的。 在执行过程中,它会检查传递的文件类型。
然后,它检索文件类型的标准文件名。
<?php
class FileInvestigator {
public function get_filetype( $filename ) {
$filepath = '';
for ( $i = 1; $i < count( $file_parts ); $i++ ) {
$current = strtolower( $file_parts[ $i ] );
$current = str_ireplace( '_', '-', $current );
$filepath = $this->get_file_name( $file_parts, $current, $i );
if ( count( $file_parts ) - 1 !== $i ) {
$filepath = trailingslashit( $filepath );
}
}
return $filepath;
}
private function get_file_name( $file_parts, $current, $i ) {
$filename = '';
if ( count( $file_parts ) - 1 === $i ) {
if ( $this->is_interface( $file_parts ) ) {
$filename = $this->get_interface_name( $file_parts );
} else {
$filename = $this->get_class_name( $current );
}
} else {
$filename = $this->get_namespace_name( $current );
}
return $filename;
}
private function is_interface( $file_parts ) {
return strpos( strtolower( $file_parts[ count( $file_parts ) - 1 ] ), 'interface' );
}
private function get_interface_name( $file_parts ) {
$interface_name = explode( '_', $file_parts[ count( $file_parts ) - 1 ] );
$interface_name = $interface_name[0];
return "interface-$interface_name.php";
}
private function get_class_name( $current ) {
return "class-$current.php";
}
private function get_namespace_name( $current ) {
return '/' . $current;
}
}
如果有一个文件可以重构得更多,那就是它。 毕竟,它试图确定我们是在使用类,接口还是类。 一个简单的工厂可能更适合于此。
当需要实现我们的代码时,也许我们将进一步重构它。 在此之前,这是一个初步的设计,可能效果很好。
文件注册
这将使用完全限定的文件路径并包含文件。 否则,它将使用WordPress API来显示错误消息。
class FileRegistry {
private $investigator;
public function __construct() {
$this->investigator = new FileInvestigator();
}
public function load( $filepath ) {
$filepath = $this->investigator->get_filetype( $filepath );
$filepath = rtrim( plugin_dir_path( dirname( __FILE__ ) ), '/' ) . $filepath;
if ( file_exists( $filepath ) ) {
include_once( $filepath );
} else {
wp_die(
esc_html( 'The specified file does not exist.' )
);
}
}
}
使用WordPress API的另一种替代方法是引发自定义异常消息。 这样,我们就可以将我们的代码与WordPress完全分离或分离。
同样,此代码是初始自动加载器的结转。 在实施过程中,我们也可能会对此进行更改。
结论
好了,所以我们看了一下自动加载器的现有代码,然后根据一些面向对象的分析和设计,挑选了一些可以使用的潜在代码。
我们正在努力的解决方案是否比现有解决方案更具可维护性? 绝对。 这将在WordPress和我们现有的插件的上下文中起作用吗? 直到我们开始将其连接到插件中,我们才知道。
如前所述,我们仍然可以在某些区域重构此代码。 如果在插件的最终版本中实现代码时遇到此类问题,我们将仔细研究一下。
无论如何,我们现在拥有的代码应该更具可读性(尽管我们仍然有DocBlocks和一些内联注释要引入),并且更具可维护性,甚至更具可测试性。
综上所述,我希望这使您对如何采用长方法并将其分解为更多目的驱动的类有所了解。 当然,一开始有多个类可能会觉得很奇怪,但这并不意味着这是一件坏事。 具有更多代码的文件(因此是类)比具有大量代码的文件更好。
在这方面,应拥抱面向对象编程的违反直觉的本质。 在下一个教程中,我们将返回到我们的插件,并将致力于实现上述代码的变体。 我们可能还会调试其中的一些。 毕竟,很少有我们第一次就能做到
在此之前,如果您有兴趣阅读有关WordPress上下文中的面向对象编程的更多信息,可以在我的个人资料页面上找到我以前的所有教程。 请随时在我的博客上关注我,或者在Twitter上关注我,我经常谈论这两者。
翻译自: https://code.tutsplus.com/tutorials/object-oriented-autoloading-in-wordpress-part-2--cms-27431