閱讀964 返回首頁    go 技術社區[雲棲]


magento開發 -- 深入理解Magento第三章 – 布局,塊和模板

作者:Alan Storm
翻譯:Hailong Zhang

第三章 – 布局,塊和模板

我們接著研究Magento。根據我們第二章講的Magento MVC的架構,我們接下來應該講模型(Model),但是我們跳過模型先來看布局和塊。和一些流行的PHP MVC架構不同的是,Magento的執行控製器不直接將數據傳給試圖,相反的視圖將直接引用模型,從模型取數據。這樣的設計就導致了視圖被拆分成兩部分,塊(Block)和模板(Template)。塊是PHP對象,而模板是原始PHP文件,混合了XHTML和PHP代碼(也就是把PHP作為模板語言來使用了)。每一個塊都和一個唯一的模板文件綁定。在模板文件phtml中,“$this”就是指該模板文件對應的快對象。

讓我們來看一個例子
File: app/design/frontend/base/default/template/catalog/product/list.phtml
你將看到如下代碼
<?php $_productCollection=$this->getLoadedProductCollection() ?>
<?php if(!$_productCollection->count()): ?>
<p ><?php echo $this->__('There are no products matching the selection.') ?></p>
<?php else: ?>

這裏“getLoadedProductCollection”方法可以在這個模板的塊對象“Mage_Catalog_Block_Product_List”中找到
File: app/code/core/Mage/Catalog/Block/Product/List.php
...
public function getLoadedProductCollection()
{
    return $this->_getProductCollection();
}   
...

塊的“_getProductCollection”方法會實例化模型,並讀取數據然後返回給模板。

嵌套塊

Magento把視圖分離成塊和模板的真正強大之處在於“getChildHtml”方法。這個方法可以讓你實現在塊中嵌套塊的功能。頂層的塊調用第二層的塊,然後是第三層……這就是Magento如何輸出HTML的。讓我們來看一下單列的頂層模板
File: app/design/frontend/base/default/template/page/1column.phtml

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="https://www.w3.org/1999/xhtml" xml:lang="<?php echo $this->getLang() ?>" lang="<?php echo $this->getLang() ?>">
<head>
<?php echo $this->getChildHtml('head') ?>
</head>
<body<?php echo $this->getBodyClass()?' ':'' ?>>
<?php echo $this->getChildHtml('after_body_start') ?>
<div >
    <?php echo $this->getChildHtml('global_notices') ?>
    <div >
        <?php echo $this->getChildHtml('header') ?>
        <div >
            <div >
                <?php echo $this->getChildHtml('breadcrumbs') ?>
                <div >
                    <?php echo $this->getChildHtml('global_messages') ?>
                    <?php echo $this->getChildHtml('content') ?>
                </div>
            </div>
        </div>
        <?php echo $this->getChildHtml('footer') ?>
        <?php echo $this->getChildHtml('before_body_end') ?>
    </div>
</div>
<?php echo $this->getAbsoluteFooter() ?>
</body>
</html>

我們可以看到這個模板裏麵很多地調用了“$this->getChildHtml(…)”。每次調用都會引入另外一個塊的HTML內容,直到最底層的塊。

布局對象

看到這裏,你可能有這樣的疑問
Magento怎麼知道在一個頁麵上要用那些塊?
Magento怎麼知道哪一個塊是頂層塊?
“$this->getChildHtml(…)”裏麵的參數是什麼意思?塊的名字嗎?
Magento引入了布局對象(Layout Object)來解決上麵的那些問題。布局對象(或者說布局文件)就是一個XML文件,定義了一個頁麵包含了哪些塊,並且定義了哪個塊是頂層塊。

在第二章的時候我們在執行方法(Action Method)裏麵直接輸出了HTML內容。現在我們要為我們的Hello World模塊創建一個簡單的HTML模板。首先我們要創建如下文件
app/design/frontend/default/default/layout/local.xml
包含以下內容
<layout version="0.1.0">
    <default>
        <reference name="root">
            <block type="page/html" name="root" output="toHtml" template="../../../../../code/local/Alanstormdotcom/Helloworld/simple_page.phtml" />
        </reference>
    </default>
</layout> 

再創建如下文件
app/code/local/Alanstormdotcom/Helloworld/simple_page.phtml
包含以下內容
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
        "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="https://www.w3.org/1999/xhtml">
<head>
    <title>Untitled</title>
    <meta name="generator" content="BBEdit 9.2" />
    <style type="text/css">
        body {
            background-color:#f00;
        }
    </style>
</head>
<body>
</body>
</html>

最後,我們要在執行控製器裏麵調用布局文件,開始輸出HTML。修改執行方法如下
public function indexAction() {
    //remove our previous echo
    //echo 'Hello Index!';
    $this->loadLayout();
    $this->renderLayout();
}

清空Magento緩存,訪問URL “https://exmaple.com/helloworld/index/index”。你應該看到一個純紅色背景的頁麵。這個頁麵的源代碼應該和我們創建的文件“simple_page.phtml”一模一樣。

究竟是怎麼回事呢?

也許你看到這裏一頭霧水,沒關係,我們來慢慢解釋。首先你得安裝一個 Layout Viewer 模塊,這和我們第一章講的 Config Viewer 模塊很相似,都是查看Magento的內部信息。安裝完這個模塊之後【譯者注:你需要參照第一章的內容,為這個模塊創建“app/etc/modules/Alanstormdotcom_Layoutviewer.xml”】,打開如下URL
https://example.com/helloworld/index/index?showLayout=page
你看到的是你正在請求的頁麵的布局文件。它是由<block />,<reference />和<remove />組成的。當你在執行方法中調用“loadLayout”時,Magento會做如下處理
生成這個布局文件
為每一個<block />和<reference />標簽實例化一個塊對象。塊對象的類名是通過標簽的name屬性來查找的。這些塊對象被存儲在布局對象的_blocks數組中
如果<block />標簽包含了output屬性,那麼這個塊的名字和output屬性的值會被添加到布局對象的_output數組中
然後,當你在執行方法中調用“renderLayout”方法是,Magento會遍曆_output數組中所有的塊名字,從_blocks數組中獲得該名字的塊,並調用塊對象中使用output屬性的值作為名字的函數。這個函數往往是“toHtml”。這個output屬性也告訴Magento這裏就是輸出HTML的起點,也就是頂層塊。【譯者注:直接閱讀Layout類的代碼應該比較容易理解這裏的邏輯
File: app/code/core/Mage/Core/Model/Layout.php
public function getOutput()
{
    $out = '';
    if (!empty($this->_output)) {
        foreach ($this->_output as $callback) {
            $out .= $this->getBlock($callback[0])->$callback[1]();
        }
    }
    return $out;
}

從這裏我們也可以看出,一個頁麵的布局文件時可以擁有多個頂層塊。

下麵我們要講解塊對象是如何被實例化的,這個布局文件時如何被生成的,最後我們將動手做一個例子來實踐這一章講的內容。

實例化塊對象

在布局文件中,<block />和<reference />標簽有一個“type”屬性,這個屬性其實是一個URI
<block type="page/html" ...
<block type="page/template_links"

Magento就是通過這個URI是用來查找塊對應的類名。這個URI分為兩部分,第一部分“page”是用來在全局配置中查找一個基本類名,第二部分“html”或者“template_link”將被添加到基本類名後麵生成一個具體的將被實例化的類名。

我們以“page/html”為例。首先Magento在全局配置中找到節點
/global/blocks/page
有以下內容
<page>
    <class>
        Mage_Page_Block
    </class>
</page>

這裏我們拿到了一個基本類名“Mage_Page_Block”,然後添加URI的第二部分“html”到基本類名後麵,我們就得到最終的塊對象的類名“Mage_Page_Block_Html”。塊的類名在Magento中被稱為“分組類名”(Grouped Class Names),這些類都用相似的方法被實例化。我們將在以後的章節中詳細介紹這個概念。

<block />和<reference />的區別

我們上麵提到<block />和<reference />都會實例化塊對象,那麼它們究竟有什麼區別呢? <reference />在布局文件中是用來表示替換一個已經存在的塊,舉個例子
<block type="page/html" name="root" output="toHtml" template="page/2columns-left.phtml">
    <!-- ... sub blocks ... -->
</block>
<!-- ... -->
<reference name="root">
    <block type="page/someothertype" name="root" template="path/to/some/other/template" />
    <!-- ... sub blocks ... -->
    </block>
</reference>

Magento首先創建了一個名叫“root”的塊。然後,它有發現了一個引用(reference)的名字也叫“root”,Magento會把原來那個“root”塊替換成<reference />標簽裏麵的那個快。

再來看看我們之前創建那個local.xml
<layout version="0.1.0">
    <default>
        <reference name="root">
            <block type="page/html" name="root" output="toHtml" template="../../../../../code/local/Alanstormdotcom/Helloworld/simple_page.phtml" />
        </reference>
    </default>
</layout>   

在這裏,塊“root”被我們用<reference />替換了,指向了一個不同的模板文件。

布局文件是如何生成的

現在我們對布局文件已經有所了解了,但是這個布局文件是那裏來的呢?要回答這個問題,我們得引入Magento中的另外兩個概念,操作(Handle)和包布局(Package Layout)。

操作

Magento會為每一個頁麵請求生成幾個不同的操作。我們的Layout View模塊可以顯示這些處理器
https://example.com/helloworld/index/index?showLayout=handles
你應該看到類似如下列表的列表(和你的配置有關)
default
STORE_bare_us
THEME_frontend_default_default
helloworld_index_index
customer_logged_out

它們每一個都是一個操作的名字。我們可以在Magento係統的不同的地方配置操作。在這裏我們需要關注兩個操作 “default” 和 “helloworld_index_index”。“default”處理器是Magento的默認處理器,參與每一個請求的處理。“helloworld_index_index”處理器的名字是frontname “helloworld”加上執行控製器的名字“index”再加上執行方法的名字“index”。這說明執行控製器的每一個執行方法都有一個相應的操作。

我們說過“index”是Magento默認的執行控製器和執行方法的名字,所以以下請求的操作名字也是“helloworld_index_index”。
https://example.com/helloworld/?showLayout=handles

包布局

包布局和我們以前講過的全局配置有些相似。它是一個巨大的XML文檔包含了Magento所有的布局配置。我們可以通過以Layout View模塊來查看包布局,請求一下URL
https://example.com/helloworld/index/index?showLayout=package
你可能要等一會兒才能看到輸出,因為文件很大。如果你的瀏覽器在渲染XML的時候卡死了,建議你換成text格式的
https://example.com/helloworld/index/index?showLayout=package&showLayoutFormat=text
假設你選擇的是XML格式輸出,那麼你應該看到一個巨大的XML文件,這就是包布局。這個文件時Magento動態生成的,合並當前主題(theme)下麵所有的布局文件。如果你用的是默認安裝的話,這些布局文件在以下目錄
app/design/frontend/base/default/layout/
其實在全局配置中,有一個<updates />節點下麵定義了所有將被裝載的布局文件
<layout>
    <updates>
        <core>
            <file>core.xml</file>
        </core>
        <page>
            <file>page.xml</file>
        </page>
        ...
    </updates>
</layout>

當這些文件被裝載以後,Magento還會裝載最後一個布局文件,local.xml,也就是我們之前新建的那個文件。我們可以通過這個文件來定製Magento的布局。

結合操作和包布局

在包布局文件中,我們可以看到一些熟悉的標簽<block />,<reference />等等,但是他們都包含在一下這些標簽中
<default />
<catalogsearch_advanced_index />
etc...

這些就是操作標簽。對於每個特定的請求來說,針對這個請求的布局文件是由包布局中所有和這個請求相關的操作標簽組成的。比如我們上麵的例子,和請求相關的操作標簽如下
<default />
<STORE_bare_us />
<THEME_frontend_default_default />
<helloworld_index_index />
<customer_logged_out />

所以,針對請求
https://example.com/helloworld/index/index
布局文件就是包布局中上麵這些標簽的內容組合。在包布局文件中,還有一個標簽<update />值得我們注意。我們可以通過這個標簽引入另外一個操作標簽。比如
<customer_account_index>
    <!-- ... -->
    <update handle="customer_account"/>
    <!-- ... -->
</customer_account_index>

這段代碼的意思是,如果一個請求包含了“customer_acount_index”操作,那麼這個請求的布局文件也應該包含“customer_account”操作標簽下麵的<block />和<reference />。

更新我們的例子

好了,理論講完了,讓我們來修改我們的例子,把這一章的內容實踐一下。我們重新來看local.xml
<layout version="0.1.0">
    <default>
        <reference name="root">
            <block type="page/html" name="root" output="toHtml" template="../../../../../code/local/Alanstormdotcom/Helloworld/simple_page.phtml" />
        </reference>
    </default>
</layout>

我們用一個引用(reference)覆蓋了名為“root”的塊。然後定義了一個新的塊,指向了一個不同的模板文件。我們把這個引用放在<default />操作標簽下麵,那就說明這個Layout將對所有的請求有效。如果你訪問Magento自帶的一些頁麵,你會發現它們要門是空白,要麼就是和我們“hello world”例子的紅色背景,但這並不是我們想要的效果。我們來修改一下local.xml,讓我們的模板僅對“hello world”的請求有效。
<layout version="0.1.0">
    <helloworld_index_index>
        <reference name="root">
            <block type="page/html" name="root" output="toHtml" template="../../../../../code/local/Alanstormdotcom/Helloworld/simple_page.phtml" />
        </reference>
    </helloworld_index_index>
</layout>

我們把操作標簽換成了“helloworld_index_index”。清空Magento緩存,重新訪問Magento的各個頁麵,你應該發現都恢複了正常,但是針對"hello world"模塊的請求頁麵還是我們自定義的那個。

目前我們隻實現了一個“index”執行函數,現在我們來實現“goodbye”執行函數。修改我們的執行控製器代碼如下
public function goodbyeAction() {
    $this->loadLayout();
    $this->renderLayout();          
}      

但是你訪問一下頁麵的時候你還是會看到Magento的默認布局
https://example.com/helloworld/index/goodbye
那是因為我們沒有為這個請求定義布局。我們需要在local.xml中添加“helloworld_index_goodbye”標簽。由於“index”請求和“goodbye”請求我們要套用的布局是一樣的,所以我們將用<update>標簽來重用已有的配置
<layout version="0.1.0">
    <!-- ... -->
    <helloworld_index_goodbye>
        <update handle="helloworld_index_index" />
    </helloworld_index_goodbye>
</layout>

清空Magento緩存,請求以下URL
https://example.com/helloworld/index/index
https://example.com/helloworld/index/goodbye

你將會得到兩個完全相同的頁麵。

開始輸出和getChildHtml方法

在Magento默認的配置下,HTML輸出是從名為“root”的塊開始(其實是因為這個塊擁有output屬性【譯者注:任何一個擁有output屬性的塊都是頂層塊,在擁有多個頂層塊的情況下Magento將按照塊定義的先後順序輸出HTML】)。我們覆蓋了“root”塊的模板
template="../../../../../code/local/Alanstormdotcom/Helloworld/simple_page.phtml"
模板文件的查找路徑是當前主題(theme)的根目錄,Magento默認設置時這裏
app/design/frontend/base/default

為頁麵加入內容

到目前為止,我們的頁麵都比較無聊,啥也沒有。我們來為頁麵加點有意義的內容。修改local.xml如下
<helloworld_index_index>
    <reference name="root">
        <block type="page/html" name="root" template="../../../../../code/local/Alanstormdotcom/Helloworld/simple_page.phtml">
            <block type="customer/form_register" name="customer_form_register" template="customer/form/register.phtml"/>
        </block>
    </reference>
</helloworld_index_index>

我們在“root”塊裏麵嵌套了一個塊“customer_form_register”。這個塊是Magento本來就有的,包含了一張用戶注冊表單。我們把這個塊嵌套進來,那麼我們在模板文件裏麵就能用這個塊的內容。使用方法如下,修改simple_page.phtml
<body>
    <?php echo $this->getChildHtml('customer_form_register'); ?>
</body>

這裏“getChildHtml”的參數就是要引入的塊的名字,使用起來相當方便。清空Magento緩存,刷新hello world頁麵,你應該在紅色背景上看到用戶注冊表單。Magento還有一個塊,叫做“top.links”,讓我們把它也加進來。修改simple_page.html
<body>
    <h1>Links</h1>
    <?php echo $this->getChildHtml('top.links'); ?>
    <?php echo $this->getChildHtml('customer_form_register'); ?>
</body>

刷新頁麵,你會發現<h1>Links</h1>顯示出來了,但是“top.links”什麼都沒有顯示。那是因為我們並沒有把這個塊引入到local.xml,所以Magento找不到這個塊。“getChildHtml”的參數一定要是當前頁麵的布局文件中聲明過的塊。這樣的話Magento就可以隻實例化需要用到的塊,節省了資源,我們也可以根據需要為塊設置不同的模板文件。

我們修改local.xml文件如下
<helloworld_index_index>
    <reference name="root">
        <block type="page/html" name="root" template="../../../../../code/local/Alanstormdotcom/Helloworld/simple_page.phtml">
            <block type="page/template_links" name="top.links"/>
            <block type="customer/form_register" name="customer_form_register" template="customer/form/register.phtml"/>
        </block>
    </reference>
</helloworld_index_index>

清空Magento緩存,刷新頁麵,你會看到一排鏈接顯示出來了。【譯者注:如果你細心一點的話你會發現“top.links”塊沒有template屬性,那是因為這個塊的類中一定定義了默認的模板
protected function _construct()
{
    $this->setTemplate('page/template/links.phtml');
}

總結

這一章我們講解了布局的基礎知識。你可能會覺得這個很複雜,但是你也不必過分擔心,因為平常使用Magento是不會用到這些知識的,Magento提供的默認布局應該可以滿足大部分需求。對於想要深入研究Magento的開發者來說,理解Magento的布局是至關重要的。布局,塊和模板構成了Magento MVC架構中的View,這也是Magento的特色之一。

 

源文:https://www.zhlmmc.com/?p=594

最後更新:2017-04-02 05:21:04

  上一篇:go magento -- 在導出CSV時進行轉碼以保證用excel打開有中文的CSV文件時不會亂碼
  下一篇:go 當你學不進去的時候,不妨看看大腦是怎麼想的?