如何進行現代 WordPress 開發(第 2 部分)
已發表: 2022-03-11WordPress 是地球上使用最廣泛的網站技術,並且有充分的理由。 然而,遺留代碼的核心是一團糟,這個問題會級聯到第三方開發人員。 一些開發人員以此為藉口在他們自己的 WordPress PHP 代碼中偷工減料,但從長遠來看,除了最微不足道的更改之外,這種方法的成本更高。
在我們的兩部分系列的第 1 部分中,我們專注於整體項目和工作流工具,然後是前端開發。 現在是時候採取行動了,與 PHP 搏鬥:具體來說,如何在使用後端 WordPress 代碼時遵循最佳實踐。 您可能會認為這是一個 PHP/WordPress 教程,但更高級一些,假設您已經完成了一些 WordPress 後端定制。
那麼,哪些現代軟件設計原則和 PHP 功能將為您的時間帶來最大價值? 以下是我強烈推薦的 10 個 WordPress 和 PHP 開發實踐。
現代 WordPress 開發最佳實踐 #1:遵循“關注點分離”
關注點分離意味著不應將具有不同功能或目的的部分 WordPress PHP 代碼混合在一起。 相反,它們應該被組織成不同的部分或模塊,通過定義的接口相互傳遞數據。 (接口是一組定義的參數,模塊將其作為輸入,以及它輸出的內容。)一個密切相關的術語是單一職責原則:每個代碼模塊(或函數)應該只負責一件事。
遵循這些原則的最終目標是生成模塊化的代碼,從而可維護、可擴展和可重用。
那是一口難言,所以讓我們看一個 WordPress PHP(來自 WordPress 核心)的例子,它把所有東西都糾纏在一起。 這種編碼風格通常被稱為“意大利麵條式代碼”,因為幾乎不可能理解其內部工作原理。 為簡潔起見,以下摘錄被刪節; 但是,保留了原始樣式和格式。
$id = isset( $_REQUEST['id'] ) ? intval( $_REQUEST['id'] ) : 0; <table class="form-table"> <?php $blog_prefix = $wpdb->get_blog_prefix( $id ); $sql = "SELECT * FROM {$blog_prefix}options WHERE option_name NOT LIKE %s AND option_name NOT LIKE %s"; $query = $wpdb->prepare( $sql, $wpdb->esc_like( '_' ) . '%', '%' . $wpdb->esc_like( 'user_roles' ) ); $options = $wpdb->get_results( $query ); foreach ( $options as $option ) { if ( strpos( $option->option_value, "\n" ) === false ) { ?> <tr class="form-field"> <th scope="row"><label for="<?php echo esc_attr( $option->option_name ); ?>"><?php echo esc_html( ucwords( str_replace( '_', ' ', $option->option_name ) ) ); ?></label></th> <?php if ( $is_main_site && in_array( $option->option_name, array( 'siteurl', 'home' ) ) ) { ?> <td><code><?php echo esc_html( $option->option_value ); ?></code></td> <?php } else { ?> <td><input class="<?php echo $class; ?>" name="option[<?php echo esc_attr( $option->option_name ); ?>]" type="text" value="<?php echo esc_attr( $option->option_value ); ?>" size="40" <?php disabled( $disabled ); ?> /></td> <?php } ?> </tr> <?php } } // End foreach </table>
首先,完全無法理解。 我喜歡唯一的評論是End foreach
,這完全是多餘的。 我們有數據庫查詢、查詢結果處理、嵌入在 HTML 中的附加處理(如果你沒有註意到的話,那裡嵌套了一個if
/ else
)、輸出轉義和 HTML 模板,這些都混合在一起。 另一個問題是直接來自全局$_REQUEST
的$id
參數,而不是將實際參數傳遞給函數。
看到這裡,完全可以理解為什麼 WordPress 核心多年來基本保持不變。 重構這種代碼——尤其是在保持現有行為的同時——是一項真正的史詩般的任務,沒有人願意做。
那麼我們如何正確地做到這一點呢? 好吧,要記住的一件事是,沒有一種真正的方法。 我們提到了我們應該爭取的上述品質: 我們需要 WordPress 自定義 PHP 代碼是可維護的和模塊化的。 讓我們看看如何將上面的代碼拆分為模塊。
- 顯然,SQL 查詢應該在一個單獨的模塊中。 WordPress 已經有一個很好抽象的
WP_Query
類,應該用作示例。 - 所有的 HTML 都進入一個模板。 我們將在下面進一步介紹 PHP 模板。
- 剩餘的 PHP 代碼應該包裝在一個函數中——如果代碼對於一個函數來說太長或太複雜,則可以使用幾個函數。
$id
等參數通過函數參數傳遞。
這是對上面示例的大大簡化的重寫:
function betterSiteSettings($args) { $data = WP_Settings_Query($args); // process $data here $context = array_merge([], $data_processed, $other_data); return Template::render('template.name', $context); }
現代 WordPress 開發最佳實踐 #2:避免使用全局變量
WordPress 有太多的全局變量。 為什麼全局變量不好? 它們使您的 WordPress PHP 代碼難以遵循並使應用程序狀態不可靠。 任何一段 PHP 代碼——也就是安裝在 WordPress 中的任何插件——都可以讀取和寫入全局變量,因此不能保證它們包含有效數據。 試圖了解在 Loop 之類的東西中使用了哪些全局變量也不是一件容易的事。
讓我們從實際的角度來看這個。 這個例子來自 WooCommerce。 可能每個 WordPress 開發人員都知道它是什麼——循環:
<?php while ( have_posts() ) : the_post(); ?> <?php wc_get_template_part( 'content', 'single-product' ); ?> <?php endwhile; // end of the loop. ?>
上面的代碼片段呈現了一個產品模板。 鑑於沒有傳遞給wc_get_template_part
的參數,它如何知道要顯示什麼產品? 查看模板,我們看到它以global $product;
開頭。 ,所以這是當前產品對象的存儲位置。
現在想像一下,我們有一個搜索和過濾產品的目錄頁面,我們希望在同一頁面上顯示“產品詳細信息”彈出窗口。 在幕後,前端腳本執行 AJAX 請求以獲取該特定產品模板。 我們不能簡單地調用wc_get_template_part('content', 'single-product')
因為它不使用參數,所以我們需要設置幾個全局變量才能使其工作。
更複雜的用例將涉及多個模板、在這些模板中觸發的掛鉤以及將其回調添加到這些掛鉤的第三方插件。 它可以迅速升級。 我們無法知道這些回調所依賴的全局狀態。 第三方插件可以自由修改其回調中的任何全局變量。 我們沒有使用系統,而是開始與系統抗爭,遇到來自不可靠的全局狀態的奇怪錯誤。
將產品 ID 作為參數傳遞不是更明智嗎? 然後我們可以重用該模板,而不必擔心會弄亂 WordPress 使用的全局變量。
現代 WordPress 開發最佳實踐 #3:使用面向對象編程 (OOP)
模塊化導致了對象和麵向對象編程的概念。 在最基本的層面上,OOP 是一種組織代碼的方式。 函數和變量被捆綁到類中,分別稱為類方法和屬性。 WordPress 插件手冊建議使用 OOP 來組織您的 WordPress 自定義 PHP 代碼。
OOP 中的一個重要原則是限制對方法和屬性的訪問——或者在 PHP 術語中,將它們表示為private
或protected
的——因此只有其他類方法可以訪問和更改它們。 對此的 OOP 術語是封裝:數據被封裝在類中,更改該數據的唯一方法是使用提供的類方法。
這使得調試和維護代碼比使用可以在整個代碼庫中的任何位置修改的全局變量時容易得多。 考慮全局 WordPress post
變量。 您可以在代碼中的任何位置訪問它,並且許多功能都依賴於使用它。 如果您可以將修改僅限於 WordPress 核心功能,但任何人都可以閱讀,該怎麼辦? 將全局post
變量隱藏或封裝在一個類中並圍繞它構建一個接口將使這成為可能。
這只是對 OOP 以及如何在現代 WordPress 開發中使用它的一個非常基本的描述。 為了進一步的學習,我徹底推薦 Carl Alexander 的電子書,使用 WordPress 發現面向對象編程,其中包含關於 WordPress 中 OOP 主題的最全面和有用的內容。
重要的是要記住,OOP 不是靈丹妙藥:使用 OOP 編寫糟糕的代碼就像使用任何其他編程範式一樣容易。
讓我們深入了解一些關於使用 PHP 進行 WordPress 開發的具體建議。
現代 PHP 最佳實踐 #1:目標 PHP 7.0+
使用現代 PHP 功能需要一個現代版本的 PHP。 根本沒有理由支持低於 7.0 的 PHP 版本。 即使是 WordPress 核心,最早也要到 2019 年底才需要 PHP 7.0。
不過,最好檢查您的最低版本以避免在不兼容的環境中出現“死機白屏”。 下面的代碼片段顯示了使用插件標頭在代碼中聲明具有保護條件的最低 PHP 版本。
<?php /** * Plugin Name: My Awesome Plugin * Requires PHP: 7.0 */ // bails if PHP version is lower than required if (version_compare(PHP_VERSION, '7.0.0', '<')) { // add admin notice here return; } // the rest of the actual plugin here
現代 PHP 最佳實踐 #2:採用 PHP 行業標準(PSR-2 編碼風格指南)
PSR 是 PHP Framework Interop Group 發布的建議。 它們是任何現代 PHP 工作流程中事實上的行業標準,可以肯定地說,整個 PHP 社區都遵循這些標準。 PSR-2 是描述編碼風格的建議。 Symfony 和 Laravel 等流行的 PHP 框架遵循 PSR-2。
為什麼要使用 PSR-2 而不是 WordPress 編碼標準? 主要是因為 WordPress 標準已經過時並且不使用任何較新的語言功能。 這是可以理解的,因為 WordPress 核心必須遵循自己的標準。 直到最近它才支持 PHP 5.2,而且 PSR-2 與 PHP 5.2 不兼容。

這可能並不明顯,但除非您致力於核心,否則不需要使用 WordPress 編碼標準。 將遵循 PSR-2 標準的插件提交到 WordPress 插件目錄是沒有問題的。 事實上,這樣做有一些很好的論據。
現代 PHP 最佳實踐 #3:使用 PHP 模板引擎
PHP 不是模板引擎。 它最初是一個,但後來演變成一種功能齊全的編程語言,沒有理由繼續將它用於模板。 PHP 最流行的兩個模板引擎是 Twig 和 Blade,它們分別被 Symfony 和 Laravel 使用。 本文將使用 Twig 作為模板引擎的示例; 但是,Blade 具有類似的特性和功能。 我懇請您考慮兩者並自己決定最適合您的。
下面的示例比較了一個 PHP 模板及其對應的 Twig 模板。 在 PHP 示例中顯示和轉義輸出特別冗長:
foreach ( $options as $option ) { ?> <tr class="form-field"> <th scope="row"> <label for="<?php echo esc_attr( $option->option_name ); ?>"> <?php echo esc_html( strtolower( $option->option_name ) ); ?> </label> </th> </tr> <?php } // End foreach
在 Twig 中,這更簡潔易讀:
{% for option in options %} <tr class="form-field"> <th scope="row"> <label for="{{ option.option_name }}"> {{ option.option_name }} </label> </th> </tr> {% endfor %}
Twig 的主要優點是:
- 可讀且簡潔的語法
- 自動輸出轉義
- 通過繼承和塊擴展模板
在性能方面,Twig 編譯為 PHP 模板並且幾乎沒有開銷。 Twig 只有一個 PHP 語言結構的子集,僅限於模板。 這迫使開發人員從模板中刪除業務邏輯,從而強制分離關注點。
甚至還有用於 WordPress 的 Twig。 它被稱為 Timber,它是開始創建更好模板的好方法。 Timber starter 主題是以 OOP 方式組織主題的完美示例。
現代 PHP 最佳實踐 #4:使用 Composer
Composer 是 PHP 的依賴管理器。 它是一個工具,允許聲明項目正在使用的庫,然後自動下載、安裝和更新。 然後你只需要包含 Composer 的自動加載文件vendor/autoload.php
而不是手動要求每個庫。
WordPress 插件和主題通常不使用任何第三方庫。 這部分是因為 WordPress 有一個廣泛的 API 幾乎可以滿足任何需求,部分是因為可能的版本衝突。 考慮兩個需要相同 PHP 庫但版本不同的插件。 第一個運行的插件獲得正確的版本,第二個插件也獲得該版本。 這很可能是另一種死機白屏情況。
為避免衝突,應在應用程序級別(即整個 WordPress 站點)使用依賴關係管理。 這就是 Roots(更具體地說是基岩)所做的。 在包(插件或主題)級別使用時,Composer 在使用第三方庫時可能會導致衝突。 這是一個已知問題。 迄今為止存在的唯一解決方案是將外部庫的名稱空間重命名為獨特的名稱,這不是一項簡單的任務。
不過 Composer 仍有一個用途:自動加載您自己的類。 但在我們進一步使用自動加載之前,我們需要了解 PHP 命名空間。
現代 PHP 最佳實踐 #5:使用命名空間
WordPress 核心是一個遺留項目,它使用全局命名空間,或者換句話說,根本沒有命名空間。 全局聲明的任何類或函數(意味著不在另一個類或函數中)在整個代碼庫的任何地方都是可見的。 它們的名稱不僅在您的代碼庫中而且對於現在使用或將來可能使用的所有插件和主題都必須是唯一的。
命名衝突(例如,用已經存在的名稱聲明一個函數)通常會導致死機白屏,我們不希望這樣。 WordPress Codex 建議為所有函數和類添加一些獨特的前綴。 因此,我們沒有像Order
這樣的簡單類名,而是得到像Akrte_Awesome_Plugin_Order
這樣的東西,其中“Akrte”是我剛剛編寫的唯一前綴。
如果我們使用文件系統類比,命名空間可以被視為組或文件夾,它有助於組織代碼並避免名稱衝突。 您可以擁有用斜杠分隔的複雜命名空間,就像嵌套文件夾一樣。 (PHP 命名空間特別使用反斜杠。)
這些命名空間部分稱為子命名空間。 如果使用命名空間完成,我們的示例類Akrte_Awesome_Plugin_Order
將是Akrte\Awesome_Plugin\Order
。 這裡Akrte
和Awesome_Plugin
是命名空間部分(或子命名空間),而Order
是類名。 然後你可以添加一個use
語句,然後只使用類名。 它肯定看起來更好:
use Akrte\Awesome_Plugin\Order; $a = new Order;
顯然,命名空間應該是唯一的; 因此,我們應該給第一個“根”子命名空間一個唯一的名稱,通常是供應商名稱。 例如,WooCommerce 類WC_REST_Order_Notes_V2_Controller
可以使用如下命名空間重新完成:
namespace WooCommerce\RestApi\V2\Controllers; class OrderNotes {}
WooCommerce 代碼庫現在確實使用命名空間。 例如,在 WooCommerce REST API 版本 4 中。
現代 PHP 最佳實踐 #6:使用自動加載器
在大多數 PHP 工作流程中,將 PHP 文件鏈接在一起的常用方法是使用require
或include
語句。 隨著項目的增長,您會在主插件文件中獲得數十個require
語句。 自動加載器會自動包含文件,並且僅在需要時才會這樣做。 從技術上講,它是一個函數,當它第一次在代碼中遇到時,它require
包含一個類或函數的 sa 文件。 不再需要手動添加任何require
語句。
由於自動加載器僅加載特定請求中使用的模塊,因此通常還會顯著提高性能。 如果沒有自動加載器,即使請求僅使用 10% 的代碼,您的整個代碼庫也會包含在內。
自動加載器函數需要知道您的類和函數存在於哪些文件中。 為此,有一個 PHP-FIG 標準 PSR-4。
它表示命名空間的一部分,即前綴,被指定為對應於基本文件夾。 其後的子命名空間對應於基本文件夾中的文件夾。 最後,類名對應文件名。 示例類Akrte\AwesomePlugin\Models\Order
需要以下文件夾結構:
/awesome-plugin awesome-plugin.php /includes /Models Order.php
命名空間前綴Akrte\AwesomePlugin\
對應於下面討論的自動加載器配置中指定的includes
文件夾。 Models
子命名空間有一個對應的Models
文件夾, Order
類包含在Order.php
中。
幸運的是,不需要自己實現自動加載器功能。 Composer 可以為您創建一個自動加載器:
- 安裝作曲家
- 在項目的根文件夾中創建一個
composer.json
文件。 它應該包含以下幾行:
{ "name": "vendor-name/plugin-name", "require": {}, "autoload": { "psr-4": { "Akrte\\AwesomePlugin\\": "includes/" } } }
- 運行
composer install
。 - 在主插件 PHP 文件的頂部包含
vendor/autoload.php
,如下所示:
<?php /** * Plugin Name: My Awesome Plugin */ defined('ABSPATH') || exit; require __DIR__ . '/vendor/autoload.php'; // do stuff
除了命名空間,WooCommerce 的最新代碼庫還使用了 Composer 自動加載器。
在涵蓋了這些 PHP 設計原則之後,是時候將我們所有的 PHP 課程與 WordPress 後端定制聯繫起來,並提出一個最終建議。
現代 WordPress 開發最佳實踐 #4:考慮使用根堆棧
Roots 是目前最全面的現代 WordPress 開發工作流程。 但是,我會說它不一定要在每個WordPress 項目中使用,因為:
- Roots 必須從一開始就使用。 最常見的原因,真的。 重構現有項目的成本太高。
- 是有主見的。 同意時好,不同意時壞。 例如,您可能更喜歡其他組織主題的方式。 自以為是的項目也需要時間來學習“他們的方式”。
- 不是每個人都知道。 追隨您以維護您使用 Roots 堆棧構建的站點的開發人員可能不知道它是什麼,並且想知道 WordPress 文件夾發生了什麼。 我們應該考慮一下我們的 WordPress 開發人員。
一般來說,在承諾使用它之前,您會希望充分了解任何強烈自以為是的項目的所有優點和缺點。
現代 PHP 和軟件原理:使 WordPress 後端開發更強大
很明顯,沒有一種真正的編寫軟件的方法。 諸如關注點分離之類的概念已有數十年的歷史。 然而,它的實際含義一直存在爭議。 以 CSS 為例。 一開始,我們將它作為style
內聯到 HTML 中,然後我們決定單獨的 CSS 表是關注點分離的意義所在。
快進十年:當今的 JavaScript 應用程序使用組件作為關注點分離的實現。 前端開發人員傾向於使用 CSS-in-JS,這基本上意味著再次將 CSS 內聯到 HTML 中(嗯,這不是那麼簡單,但你明白了)。 圈子就完成了!
最佳實踐一直是關於改善開發者體驗:
必須編寫程序供人們閱讀,並且只是偶然地供機器執行。
Abelson & Sussman,計算機程序的結構和解釋
本 PHP WordPress 教程中的一些實踐在您的項目中快速且易於實施。 例如,自動加載器:每個項目執行一次,然後儘情享受。 另一方面,新的軟件架構理念需要時間、實踐和無數次迭代才能變得方便和舒適。 不過,回報要大得多。 你不僅會更有效率地做你所做的事情,而且會更享受你所做的事情。 定期享受您為客戶所做的工作也許是可持續發展的唯一途徑。