網站製作學習誌

記錄學習製作網站的一切

當 Zend Framework 的 Partial View Helper 遇上 Smarty

昨天小魚遇到一個詭異的 Zend Framework 與 Smarty 整合問題,幫她解決之後,順手記一下。

在「將 Smarty 3 整合到 Zend Framework 1.11 中」一文裡,我們在 My_View_Smarty 中註冊了「 this 」這個樣版變數,目的就是讓 Smarty 可以在樣版中透過 $this 來使用 View Helper 。

不過因為 Smarty 和 Zend_View 在解析樣版時,對 $this 這個變數的解釋是不一樣的,這樣一來就會造成了一些麻煩。

重建問題

接著我們來做個小實驗,先依照「將 Smarty 3 整合到 Zend Framework 1.11 中」準備好一個已經將 Smarty 整合好的 Zend Framework 專案。

然後在 application/layouts/scripts/layout.phtml 的 head 標籤內加入以下的 Smarty 語法:

1
<% $this->headTitle('Page Title') %>

接著在 application/views/scripts/index/index.phtml 中加入以下語法:

1
<% $this->partial('test.phtml') %>

最後再建立一個 application/views/scripts/test.phtml ,內容如下:

1
<% $this->url(['param' => 'value']) %>

現在瀏覽首頁,你會發現程式出現了以下的錯誤:

1
Fatal error: Call to a member function url() on a non-object ...

這是為什麼呢?因為在原生的 Zend_View 樣版中,因為樣版是透過 include 指令來載入並解析的,所以 $this 變數就是 Zend_View 物件本身;但是在 Smarty 底下就不一樣了,在 My_View_Smarty 的建構函式裡,我們透過了以下指令來將 My_View_Smarty 物件本身指定給 Smarty 的 this 樣版變數:

1
$this->_smarty->assign('this', $this);

這樣一來,在 Smarty 樣版的 $this 就是一個樣版變數,而不是 View 物件本身。

然後問題來了,在 Partial View Helper 執行的過程中,竟然會把 View 物件複製出來後,再清空裡面所有指定好的樣版變數!換句話說,在透過 Partial View Helper 去呼叫的 Smarty 子樣版,會因為所有樣版變數都被清掉的關係而把 $this 當做是 null 值;這下子,對子樣版來說,當然就無法透過 $this 來呼叫 View 物件了。

而這個問題也會發生在使用 PaginationControl 這個 View Helper 上,因為它就是透過 Partial View Helper 來完成它的工作的。

該怎麼辦呢?

解決方案

答案很簡單,再告訴子樣版 $this 是什麼就好了。怎麼做呢?有兩個方法可以達成。

解法一

我們可以在 Partial View Helper 的第二個參數重新指定 $this ,如下:

1
<% $this->partial('test.phtml', ['this' => $this]) %>

這樣一來, Partial View Helper 就會把重新把現在 Smarty 主樣版的 $this 再指定給子樣版的 $this ,所以我們就可以在子樣版裡再次利用 $this 來呼叫 View Helper 了。

而 PaginationControl View Helper 也是同樣的原理:

1
<% $this->paginationControl($paginator, 'Sliding', 'pagination.phtml', ['this' => $this]) %>

不過這個解決方案就得一直在呼叫 Partial View Helper 加上指定 this 的動作,感覺上很麻煩;還好我們有第二種方式來一勞永逸地解決它。

解法二

第二個方法就是直接讓 Partial View Helper 預先把 $this 變數指向 View 物件就可以了,但我們並不需要修改 Zend Framework 的原始碼, Zend Framework 提供了一個更好的擴充方式。

首先我們建立 library/My/View/Helper/Partial.php 這個檔案,其內容如下:

1
2
3
4
5
6
7
8
9
10
<?php
class My_View_Helper_Partial extends Zend_View_Helper_Partial
{
    public function cloneView()
    {
        $view = parent::cloneView();
        $view->assign('this', $this->view); // 指向原來的 View 物件,而不是 clone 後的 View 物件
        return $view;
    }
}

然後在 application.ini 中,指定自訂的 View Helper 位置:

1
resources.view.helperPath.My_View_Helper = "My/View/Helper"

這樣一來,我們就可以安心地透過 Smarty 來呼叫 Partial View Helper ,而不必再加上 ['this' => $this] 了。

解法一與解法二共有的問題

這兩種解法還是有一個問題,那就是在主樣版的變數無法被傳遞到 Partial 樣版中,以下直接用例子來說明。

假設我們在 IndexController::indexAction() 中加入以下程式碼:

1
2
3
4
5
6
7
public function indexAction()
    {
        $this->view->data = array(
            'abc' => '123',
            'def' => '456',
        );
    }

在 application/views/scripts/test.phtml 中再加入:

1
<% $data|@var_dump %>

你會發現 test.phtml 並沒有辦法正確顯示 $data 的內容。

因此我們必須把主樣版的變數再複製一份給 Partial 樣版,所以解法一就必須修改成如下程式碼:

1
2
<% $__vars = array_merge(['this' => $this], $this->getVars()) %>
<% $this->partial('index/test.phtml', $__vars) %>

原理就是把原來的樣版變數和 $this 變數合併後再傳給 Partial 樣版。

至於解法二的修改也是一樣:

1
2
3
4
5
6
7
8
9
10
11
<?php
class My_View_Helper_Partial extends Zend_View_Helper_Partial
{
    public function cloneView()
    {
        $view = parent::cloneView();
        $view->assign('this', $this->view);
        $view->assign($this->view->getVars()); // 把原來的樣版變數傳給 Partial 樣版
        return $view;
    }
}

這樣一來就沒問題了。

Comments