使用 OOP 简化 WordPress 的 functions.php
我非常喜欢 WordPress,我的大多数客户网站都用它。它内置的功能、丰富的插件生态系统以及海量的学习资源/文档,让我可以花更多时间为每个项目构建独特的功能,而无需浪费时间重复造轮子。
话虽如此,WordPress 并非完美无缺。就像 PHP 本身一样,WordPress 的 API 也常常显得笨重且不一致。我花了很多时间在 Google 上搜索我经常使用的函数和动作钩子,因为我记不住它们的名字。WordPress 的这方面并不理想。
当我学习 Laravel 时,我发现使用简洁优雅的面向对象 API 可以带来多么棒的编码体验。这种感觉就像毒品一样:一旦尝到,就上瘾了。我想在我的 WordPress 开发中加入一些这种甜蜜的面向对象编程(简称 OOP)元素。
进入functions.php
。
我维护着一个基于Underscores的入门主题,用于我的大部分项目,functions.php
感觉它很笨重。我的函数文件相当典型,与Underscores 的 functions.php文件很接近。以下是它的功能概要:
- 钩子内部
after_setup_theme
:- 添加
title-tag
对、custom-logo
、post-thumbnails
和使用 的组件customize-selective-refresh-widgets
数组的主题支持。html5
add_theme_support()
- 使用注册导航菜单
register_nav_menus()
。 - 使用添加图像尺寸
add_image_size()
。
- 添加
- 钩子内部
wp_enqueue_scripts
:- 将样式和脚本与其各自的
wp_enqueue_style()
功能一起排队wp_enqueue_script()
。
- 将样式和脚本与其各自的
- 包含相关文件。
再说一次,这是一个相当典型的示例functions.php
,没有任何问题。但是,我对它的设置有几个问题:
- 记忆不是我最大的优势,记住哪些函数以单词add、register和wp_enqueue开头对我来说是不可能的。
- 动作钩子默默地失败了,我无法告诉你我输入了多少次
after_theme_setup
而不是after_setup_theme
。 - 我实际上在每个项目中都为完全相同的内容添加了主题支持,并且我真的不希望相同的样板弄乱我的
functions.php
代码。
让我们退一步考虑一下functions.php
代码在这里实际上做了什么。
仔细想想,所有正在发生的事情都是对主题本身执行某种操作。如果我们有一个主题对象,可以用一个简单的面向对象的 API 来执行这些操作,那会怎么样?
一个简单的面向对象的 API
我不想记住哪些函数以add、register或wp_enqueue开头。事实上,所有这些函数本质上都做同一件事:它们为主题添加一些内容。所以我将所有这些函数都用“add”这个词来表示。我想添加主题支持、添加导航菜单、添加图片大小以及添加脚本。
我很懒,跟我斗吧。
我希望我的functions.php
外观或多或少像这样:
<?php // functions.php
require get_template_directory() . '/classes/theme-class.php';
$theme = new MyTheme;
$theme->addNavMenus([
'menu-1' => 'Primary',
]);
$theme->addSupport('post-thumbnails');
$theme->addImageSize('full-width', 1600);
$theme->addStyle('theme-styles', get_stylesheet_uri())
->addScript('theme-script', get_template_directory_uri() . '/js/custom.js');
无需再记忆钩子名称和函数前缀。欢呼吧!
非常清楚的是,所有这些方法都是对主题本身采取行动。
那么让我们来构建它。
我们首先在新文件中定义一个主题类并构建一个addNavMenus()
方法。本质上,我们只是围绕现有的 WordPress 钩子和函数构建包装器,所以这应该不会太复杂。
<?php // theme-class.php
class MyTheme
{
public function addNavMenus($locations = array())
{
add_action('after_setup_theme',function() use ($locations){
register_nav_menus($locations);
});
}
}
让我们来探究一下这里发生的事情。
我们定义我们的类MyTheme
,为其创建一个公共方法addNavMenus()
并赋予它与 WordPress 函数相同的参数register_nav_menus()
。
在方法内部,我们为after_setup_theme
钩子添加了一个动作,并创建一个闭包(PHP 风格的匿名函数),在其中调用 WordPressregister_nav_menu()
函数。$locations
变量使用 PHP 的关键字传递到闭包中use
,否则变量将超出闭包的作用域。
附注:从 PHP 5.3 开始支持闭包,我 90% 的时间都是通过闭包来与 WordPress 钩子交互,以避免全局命名空间混乱。然而,尽管 WordPress 采用了 React.js 等现代技术,但 WordPress 官方仍然保持了对 PHP 5.2 的向后兼容性,而 PHP 5.2 已于 2011 年 1 月停用🤷♂️
减少钩子故障
在该方法中addNavMenus()
,我们解决了上面定义的第一个问题:简化了 API(不再需要记住像register这样的前缀)。但我们仍然面临第二个问题:拼写错误的钩子会默默地失败。当我构建主题类的方法时,我可能会在某个时候把 写成after_theme_setup
而不是after_setup_theme
某个地方,而我却没有注意到。
让我们通过创建一个触发after_setup_theme
动作钩子的私有方法来解决这个问题,然后在方法中调用该方法addNavMenus()
而不是add_action()
函数。
<?php // theme-class.php
class MyTheme
{
private function actionAfterSetup($function)
{
add_action('after_setup_theme', function() use ($function) {
$function();
});
}
public function addNavMenus($locations = array())
{
$this->actionAfterSetup(function() use ($locations){
register_nav_menus($locations);
});
}
}
这真是太酷了:我们将闭包传入addNavMenus()
方法actionAfterSetup()
,然后通过关键字将其传递给闭包use
,最后在闭包内部通过变量名调用代码。多么神奇的魔法啊!
...如果该描述根本没有意义,只需研究代码就不会太糟糕。
我在方法前面加上了“action”这个词来告诉我这是一个动作钩子,并且我将其设为私有,因为它只能在类中使用。
这解决了我们的第二个问题:如果我们输入了actionAfterSetup()
错误的函数名,它将不再默默地失败。现在我们只需要在一个地方确保钩子名称正确即可。
让我们添加更多方法!
抽象项目间共享的代码
我几乎在每个项目上都添加了对相同功能的主题支持:title-tag
、、、以及一系列组件custom-logo
。post-thumbnails
customize-selective-refresh-widgets
html5
让我们为该add_theme_support()
函数添加一个包装器。我们直接调用它addSupport()
,因为在主题类中包含“theme”这个词感觉有些多余。实现之后,我们将看到如何抽象一些重复的代码。
函数的驱动代码add_theme_support()
有点奇怪:它计算传入的参数数量来决定执行什么操作。因此,我们将在包装器中设置一个条件,判断第二个参数是否设置,并且只有当第二个参数有值时才传入它。
<?php // theme-class.php
class MyTheme
{
/** Previous code truncated for clarity */
public function addSupport($feature, $options = null)
{
$this->actionAfterSetup(function() use ($feature, $options) {
if ($options){
add_theme_support($feature, $options);
} else {
add_theme_support($feature);
}
});
}
}
此外,我希望能够将其中几种方法链接在一起,因此我将拥有addSupport()
并且所有其他公共方法都返回$this
。
<?php // theme-class.php
class MyTheme
{
/** Previous code truncated for clarity */
public function addSupport($feature, $options = null)
{
$this->actionAfterSetup(function() use ($feature, $options) {
if ($options){
add_theme_support($feature, $options);
} else {
add_theme_support($feature);
}
});
return $this;
}
}
现在我们已经实现了添加主题支持的方法,接下来,让我们通过创建一组可用的主题默认设置,来抽象出每个项目所需的设置。我们可以通过使用类构造函数来实现这一点。
<?php // theme-class.php
class MyTheme
{
/** Previous code truncated for clarity */
public function __construct()
{
$this->addSupport('title-tag')
->addSupport('custom-logo')
->addSupport('post-thumbnails')
->addSupport('customize-selective-refresh-widgets')
->addSupport('html5', [
'search-form',
'comment-form',
'comment-list',
'gallery',
'caption'
]);
}
}
因为这些现在在类构造函数中,所以一旦主题对象被实例化,这段代码就会立即执行,这意味着我们不再需要用用这个主题构建的每个站点中使用的样板来弄乱函数文件。
相反,功能文件仅定义每个项目中不同的主题部分。
更进一步
这仅仅是个开始,还有很多事情要做!您可以创建包装器来添加脚本和样式表。您可以通过主题类构造函数自动将style.css
和添加main.js
到主题中,functions.php
从而进一步简化流程。您可以创建方法来移除主题支持以及移除样式/脚本。这种方法非常灵活,最终可以减少代码的冗余。
如果您想了解我自己的主题类的进展,请查看我的GitHub gist。
希望本文对您有所帮助,并向您展示了一些适用于 WordPress 的实用面向对象选项。如果您有兴趣了解更多关于 WordPress 面向对象编程的知识,我强烈建议您观看Kevin Fodness 的WordCamp 面向对象主题开发讲座。对于已经在 WordPress 中使用面向对象编程 (OOP) 的用户,我非常想听听你们对方法名、PHP 命名空间等使用驼峰式命名 (camelCase) 和蛇形命名 (snake_case) 的看法。谢谢!
文章来源:https://dev.to/tylerlwsmith/simplifying-wordpresss-functionsphp-with-oop-2mj8