刚入门magento的开发者容易吧布局和视图给混淆. 本文将看看Magento的Layout/Block的做法, 并告诉您如何将其融入Magento的MVC的世界观。
与许多流行的MVC系统相比,Magento的执行控制器不通过数据对象到视图或在视图对象中设置属性(只有少数例外)。相反,视图组件直接引用系统模型来获得它需要显示的信息。
这样的设计决策的后果之一是,视图分成块和模板。块是PHP对象,模板是包含HTML和PHP(在这里PHP作为模板语言)“原始”的PHP文件(带.phtml扩展名)的组合。每一个块都和一个唯一的模板文件绑定。在模板文件 phtml中,“$this”就是指该模板文件对应的块对象。
一个简单的例子
看一个默认的产品模板的文件
app/design/frontend/base/default/template/catalog/product/list.phtml
你会看到下面的PHP模板代码。
<?php $_productCollection=$this->getLoadedProductCollection() ?>
<?php if(!$_productCollection->count()): ?> <div class="note-msg">
<?php echo $this->__("There are no products matching the selection.") ?>
</div> <?php else: ?>
...
getLoadedProductCollection method方法可以在改模板的Block的class中找到。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的。
块调用块调用块是如何为您的网页的整个HTML布局中创建的。看看一列布局模板。
File: app/design/frontend/base/default/template/page/one-column.phtml
<!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 class="page-popup <?php echo $this->getBodyClass()?$this->getBodyClass():'' ?>">
<?php echo $this->getChildHtml('content') ?>
<?php echo $this->getChildHtml('before_body_end') ?>
<?php echo $this->getAbsoluteFooter() ?>
</body>
模板本身只有11行代码。然而,每次调用$this-> getChildHtml(…)将包含和引入另一个块。使用getChildHtml将依次引入另外一个块的HTML 内容,直到最底层的块。
Magento布局文件
到这里,块和模板你大概已经熟悉了,但你可能有以下疑问
Magento 怎么知道在一个页面上要用那些块?
Magento 怎么知道哪一个块是顶层块?
$this->getChildHtml(…)”里面的参数是什么意思?块的名字吗?
Magento 引入了布局对象(Layout Object)来解决上面的那些问题。布局对象(或者说布局文件)就是一个 XML文件,定义了一个页面包含了哪些块,并且定义了哪个块是顶层块。
上次我们是直接从我们的操作方法中输出内容。这一次,让我们为我们的Hello World模块,一个简单的HTML模板。
首先,创建一个文件:
app/design/frontend/base/default/layout/local.xml
包含以下内容
<layout version="0.1.0">
<default>
<block type="page/html" name="root" output="toHtml" template="jbw/helloworld/simple_page.phtml" />
</default>
</layout>
创建模板文件
app/design/frontend/base/default/template/jbw/helloworld/simple_page.phtml
加入以下内容
<!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>Hello World</title>
<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缓存并加载你的Hello World器页面(URL“ http://exmaple.com/helloworld/index/index ”)。现在,你应该可以看到一个明显的红色背景,并且simple_page.phtml的HTML源代码和你打开网站的源码一样。
这是怎么回事呢?
也许你看到这里一头雾水,没关系,我们来慢慢解释
首先,你要安装一个Layout Viewer模块。这类似于你建的Hello World的文章,可以让我们看一些Magento的内部在Yljtest模块一个模块。
一旦你安装了这个模块(类似于你如何设置Yljtest模块),请访问以下网址
http://example.com/helloworld/index/index?showLayout=page
这是你正在请求页面的布局XML文件,它由<block />, <reference /> 和 <remove />标签组成. 当你调用方法控制器中的loadLayout时, Magento 将
生成布局的XML文件
为每一个<block />标签生成一个Block类,查找使用标签的type属性的类作为一个全局性的配置路径,并将其保存在布局对象internal _blocks数组中,使用标签的name属性为数组的key。
如果<block />标签包含一个输出属性,它的值将被添加到布局对象的internal _output数组中。
然后,当你在你的动作控制器调用renderLayout方法是,Magento将在_output数组中遍历所有的块,使用输出属性的值作为一个回调方法。这始终是toHtml,以及起点输出将是块的模板。
以下各节将介绍如何块被实例化,这个布局文件是如何产生的,并且完成了输出过程。
Magento块实例化
在布局文件中,<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”,然后添加“html”到基本类名后面,我们就得到最终的块对象的类名
“Mage_Page_Block_Html”。块的类名在 Magento中被称为(Grouped Class Names),这些类都用相似的方法被实例化。
如果我们创建一个已经存在的且具有相同名称的块的块,新的块实例将取代原来的实例。这时我们的引用(reference)文件从local.xom中。
<layout version="0.1.0">
<default>
<block type="page/html" name="root" output="toHtml" template="jbw/helloworld/simple_page.phtml" />
</default>
</layout>
在这里,块“root”被我们用<reference />替换了,指向了一个不同的模板文件。
references的使用
<refernce name=”” />将挂钩于一个指定name的xml块中<refernce name=”” />。<block />节点将作为子块来被父块引用。
<layout version="0.1.0">
<default>
<block type="page/html" name="root" output="toHtml" template="page/2columns-left.phtml">
<!-- ... sub blocks ... -->
</block>
</default>
</layout>
在不同的布局文件:
<layout version="0.1.0">
<default>
<reference name="root">
<!-- ... another sub block ... -->
<block type="page/someothertype" name="some.other.block.name" template="path/to/some/other/template" />
</reference>
</default>
</layout>
尽管root block是在一个单独的XML配置文件中声明,新的block作为子块添加块。 Magento最初创建一个名为root的page/ html块。我们仍然可以用reference来分配新块some.other.block.name来作为root的子块。
布局文件是如何生成的
现在我们对布局文件已经有所了解了,但是这个布局文件是那里来的呢?要回答这个问题,我们得引入Magento 中的另外两个概念,句柄(Handle)和包布局(Package Layout)。
句柄
Magento 会为每一个页面请求生成几个不同的操作。我们的Layout View模块可以显示这些处理器
http://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手柄是由路由名称(HelloWorld),动作控制器的名称(index),和动作控制器动作方法(index)组合成一个字符串创建。 这意味着,在动作控制器的每个方法都有它关联的句柄。
请记住,“index”是Magento默认的动作控制器和操作方法,所以下面的请求
http://example.com/helloworld/?showLayout=handles
同时将产生一个手柄名为helloworld_index_index
包布局
包布局和我们以前讲过的全局配置有些相似。它是一个巨大的XML 文档包含了Magento 所有的布局配置。我们可以通过以Layout View模块来查看包布局,请求一下 URL
http://example.com/helloworld/index/index?showLayout=package
这可能需要一段时间来加载。如果您的浏览器呈现XML时卡死了,尝试文本格式(txt)
http://example.com/helloworld/index/index?showLayout=package&showLayoutFormat=text
你应该可以看到一个非常大的XML文件。这是包布局。这个XML文件是通过结合所有的XML布局文件的内容形成当前的主题(或包)创建的。如果是默认安装,在以下目录
app/design/frontend/base/default/layout/
幕后有<frontend><layout><updates /> 和 <adminhtml><layout><updates />全局配置的区域包含所有文件名的节点加载相应区域。一旦在配置中列出的文件被合并,Magento的合并将在最后一个XML文件中,引用(reference)。在这里,你能够自定义添加到您的Magento安装文件。
结合句柄和包布局
所以,如果你看一下包布局,你会看到一些熟悉的标记,如<block /> and <reference />,但他们都被标签包围着,例如
<default />
<catalogsearch_advanced_index />
etc...
这些就是操作标签。对于每个特定的请求来说,针对这个请求的布局文件是由包布局中所有和这个请求相关的操作标签组成的。比如我们上面的例子,和请求相关的操作标签如下
<default />
<STORE_bare_us />
<THEME_frontend_default_default />
<helloworld_index_index />
<customer_logged_out />
有一个额外的标签,你需要注意包布局,<update />标签可以让你有另一个句柄的标签。例如
<customer_account_index>
<!-- ... -->
<update handle="customer_account"/>
<!-- ... -->
</customer_account_index>
这段代码的意思是,如果一个请求包含了“customer_acount_index“,那么这个请求的布局文件也应该包含“customer_account”句柄标签
运用我们所学
好了,理论讲完了。让我们回到我们之前做过的,了解我们现在要做什么,添加
<layout version="0.1.0">
<default>
<block type="page/html" name="root" output="toHtml" template="jbw/helloworld/simple_page.phtml" />
</default>
</layout>
我们来看 local.xml,我们已经用不同的块覆盖了“root”标签。把这个放在<default />句柄里,这个句柄是我们已经确保了在系统中每一个页面请求将被覆盖。这可能不是我们想要的。
如果你到你的Magento网站任何其他网页,你会发现他们要么空白,或具有相同的红色背景在你的hello world 页面。让我们改变你的文件local.xml,因此只适用于这个Hello World页面。我们将通过改变默认的,而使用完整的动作名称句柄(helloworld_index_index)做到这一点。
<layout version="0.1.0">
<helloworld_index_index>
<block type="page/html" name="root" output="toHtml" template="jbw/helloworld/simple_page.phtml" />
</helloworld_index_index>
</layout>
清空Magento缓存,并重新请求magento的各个页面,你应该发现都恢复正常了,但是针对”hello world”模块的请求页面还是我们自定义的那个。
眼下这仅适用于我们的index操作方法。让我们把它添加到goodbye动作方法为好。在动作控制器,修改goodbye的动作,如
public function goodbyeAction() {
$this->loadLayout();
$this->renderLayout();
}
如果加载了下面的URL,你会发现你得到仍然是默认Magento布局。
http://example.com/helloworld/index/goodbye
We need to add a Handle for the full action name (helloworld_index_goodbye) to our local.xml file. Rather than specify a new <bloxk />, lets use the update tag to include the helloworld_index_index Handle.我们需要添加一个句柄全动作名称(helloworld_index_goodbye)到我们的local.xml文件。而不是指定一个新的<bloxk/>,允许使用更新标记来包含helloworld_index_index手柄。
<layout version="0.1.0">
<!-- ... -->
<helloworld_index_goodbye>
<update handle="helloworld_index_index" />
</helloworld_index_goodbye>
</layout>
加载以下页面(清除Magento缓存后)现在应该产生相同的结果。
http://example.com/helloworld/index/index
http://example.com/helloworld/index/goodbye
开始输出和getChildHtml
In a standard configuration, output starts on the Block named root (because it has an output attribute). We’ve overridden root’s Template with our own在标准配置中,输出开始于名为root块(因为它有一个output输出属性)。我们用我们自己的覆盖root模板
template="jbw/helloworld/simple_page.phtml"
模板是从当前主题的root文件夹引用的。在这种情况下,这是
app/design/frontend/base/default
所以我们需要深入到我们的自定义页面。大多数Magento模板存储在
app/design/frontend/base/default/templates
结合我们的完整路径
app/design/frontend/base/default/templates/jbw/helloworld/simple_page.phtml
添加内容块
一个简单的红色的页面是很无聊的。让我们添加一些内容到这个网页。改变你的<helloworld_index_index/>在local.xml文件,所以它看起来像下面的
<helloworld_index_index>
<block type="page/html" name="root" output="toHtml" template="jbw/helloworld/simple_page.phtml">
<block type="customer/form_register" name="customer_form_register" template="customer/form/register.phtml"/>
</block>
</helloworld_index_index>
We’re adding a new Block nested within our root. This is a Block that’s distributed with Magento, and will display a customer registration form. By nesting this Block within our root Block, we’ve made it available to be pulled into our simple_page.html Template. Next, we’ll use the Block’s getChildHtml method in our simple_page.phtml file. Edit simple_page.html so it looks like this
我们增加一个新的模块嵌套在我们的root。这是一个Magento分布式,并会显示客户登记表的block。在我们的root块中嵌套这个block,我们已经将其提放进了我们的simple_page.html模板。接下来,我们将在我们的simple_page.phtml文件中使用块的getChildHtml方法。编辑simple_page.html,所以它看起来是这样的
<body>
<?php echo $this->getChildHtml('customer_form_register'); ?>
</body>
Clear your Magento cache and reload the page and you should see the customer registration form on your red background. Magento also has a Block named top.links. Let’s try including that. Change your simple_page.html file so it reads清空Magento缓存并刷新页面,你会看到你的红色背景的客户登记表。 Magento的也有一个叫块top.links。让我们尝试包含他。改变你的simple_page.html文件,以便它读取
<body>
<h1>Links</h1>
<?php echo $this->getChildHtml('top.links'); ?>
</body>
When you reload the page, you’ll notice that your <h1>Links</h1> title is rendering, but nothing is rendering for top.links. That’s because we didn’t add it to local.xml. The getChildHtml method can only include Blocks that are specified as sub-Blocks in the Layout. This allows Magento to only instantiate the Blocks it needs, and also allows you to set difference Templates for Blocks based on context.当你刷新页面,你会发现,你的<h1>Links</h1>标题显示出来了,但top.links什么都没有显示。这是因为我们没有将它添加到local.xml中。该getChildHtml方法只能包括被指定为子块的布局块。这样Magento只实例化它需要的块,并且还允许您设置不同的模板基于上下文块。
让我们添加top.links到我们的local.xml
<helloworld_index_index>
<block type="page/html" name="root" output="toHtml" template="jbw/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>
</helloworld_index_index>
清除缓存,并重新加载页面。您现在应该看到top.links模块。
Time for action
在我们总结这一刻之前,我们还有一个更重要的概念”覆盖“,那就是<action />标签 ,and that is the <action /> tag. Using the <action /> tag enables us to call public PHP methods of the block classes. So instead of changing the template of the root block by replacing the block instance with our own, we can use a call to setTemplate instead.使用<action />标签,使我们能够调用该块类的公共PHP方法。而不是通过替换我们自己的块实例来更改root块的模板,我们可以用一个调用方法setTemplate来代替。
<layout version="0.1.0">
<helloworld_index_index>
<reference name="root">
<action method="setTemplate">
<template>jbw/helloworld/simple_page.phtml</template>
</action>
<block type="page/template_links" name="top.links"/>
<block type="customer/form_register" name="customer_form_register" template="customer/form/register.phtml"/>
</reference>
</helloworld_index_index>
</layout>
This layout XML will first set the template property of the root block, and then will add the two blocks we use as child blocks. Once we clear the cache, the result should look just as before. The benefit of using the <action /> is the same block instance is used that was created earlier, and all other parent/child associations still exist. For that reason this is a more upgrade proof way of implementing our changes.这种布局的XML将首先设置root块的模板属性,然后将增加我们的两个子块。一旦我们清除缓存,结果看起来应该像以前一样。使用<action />的好处是同一个块实例可以用前面创建过的,和所有其他的父/子关联仍然存在。出于这个原因,这是我们一个重要的变化升级方式。
All arguments to the action’s method need to be wrapped in an individual child node of the <action /> tag. The name of that node doesn’t matter, only the order of the nodes. We could have written the action node from the previous example as follows with the same effect.
该操作的方法的所有参数需要被包裹在<action />标签的单个子节点。该节点的名字也没关系,仅仅是节点的顺序。我们可以写上一示例的具有相同的效果<action />节点如下。
<action method="setTemplate">
<some_new_template>jbw/helloworld/simple_page.phtml</some_new_template>
</action>
这只是为了说明这一行动的说法,节点名是任意的。