問題描述
我在 header.php 中有一個變數,如:
$page_extra_title = get_post_meta($this_page->ID, "_theme_extra_title", true);
一旦我做:
var_dump($page_extra_title);
我總是在 header.php 之外得到 NULL(var_dump 僅在 header.php 中正常工作) 。我一直在貼上同樣的變數,我需要它 (page.php,post.php,footer.php 等),但它是瘋狂,使一切幾乎不可能維護。
我想知道什麼是傳遞變數透過我的主題中的所有檔案的最佳方法?我猜使用 functions.php 和”get_post_meta” 可能不是最好的主意? 🙂
最佳解決方案
基本分離的資料結構
要傳遞資料,您通常會使用一個 Model(“M” 在”MVC” 中) 。我們來看一個非常簡單的資料介面。介面僅用作我們的構建塊的”Recipes”:
namespace WeCodeMorePackageModels;
interface ArgsInterface
{
public function getID();
public function getLabel();
}
以上是我們傳遞的:一個常見的 ID 和一個”Label” 。
透過組合原子片顯示資料
接下來,我們需要一些 View,在我們的模型和我們的模板之間進行協商。
namespace WeCodeMorePackage;
interface PackageViewInterface
{
/**
* @param ModelsArgsInterface $args
* @return int|void
*/
public function render( ModelsArgsInterface $args );
}
基本上介面說
“We can render something and a Model is mandatory for that task”
最後我們需要實現上面的內容並構建實際的 View 。您可以看到,建構函式告訴我們,檢視的強制性是一個 Template,我們可以渲染它。為了方便開發,我們甚至檢查模板檔案是否真實存在,這樣我們可以使其他開發人員的生活 (以及我們的) 更容易,並注意到這一點。
在 render 函式的第二步中,我們使用 Closure 構建實際的模板包裝器和 bindTo()模型的模板。
namespace WeCodeMorePackage;
use WeCodeMorePackageModelsArgsInterface;
/** @noinspection PhpInconsistentReturnPointsInspection */
class PackageView implements PackageViewInterface
{
/** @var string|WP_Error */
private $template;
/**
* @param string $template
*/
public function __construct( $template )
{
$this->template = ! file_exists( $template )
? new WP_Error( 'wcm-package', 'A package view needs a template' )
: $template;
}
/**
* @param ModelsArgsInterface $args
* @return int|void
*/
public function render( ModelsArgsInterface $args )
{
if ( is_wp_error( $this->template ) )
return print $this->template->get_error_message();
/** @var $callback Closure */
$callback = function( $template )
{
extract( get_object_vars( $this ) );
require $template;
};
call_user_func(
$callback->bindTo( $args ),
$this->template
);
}
}
分離檢視和渲染
這意味著我們可以使用一個非常簡單的模板,如下所示
<!--suppress HtmlFormInputWithoutLabel -->
<p><?= $label ?></p>
呈現我們的內容。將這些片段放在一起,我們會得到以下幾點 (在我們的控制器,中介者等) 中:
namespace WeCodeMorePackage;
$view = new PackageView( plugin_dir_path( __FILE__ ).'tmpl/label.tmpl.php' );
$view->render( new ModelsArgs );
我們獲得了什麼?
這樣我們可以
-
輕鬆交換模板而不改變資料結構
-
有容易閱讀的文章
-
避免全球範圍
-
可以 Unit-Test
-
可以交換模型/資料,而不會損害其他元件
將 OOP PHP 與 WP API 相結合
當然這不是用 get_header(),get_footer()等基本的主題功能呢,對吧?錯誤。只需要在任何你想要的模板或模板部分呼叫你的班級。渲染它,轉換資料,做任何你想要的。如果你真的很好,你甚至可以新增自己的一些自定義過濾器,並且有一些談判者來處理由哪個控制器渲染哪個路由/條件模板載入。
結論?
您可以在 WP 中使用如上所述的內容,而不會出現問題,並且仍然堅持使用基本的 API,並重新使用程式碼和資料,而不會呼叫單個全域性或者弄亂和汙染全域性名稱空間。
次佳解決方案
這是 @kaiser 答案的替代方法,我發現很好 (來自我的+1),但是需要額外的工作來與核心 WP 功能一起使用,並且它與模板層次結構整合在一起較低的 per-se 。
我想要分享的方法是基於單個類 (它是我正在開發的一個 stripped-down 版本),它負責處理模板的渲染資料。
它有一些 (IMO) 有趣的功能:
-
模板是標準的 WordPress 模板檔案 (single.php,page.php),它們有更多的功能
-
現有的模板只是工作,所以您可以不用努力地整合現有主題的模板
-
與 @kaiser 方法不同,在模板中,您可以使用
$this關鍵字訪問變數:這樣可以在未定義的變數的情況下避免生產中的通知
Engine 類
namespace GMTemplate;
class Engine
{
private $data;
private $template;
private $debug = false;
/**
* Bootstrap rendering process. Should be called on 'template_redirect'.
*/
public static function init()
{
add_filter('template_include', new static(), 99, 1);
}
/**
* Constructor. Sets debug properties.
*/
public function __construct()
{
$this->debug =
(! defined('WP_DEBUG') || WP_DEBUG)
&& (! defined('WP_DEBUG_DISPLAY') || WP_DEBUG_DISPLAY);
}
/**
* Render a template.
* Data is set via filters (for main template) or passed to method for partials.
* @param string $template template file path
* @param array $data template data
* @param bool $partial is the template a partial?
* @return mixed|void
*/
public function __invoke($template, array $data = array(), $partial = false)
{
if ($partial || $template) {
$this->data = $partial
? $data
: $this->provide(substr(basename($template), 0, -4));
require $template;
$partial or exit;
}
return $template;
}
/**
* Render a partial.
* Partial-specific data can be passed to method.
* @param string $template template file path
* @param array $data template data
* @param bool $isolated when true partial has no access on parent template context
*/
public function partial($partial, array $data = array(), $isolated = false)
{
do_action("get_template_part_{$partial}", $partial, null);
$file = locate_template("{$partial}.php");
if ($file) {
$class = __CLASS__;
$template = new $class();
$template_data = $isolated ? $data : array_merge($this->data, $data);
$template($file, $template_data, true);
} elseif ($this->debug) {
throw new RuntimeException("{$partial} is not a valid partial.");
}
}
/**
* Used in templates to access data.
* @param string $name
* @return string
*/
public function __get($name)
{
if (array_key_exists($name, $this->data)) {
return $this->data[$name];
}
if ($this->debug) {
throw new RuntimeException("{$name} is undefined.");
}
return '';
}
/**
* Provide data to templates using two filters hooks:
* one generic and another query type specific.
* @param string $type Template file name (without extension, e.g. "single")
* @return array
*/
private function provide($type)
{
$generic = apply_filters('gm_template_data', array(), $type);
$specific = apply_filters("gm_template_data_{$type}", array());
return array_merge(
is_array($generic) ? $generic : array(),
is_array($specific) ? $specific : array()
);
}
}
(在這裡提供 Gist)
如何使用
只需要呼叫 Engine::init()方法,大概在'template_redirect'鉤子上。這可以在主題 functions.php 或外掛中完成。
require_once '/path/to/the/file/Engine.php';
add_action('template_redirect', array('GMTemplateEngine', 'init'), 99);
就這樣。
您現有的模板將以 expcted 的形式工作。但現在您可以訪問自定義模板資料。
自定義模板資料
要將自定義資料傳遞給模板,有兩個過濾器:
-
'gm_template_data' -
'gm_template_data_{$type}'
第一個是針對所有模板觸發的,第二個是模板特定的,實際上,{$type}是副檔名的模板檔案的基本名稱。
例如。過濾器'gm_template_data_single'可用於將資料傳遞給 single.php 模板。
附加到這些鉤子的回撥必須返回一個陣列,其中的鍵是變數名。
例如,您可以傳遞後設資料作為模板資料,所以:
add_filter('gm_template_data', function($data) {
if (is_singular()) {
$id = get_queried_object_id();
$data['extra_title'] = get_post_meta($id, "_theme_extra_title", true);
}
return $data;
};
然後,您可以在模板中使用:
<?= $this->extra_title ?>
除錯模式
當常數 WP_DEBUG 和 WP_DEBUG_DISPLAY 都為真時,類工作在除錯模式。這意味著如果未定義變數,則會丟擲異常。
當類不處於除錯模式 (可能在生產中) 訪問未定義的變數將輸出一個空字串。
資料模型
組織資料的一個好的和可維護的方式是使用模型類。
它們可以是非常簡單的類,它使用上述相同的過濾器返回資料。沒有特定的介面可以按照你的喜好進行組織。
在這下面,只是一個例子,但是你可以自己去做。
class SeoModel
{
public function __invoke(array $data, $type = '')
{
switch ($type) {
case 'front-page':
case 'home':
$data['seo_title'] = 'Welcome to my site';
break;
default:
$data['seo_title'] = wp_title(' - ', false, 'right');
break;
}
return $data;
}
}
add_filter('gm_template_data', new SeoModel(), 10, 2);
__invoke()方法 (當類被用作回撥時執行) 返回一個字串,用於模板的<title> 標籤。
感謝'gm_template_data'傳遞的第二個引數是模板名稱,該方法返回主頁的自定義標題。
上面的程式碼可以使用類似的東西
<title><?= $this->seo_title ?></title>
在頁面的<head> 部分。
Partials
WordPress 具有 get_header()或 get_template_part()等功能,可用於將部分載入到主模板中。
使用 Engine 類時,這些函式就像所有其他 WordPress 函式一樣可以在模板中使用。
唯一的問題是,在使用核心 WordPress 功能載入的部分內部不可能使用使用 $this 獲取自定義模板資料的高階功能。
因此,Engine 類具有方法 partial(),允許載入部分 (以完全 child-theme 相容的方式),並且仍然可以在部分自定義模板資料中使用。
用法非常簡單。
假設在主題 (或子主題) 資料夾中有一個名為 partials/content.php 的檔案,它可以包括使用:
<?php $this->partial('partials/content') ?>
內部的這個部分將可能訪問所有父主題資料是一樣的。
與 WordPress 功能不同,Engine::partial()方法允許將特定資料傳遞給部分資料,只需將資料陣列作為第二個引數傳遞。
<?php $this->partial('partials/content', array('greeting' => 'Welcome!')) ?>
預設情況下,partials 可以訪問父主題中可用的資料,並透過資料顯示。
如果顯式傳遞給 partial 的某個變數具有父主題變數的相同名稱,那麼明確傳遞的變數將獲勝。
然而,也可以包括部分隔離模式,即部分無法訪問父主題資料。要做到這一點,只需將 true 作為第三個引數傳遞給 partial():
<?php $this->partial('partials/content', array('greeting' => 'Welcome!'), true) ?>
Conclusion
即使相當簡單,Engine 類也相當完整,但肯定可以進一步改進。例如。沒有辦法檢查變數是否被定義。
由於其與 WordPress 功能和模板層次結構的 100%相容性,您可以將其與現有和第三方程式碼整合,沒有任何問題。
但是,請注意,僅部分測試,所以有可能有我還沒有發現的問題。
-
輕鬆交換模板而不改變資料結構
-
有容易閱讀的文章
-
避免全球範圍
-
可以 Unit-Test
-
可以交換模型/資料,而不會損害其他元件
對我的班級也是有效的。
第三種解決方案
簡單的回答,不要在任何地方傳遞變數,因為它使用惡意的全域性變數發臭。
從你的例子看,你似乎試圖做一個早期的最佳化,另一個邪惡的)
使用 wordpress API 獲取儲存在資料庫中的資料,並且不要嘗試超越並最佳化其使用,因為 API 只是檢索值並啟用過濾器和操作。透過刪除 API 呼叫,您可以刪除其他開發人員在不修改程式碼的情況下更改程式碼行為的能力。
參考文獻
注:本文內容整合自 Google/Baidu/Bing 輔助翻譯的英文資料結果。如果您對結果不滿意,可以加入我們改善翻譯效果:薇曉朵技術論壇。