經過在 Day 27 介紹 WordPress 的 Hook,之後,想必對於 WordPres 的事件處理機制有了進一步的認識,接下來就是利用 Hook 來變魔術的時間囉。非常簡單的程式碼,輕鬆完成 WordPress 的快取外掛,真心不騙!
這次的進度主要實作以下邏輯:
完成以上三個項目,讓外掛可以正常運作。
今日的檔案結構如下:
.                              
├── LICENSE                    
├── README.md                  
├── README.txt                     
├── cache-master.php           
├── composer.json                     
├── inc                        
│   ├── admin                  
│   │   ├── menu.php           
│   │   ├── register.php       
│   │   ├── setting-update.php 
│   │   └── setting.php        
│   ├── autoload.php           
│   ├── class-cache-master.php 
│   └── helpers.php            
├── languages                  
├── phpcs.xml                  
└── vendor                     
對比 Day 27 的檔案結構多了以下檔案:
而在 Day 27 時檔案還是空的「class-cache-master.php」已經寫好主功能。也是今天變出快取功能的主角!
以下是新增的檔案結構說明:
| 檔案路徑 | 說明 | 
|---|---|
| inc/admin/setting-update.php | 在設定頁面時按下送出按紐,觸發的事件處理。 | 
| inc/helpers.php | 函式集,包含檢查 Driver、一個產出 SimpleCache 物件的簡單工廠。 | 
範例:/day-29/cache-master/cache-master.php

(圖 A:cache-master.php 程式碼截圖)
和 Day 27 比較起來,多了以下的改變:
第 7 行:載入需要用的函式集。
第 10 行:在 WordPress 管理介面的外掛列表中,啟用外掛後,會觸發的 Hook。
第 11 行:在 WordPress 管理介面的外掛列表中,移除外掛後,會觸發的 Hook。
第 19 行:儲存設定值時的事件處理。
第 23-25 行:實例化主功能類別 Cache_Master。
WordPress 外掛在存放自己產生的檔案,都是在 wp-content/uploads 這個目錄裡。因此,這個作品要產生的檔案也在這裡。

(圖 B:uploads 目錄截圖)
由上圖可以看到,以年份作為名稱的目錄是 WordPress 自身的媒體庫,專門放上傳的圖片、影片等等。而 sites 這個目錄是啟用多站點後,各部落格自身的媒體庫。
除了以上兩者外,都是其它外掛產生的檔案、日誌等等。因為我們也要產生一個和外掛名稱 (slug) 同名的目錄,「cache-master」。
基於不同的網站伺服器 (web server) 的設定值不同。建議要考慮到目錄若沒有 index.html 檔案,可能是可以被瀏覽目錄的情況。為了防偷窺,我們會產生空白的 index.html 檔案,和 .htaccess 檔案。
這個功能實作在 register.php 這個檔案中。

(圖 C:register.php 下半部程式碼截圖)
而這支建立目錄用的函式由那裡呼叫呢?
見下圖的第 19 行。

(圖 D:register.php 最上方程式碼截圖)
scm_activation 這個函式會在啟用外掛時觸發。
在初始化檔案中:
register_activation_hook( __FILE__, 'scm_activation' );
而 scm_activation 函式使用 add_option 在啟用外掛時先寫入一些預設的設定值。
結果:

(圖 E:cache-master 目錄截圖)
然後再把資料放在下一層目錄,此層為空。因為要考慮到多站點放不同目錄的情況。在這個範例,筆者以開頭為 blog_id 和 8 字元雜湊碼作目錄名稱。
快取主功能,就是把整個 WordPress 網頁 HTML 存起來,然後就不在從資料庫撈資料,直接從快取拿出來吐給網站訪客。這樣的主功能,含註解,只要 106 行。

(圖 F:class-cache-master.php 檔案截圖)
第 28 行:在實例化這個類別時,從 helper.php 的簡單工廠函式拿到 Simple Cache 物件。
第 28 行:每個網站的快取資料的 key 值就是它的路徑的 md5 值。
第 35-39 行:這個 init 方法,就是我們從初始化檔案呼叫這個主功能程式的地方。(圖 A 第 24 行)它分別把 ob_start、ob_stop 及 get_post_data 這三個方法注入到三個不同的 Hook 執行,這就是產生魔法的地方唷!
還記得 Day 28 介紹的 WordPress Hook 生命週期吧?

(圖:Action Hook 的生命週期圖表)
Cache_Master 類別的 ob_start 方法,注入到 init 這個很早期的 Hook。
開始產生頁面的 Hook 是 template_redirect,距離 init 很遠呀!但筆者要求這個外掛越早輸出快取,略過越多的流程,節省的 CPU 和記憶體效率越高。

(圖 G:方法 ob_start 程式碼截圖)
第 7 行:從快取讀取頁面的 key 值,有的話呢....
第 10 行:筆者要留一行 debug 的 HTML 註解訊息在原始碼最尾端,來辨別是不是快取網頁。當然,一般人看不到。因為它是 HTML 註解語法。
第 11-12 行:有快取就輸出,然後離開程式啦。
第 15 行:當沒有快取的時候,使用 PHP 原生函式 ob_start 來收集緩衝區資料。

(圖 H:方法 ob_stop 程式碼截圖)
這個方法是注入到 shotdown 這個 Hook,是 WordPress Hook 生命週期中的最後一個。
第 8 行:這一行是判斷該頁面是否可以被快取喔!條件決定在 get_post_data (圖 F 第46-71行),不是每一頁都會加進快取,本外掛只存放首頁、文章 (post) 和頁面 (page)。
第 9 行:將緩衝區資料存到 $content 變數。
第 12 行:scm_option_ttl 是在設定頁面中讓使用者自己設定的快取存活時間,快取如果過期就自動清除唷!
第 14 行:接著將資料放進快取中。
如果該頁面是不支援快取,就什麼事都不會發生。讀 WordPress 原本的網頁。

(圖 I:方法 get_post_data 程式碼截圖)
預設 $this->cache 是 false。
第 8 行:這是外掛設定值中,使用者自己設定那幾個頁面類型要快取。
第 11 行:設定是 yes 且目前頁面在首頁。
第 15 行:設定是 yes 且目前頁面在文章。
第 19 行:設定是 yes 且目前頁面在頁面。
以上是判斷頁面。不過有以下情況,則不能快取喔!
第 24 行:404 頁面不能快取啦。
第 28 行:使用者登入不能快取啦,因為快取是存整張頁面,包含登入的使用者可以看到的所有東西,網頁上方的 admin bar 會被沒登入的一般訪客看到哦。
這個方法是注入到 pre_get_posts 這個 Hook,但其實注入到更早的 wp 就可以使用 is_home、is_single 和 is_page 這三個 WordPress 函式了。不過日後應該還會新加功能,直接注入到 pre_get_posts 就可以取文章資料,不需要再換 Hook 囉。

(圖 J:Cache Master 外掛設定頁面截圖)

(圖 J:setting-update.php 程式碼截圖)
WordPress 很多這種 update_option_{option} 後面加上動態 ID 的 Hook。點進去看官方文件,說明長 update_option_{option} 的就屬於動態 Hook。
以這個例子來說。scm_option_driver 是設定頁面的一個選項。會產生一個動態 Hook,update_option_ 加上 scm_option_driver 變成:
update_option_scm_option_driver
然後筆者注入函式到裡面,當這個選項有更新時,快取的 Driver 會重建資料表。
scm_option_post_types 這個也是一個設定頁面選項,讓使用者選擇想快取的類型。這邊的注入函式的作用是,當這個選項更新時。快取全部清除。(因為要還要寫今天的文章,所以先全清掉啦。以後再說?)
就這樣,一個快取外掛完成了。
範例原始碼
鐵人賽終於剩最後一天了,我們明天見。不對。兩小時候見。因為筆者要一股作氣完成鐵人賽。終於解脫了。><||