作者:alan storm
翻譯:hailong zhang
我們接着研究magento。根據我們第二章講的magento mvc的架構,我們接下來應該講模型(model),但是我們跳過模型先來看布局和塊。和一些流行的php mvc架構不同的是,magento的執行控制器不直接将資料傳給試圖,相反的視圖将直接引用模型,從模型取資料。這樣的設計就導緻了視圖被拆分成兩部分,塊(block)和模闆(template)。塊是php對象,而模闆是原始php檔案,混合了xhtml和php代碼(也就是把php作為模闆語言來使用了)。每一個塊都和一個唯一的模闆檔案綁定。在模闆檔案phtml中,“$this”就是指該模闆檔案對應的快對象。
讓我們來看一個例子
<code>file: app/design/frontend/base/default/template/catalog/product/list.phtml</code>
你将看到如下代碼
<code><?php $_productcollection=$this->getloadedproductcollection() ?> <?php if(!$_productcollection->count()): ?> <p class="note-msg"><?php echo $this->__('there are no products matching the selection.') ?></p> <?php else: ?></code>
這裡“getloadedproductcollection”方法可以在這個模闆的塊對象“mage_catalog_block_product_list”中找到
<code>file: app/code/core/mage/catalog/block/product/list.php ... public function getloadedproductcollection() { return $this->_getproductcollection(); } ...</code>
塊的“_getproductcollection”方法會執行個體化模型,并讀取資料然後傳回給模闆。
magento把視圖分離成塊和模闆的真正強大之處在于“getchildhtml”方法。這個方法可以讓你實作在塊中嵌套塊的功能。頂層的塊調用第二層的塊,然後是第三層……這就是magento如何輸出html的。讓我們來看一下單列的頂層模闆
<code>file: app/design/frontend/base/default/template/page/1column.phtml</code>
<code><!doctype html public "-//w3c//dtd xhtml 1.0 strict//en" "http://www.w3.org/tr/xhtml1/dtd/xhtml1-strict.dtd"> <html xmlns="http://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()?' class="'.$this->getbodyclass().'"':'' ?>> <?php echo $this->getchildhtml('after_body_start') ?> <div class="wrapper"> <?php echo $this->getchildhtml('global_notices') ?> <div class="page"> <?php echo $this->getchildhtml('header') ?> <div class="main-container col1-layout"> <div class="main"> <?php echo $this->getchildhtml('breadcrumbs') ?> <div class="col-main"> <?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></code>
我們可以看到這個模闆裡面很多地調用了“$this->getchildhtml(…)”。每次調用都會引入另外一個塊的html内容,直到最底層的塊。
看到這裡,你可能有這樣的疑問
magento怎麼知道在一個頁面上要用那些塊?
magento怎麼知道哪一個塊是頂層塊?
“$this->getchildhtml(…)”裡面的參數是什麼意思?塊的名字嗎?
magento引入了布局對象(layout object)來解決上面的那些問題。布局對象(或者說布局檔案)就是一個xml檔案,定義了一個頁面包含了哪些塊,并且定義了哪個塊是頂層塊。
在第二章的時候我們在執行方法(action method)裡面直接輸出了html内容。現在我們要為我們的hello world子產品建立一個簡單的html模闆。首先我們要建立如下檔案
<code>app/design/frontend/default/default/layout/local.xml</code>
包含以下内容
<code><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> </code>
再建立如下檔案
<code>app/code/local/alanstormdotcom/helloworld/simple_page.phtml</code>
<code><!doctype html public "-//w3c//dtd xhtml 1.0 strict//en" "http://www.w3.org/tr/xhtml1/dtd/xhtml1-strict.dtd"> <html xmlns="http://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></code>
最後,我們要在執行控制器裡面調用布局檔案,開始輸出html。修改執行方法如下
<code>public function indexaction() { //remove our previous echo //echo 'hello index!'; $this->loadlayout(); $this->renderlayout(); }</code>
清空magento緩存,通路url “http://exmaple.com/helloworld/index/index”。你應該看到一個純紅色背景的頁面。這個頁面的源代碼應該和我們建立的檔案“simple_page.phtml”一模一樣。
也許你看到這裡一頭霧水,沒關系,我們來慢慢解釋。首先你得安裝一個 layout viewer 子產品,這和我們第一章講的 config viewer 子產品很相似,都是檢視magento的内部資訊。安裝完這個子產品之後【譯者注:你需要參照第一章的内容,為這個子產品建立“app/etc/modules/alanstormdotcom_layoutviewer.xml”】,打開如下url
<code>http://example.com/helloworld/index/index?showlayout=page</code>
你看到的是你正在請求的頁面的布局檔案。它是由<block />,<reference />和<remove />組成的。當你在執行方法中調用“loadlayout”時,magento會做如下處理
生成這個布局檔案
為每一個<block />和<reference />标簽執行個體化一個塊對象。塊對象的類名是通過标簽的name屬性來查找的。這些塊對象被存儲在布局對象的_blocks數組中
如果<block />标簽包含了output屬性,那麼這個塊的名字和output屬性的值會被添加到布局對象的_output數組中
然後,當你在執行方法中調用“renderlayout”方法是,magento會周遊_output數組中所有的塊名字,從_blocks數組中獲得該名字的塊,并調用塊對象中使用output屬性的值作為名字的函數。這個函數往往是“tohtml”。這個output屬性也告訴magento這裡就是輸出html的起點,也就是頂層塊。【譯者注:直接閱讀layout類的代碼應該比較容易了解這裡的邏輯
<code>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; }</code>
從這裡我們也可以看出,一個頁面的布局檔案時可以擁有多個頂層塊。
】
下面我們要講解塊對象是如何被執行個體化的,這個布局檔案時如何被生成的,最後我們将動手做一個例子來實踐這一章講的内容。
在布局檔案中,<block />和<reference />标簽有一個“type”屬性,這個屬性其實是一個uri
<code><block type="page/html" ... <block type="page/template_links"</code>
magento就是通過這個uri是用來查找塊對應的類名。這個uri分為兩部分,第一部分“page”是用來在全局配置中查找一個基本類名,第二部分“html”或者“template_link”将被添加到基本類名後面生成一個具體的将被執行個體化的類名。
我們以“page/html”為例。首先magento在全局配置中找到節點
<code>/global/blocks/page</code>
有以下内容
<code><page> <class> mage_page_block </class> </page></code>
這裡我們拿到了一個基本類名“mage_page_block”,然後添加uri的第二部分“html”到基本類名後面,我們就得到最終的塊對象的類名“mage_page_block_html”。塊的類名在magento中被稱為“分組類名”(grouped class names),這些類都用相似的方法被執行個體化。我們将在以後的章節中詳細介紹這個概念。
我們上面提到<block />和<reference />都會執行個體化塊對象,那麼它們究竟有什麼差別呢? <reference />在布局檔案中是用來表示替換一個已經存在的塊,舉個例子
<code><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></code>
magento首先建立了一個名叫“root”的塊。然後,它有發現了一個引用(reference)的名字也叫“root”,magento會把原來那個“root”塊替換成<reference />标簽裡面的那個快。
再來看看我們之前建立那個local.xml
<code><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> </code>
在這裡,塊“root”被我們用<reference />替換了,指向了一個不同的模闆檔案。
現在我們對布局檔案已經有所了解了,但是這個布局檔案是那裡來的呢?要回答這個問題,我們得引入magento中的另外兩個概念,操作(handle)和包布局(package layout)。
magento會為每一個頁面請求生成幾個不同的操作。我們的layout view子產品可以顯示這些處理器
<code>http://example.com/helloworld/index/index?showlayout=handles</code>
你應該看到類似如下清單的清單(和你的配置有關)
<code>default store_bare_us theme_frontend_default_default helloworld_index_index customer_logged_out</code>
它們每一個都是一個操作的名字。我們可以在magento系統的不同的地方配置操作。在這裡我們需要關注兩個操作 “default” 和 “helloworld_index_index”。“default”處理器是magento的預設處理器,參與每一個請求的處理。“helloworld_index_index”處理器的名字是frontname “helloworld”加上執行控制器的名字“index”再加上執行方法的名字“index”。這說明執行控制器的每一個執行方法都有一個相應的操作。
我們說過“index”是magento預設的執行控制器和執行方法的名字,是以以下請求的操作名字也是“helloworld_index_index”。
<code>http://example.com/helloworld/?showlayout=handles</code>
包布局和我們以前講過的全局配置有些相似。它是一個巨大的xml文檔包含了magento所有的布局配置。我們可以通過以layout view子產品來檢視包布局,請求一下url
<code>http://example.com/helloworld/index/index?showlayout=package</code>
你可能要等一會兒才能看到輸出,因為檔案很大。如果你的浏覽器在渲染xml的時候卡死了,建議你換成text格式的
<code>http://example.com/helloworld/index/index?showlayout=package&showlayoutformat=text</code>
假設你選擇的是xml格式輸出,那麼你應該看到一個巨大的xml檔案,這就是包布局。這個檔案時magento動态生成的,合并目前主題(theme)下面所有的布局檔案。如果你用的是預設安裝的話,這些布局檔案在以下目錄
<code>app/design/frontend/base/default/layout/</code>
其實在全局配置中,有一個<updates />節點下面定義了所有将被裝載的布局檔案
<code><layout> <updates> <core> <file>core.xml</file> </core> <page> <file>page.xml</file> </page> ... </updates> </layout></code>
當這些檔案被裝載以後,magento還會裝載最後一個布局檔案,local.xml,也就是我們之前建立的那個檔案。我們可以通過這個檔案來定制magento的布局。
在包布局檔案中,我們可以看到一些熟悉的标簽<block />,<reference />等等,但是他們都包含在一下這些标簽中
<code><default /> <catalogsearch_advanced_index /> etc...</code>
這些就是操作标簽。對于每個特定的請求來說,針對這個請求的布局檔案是由包布局中所有和這個請求相關的操作标簽組成的。比如我們上面的例子,和請求相關的操作标簽如下
<code><default /> <store_bare_us /> <theme_frontend_default_default /> <helloworld_index_index /> <customer_logged_out /></code>
是以,針對請求
<code>http://example.com/helloworld/index/index</code>
布局檔案就是包布局中上面這些标簽的内容組合。在包布局檔案中,還有一個标簽<update />值得我們注意。我們可以通過這個标簽引入另外一個操作标簽。比如
<code><customer_account_index> <!-- ... --> <update handle="customer_account"/> <!-- ... --> </customer_account_index></code>
這段代碼的意思是,如果一個請求包含了“customer_acount_index”操作,那麼這個請求的布局檔案也應該包含“customer_account”操作标簽下面的<block />和<reference />。
好了,理論講完了,讓我們來修改我們的例子,把這一章的内容實踐一下。我們重新來看local.xml
<code><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></code>
我們用一個引用(reference)覆寫了名為“root”的塊。然後定義了一個新的塊,指向了一個不同的模闆檔案。我們把這個引用放在<default />操作标簽下面,那就說明這個layout将對所有的請求有效。如果你通路magento自帶的一些頁面,你會發現它們要門是空白,要麼就是和我們“hello world”例子的紅色背景,但這并不是我們想要的效果。我們來修改一下local.xml,讓我們的模闆僅對“hello world”的請求有效。
<code><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></code>
我們把操作标簽換成了“helloworld_index_index”。清空magento緩存,重新通路magento的各個頁面,你應該發現都恢複了正常,但是針對"hello world"子產品的請求頁面還是我們自定義的那個。
目前我們隻實作了一個“index”執行函數,現在我們來實作“goodbye”執行函數。修改我們的執行控制器代碼如下
<code>public function goodbyeaction() { $this->loadlayout(); $this->renderlayout(); } </code>
但是你通路一下頁面的時候你還是會看到magento的預設布局
<code>http://example.com/helloworld/index/goodbye</code>
那是因為我們沒有為這個請求定義布局。我們需要在local.xml中添加“helloworld_index_goodbye”标簽。由于“index”請求和“goodbye”請求我們要套用的布局是一樣的,是以我們将用<update>标簽來重用已有的配置
<code><layout version="0.1.0"> <!-- ... --> <helloworld_index_goodbye> <update handle="helloworld_index_index" /> </helloworld_index_goodbye> </layout></code>
清空magento緩存,請求以下url
<code>http://example.com/helloworld/index/index http://example.com/helloworld/index/goodbye</code>
你将會得到兩個完全相同的頁面。
在magento預設的配置下,html輸出是從名為“root”的塊開始(其實是因為這個塊擁有output屬性【譯者注:任何一個擁有output屬性的塊都是頂層塊,在擁有多個頂層塊的情況下magento将按照塊定義的先後順序輸出html】)。我們覆寫了“root”塊的模闆
<code>template="../../../../../code/local/alanstormdotcom/helloworld/simple_page.phtml"</code>
模闆檔案的查找路徑是目前主題(theme)的根目錄,magento預設設定時這裡
<code>app/design/frontend/base/default</code>
到目前為止,我們的頁面都比較無聊,啥也沒有。我們來為頁面加點有意義的内容。修改local.xml如下
<code><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></code>
我們在“root”塊裡面嵌套了一個塊“customer_form_register”。這個塊是magento本來就有的,包含了一張使用者系統資料庫單。我們把這個塊嵌套進來,那麼我們在模闆檔案裡面就能用這個塊的内容。使用方法如下,修改simple_page.phtml
<code><body> <?php echo $this->getchildhtml('customer_form_register'); ?> </body></code>
這裡“getchildhtml”的參數就是要引入的塊的名字,使用起來相當友善。清空magento緩存,重新整理hello world頁面,你應該在紅色背景上看到使用者系統資料庫單。magento還有一個塊,叫做“top.links”,讓我們把它也加進來。修改simple_page.html
<code><body> <h1>links</h1> <?php echo $this->getchildhtml('top.links'); ?> <?php echo $this->getchildhtml('customer_form_register'); ?> </body></code>
重新整理頁面,你會發現<h1>links</h1>顯示出來了,但是“top.links”什麼都沒有顯示。那是因為我們并沒有把這個塊引入到local.xml,是以magento找不到這個塊。“getchildhtml”的參數一定要是目前頁面的布局檔案中聲明過的塊。這樣的話magento就可以隻執行個體化需要用到的塊,節省了資源,我們也可以根據需要為塊設定不同的模闆檔案。
我們修改local.xml檔案如下
<code><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></code>
清空magento緩存,重新整理頁面,你會看到一排連結顯示出來了。【譯者注:如果你細心一點的話你會發現“top.links”塊沒有template屬性,那是因為這個塊的類中一定定義了預設的模闆
<code>protected function _construct() { $this->settemplate('page/template/links.phtml'); }</code>
這一章我們講解了布局的基礎知識。你可能會覺得這個很複雜,但是你也不必過分擔心,因為平常使用magento是不會用到這些知識的,magento提供的預設布局應該可以滿足大部分需求。對于想要深入研究magento的開發者來說,了解magento的布局是至關重要的。布局,塊和模闆構成了magento mvc架構中的view,這也是magento的特色之一。
源文:http://www.zhlmmc.com/?p=594