我们在这个部分关注一下View里Layouts和Blocks。
跟其他主流PHPMVC架构不一样,magento 的ActionController不会把数据对象传给view,也不会设置View对象里的属性。View是通过系统模块去获取它所需要的信息。
这个设计的结果是View被分为Blocks 和Templates。Blocks是PHP对象,Templates是PHP代码和HTML的混合(也可以认为是PHP作为了模版语言)。每个Block绑定到一个Template文件。在一个Phtml文件里,PHP的关键字$this会包含了对Temeplate对应Block的引用。
下面是一个快速的例子。查看模版文件app/design/frontend/base/default/template/catalog/product/list.phtml
会看到如下的代码
<?php$_productCollection=$this->getLoadedProductCollection() ?>
<?phpif(!$_productCollection->count()): ?>
<divclass="note-msg">
<?php echo$this->__("There are no products matching the selection.")?>
</div>
<?php else: ?>
其中的getLoadedProudctController可以在对应的block文件找到
app/code/core/Mage/Catalog/Block/Product/List.php
public functiongetLoadedProductCollection()
{
return$this->_getProductCollection();
}
其中的_getProductCollection会实例化models,并取到数据给对应template。
内嵌Block
Blocks/Templates真正的强大的地方是getChildHtml方法。这可以让我们包含次一级的Block/Template在主的Block/Template里面(xml的格式)
Blocks调用Blocks是会组成我们整个HTMLlayout。看一个例子
App/design/frotend/base/default/template/page/1column.phtml
<!DOCTYPE htmlPUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <htmlxmlns="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> </html>
该文件不长,但是每个调用都是$this->getChildHtml(…),这会包含并渲染其他的block。这些block也可能会调用其他block 。
Layout
尽管Block和Template很好,但是你可能会有下面的疑问
1. 我怎么告诉Magento哪个Block在页面中使用?
2. 我们怎么告诉Magento是初始的
3. 我怎么告诉每个Block去调用下面的block
这时候就需要Layout对象了。Layout对象是Xml格式,定义一个页面里包含哪些Blocks,并且哪些Blocks负责渲染页面。
之前的Hello World项目我们直接在Action Method上做的。这次我们创建一个简单的HTMLtemplate来为该模块服务。
首先创建一个文件
App/design/frontend/base/default/layout/local.xml
然后写入下面的内容
<layoutversion="0.1.0">
<default>
<reference name="root">
<block type="page/html"name="root" output="toHtml"template="simple_page.phtml" />
</reference>
</default>
</layout>
然后再创建一个文件
app/design/frontend/base/default/template/simple_page.phtml(注意和配置里的template一致)
写入以下内容
<!DOCTYPE html PUBLIC"-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<htmlxmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Untitled</title>
<metaname="generator" content="BBEdit 9.2" />
<styletype="text/css">
body { background-color:#f00; }
</style>
</head>
<body>
</body>
</html>
最后在Aciton Controller里负责启动layout过程。增加下面两行代码
public functionindexAction() {
//remove our previous echo
//echo'Hello Index!';
$this->loadLayout();
$this->renderLayout();
}
清除缓存,重新加载Hello World controller页面,可以看到页面的背景hi红色,并且Html源码和simple_page.phtml也是对应的。
发生了什么?
刚才的一切看上去很神秘。我们来仔细的看看这个过程。首先,我们想安装Layoutviewer模块。这个模块和Configviewer很像。
一旦装好了,就可以用下面的URL
http://localhost/magento/helloworld/index/index?showLayout=page
这个是一个layout xml对应于请求的页面。由<block /> <refenece /> 和 <remove />标签组成。当你调用loadLaout方法时,
1. 生成一个Layout xml
2. 实例化<block/> 和<reference />下面的Block类。查找用标签name属性的,在全局配置文件里找到对应的,并储存在Layout对象的internal_blocks数组里。
3. 如果<block />标签包含了输出属性,它的值也加入到Layout对象的internal_blocks数组里。
这样一来,当我们在Action Controller里调用renderLayout时,Mageno 会迭代_blocks数组里所有的Blocks, 并把它对应的输出属性作为回调函数。这相当于是向Html的转化,意味着Block的Template就是输出的起点。
下面的部分涉及到Block如何实例化,Layout文件如何生成,还有output的结束。
Block实例化
在LayoutXml里,<block/>或者<reference/>有个类型相当于是URI
<block type=”page/html” …
<block type=”page/template_links”
URI会在全局配置文件指明一个地址。URI的第一个部分用来去查找全局配置文件,找到Page类名。第二部分跟在第一个部分后面成为新的类, 然后再实例化该类。
以page/html作为例子。首先Magento会在全局配置文件里查找下面的
/global/blocks/page
然后找到
<page>
<class>
Mage_Page_Block
</class>
</page>
这样我们就得到了MagePageBlock类。然后,URI的第二部分会加在它后面成为MagePageBlock_Html。这个类随后就会呗实例化。
Blocks也是Magento里的组类,所有的共享类似的实例化方法。后面会有该部分的详细介绍。
<block />和<reference />之间的不同
我们谈到了<blocks/>和<references />都可以实例化Block,那他们有什么不同呢。
先有
<blocktype="page/html" name="root" output="toHtml"template="page/2columns-left.phtml"> <!-- ... sub blocks ... --></block>
然后有
<layoutversion="0.1.0">
<default>
<reference name="root">
<block type="page/html"name="root" output="toHtml"template="simple_page.phtml" />
</reference>
</default>
</layout>
<reference />里面的blocks不会替代blocks。相反,他们是增加,或者修改现有的blocks。上面的样例中,是插入了一个新的叫root的block到现有的rootblock中。这在Magento Layout中是未定义的。最终结果是老的被替换掉了,但是靠此来保证一致性是很糟的主意。
Layout 文件如何生成
到现在我们对Layout XML应该有比较清晰的认识了。但是Layout XML从何而来?要解答这个问题,我们需要引入两个新的概念,Handles和Package Layout。
Handles
Magento中的每个请求会生成几个不同的Handles。Layoutview 模块就可以给我们用URL展示这些
http://localhost/magento/helloworld/index/index?showLayout=handles
我们会看到类似
1. 默认
2. STORE_bare_us
3. THEME_frontend_default_default
4. Helloworld_index_index
5. Customer_logged_out
这些每个都是一个Handle。Handle在Magento的很多地方会被设置。我们需要关注其中两个地方:default和helloworld_index_index.。默认的Handle是每个请求都会出现的。而Helloworld_index_indexHandle是通过合并frontName(helloworld),Actioncontroller (index), 和Action Controller Action Method(index)而成。这意味这每个ActionController方法都可能对应一个Handle。
记住“index”是Magento对每个Action Controller和ActionMethods的默认,因此下面的请求
http://localhost/magento/helloworld/?showLayout=handles
同样会产生Handle的名字交helloworld_index_index
Package Layout
你可以认为PackageLayout等同于全局配置。他是一个大的XML文件,包含了Magento内每个可能的Layout配置。让我们看一下
http://localhost/magento/helloworld/index/index?showLayout=package
这个可能会加载一会。如果浏览器在xml下卡了,请换成text模式
http://localhost/magento/helloworld/index/index?showLayout=package&showLayoutFormat=text
你可以看到很大的XML文件。这就是Package Layout。它是综合了所有当前主题下XMLLayout的文件。默认的安装是
app/design/frontend/base/default/layout/
在全局文件里面有个<updates />部分,节点中包含了所有要加载的名字。一旦配置文件中给出的文件合并了,Magento会合并到上一个xml文件,local.xml。这个可增加你想要的功能。
合并Hanldes和Packge Layout
如果你看到Package Layout, 你可以看到一些熟悉的标签,例如<block />和<reference/>, 但是他们都类似这样的标签覆盖
<default />
<catelogsearch_advanced_index/>
etc…
这些都是Handle标签。一个请求的Layout是由所有匹配请求的Handles的Package Layout生成。因此,在上面的例子中,我们的layout是在下面的部分中生成
<default />
<STORE_bare_us />
<THEME_frontend_default_default/>
<helloworld_index_index/>
<customer_logged_out/>
还有一个标签需要我们注意。<update />容许我们包含其他的Handle。例如
<customer_account_index>
<!-- ... -->
<update handle="customer_account"/>
<!-- ... -->
</customer_account_index>
这意味这请求到customeraccountindex时,应该包含<customer_account>下面的<reference/>和<block />.
学以致用
看够了理论,我们来回顾一下之前的工作。
<layoutversion="0.1.0">
<default>
<referencename="root">
<blocktype="page/html" name="root" output="toHtml"template="simple_page.phtml" />
</reference>
</default>
</layout>
这个意味着我们重写了root标签。而<default/>部分保证了每次请求都会发生。这可能并不是我们想要的效果。
如果访问任意其他页面,我们同样是空白页面,或者是红色背景(之前helloworl页面的那样)。所以我们改进一下local.xml,确保它只用于helloworld页面。修改如下
<layout version="0.1.0">
<helloworld_index_index>
<referencename="root">
<blocktype="page/html" name="root" output="toHtml"template="simple_page.phtml" />
</reference>
</helloworld_index_index>
</layout>
清除cache,这个时候你的其他页面应该恢复了。
然后应用到googbye Aciton Method
public function goodbyeAction() { $this->loadLayout(); $this->renderLayout(); }
这个时候加载http://localhost/magento/helloworld/index/goodbye
会发现还是blank页面。这个时候我们需要在local.xml增加一个actionname, 内容如下
<layout version="0.1.0">
<!-- ... -->
<helloworld_index_goodbye>
<updatehandle="helloworld_index_index" />
</helloworld_index_goodbye>
</layout>
清楚cache,这个时候,下面两个页面会有同样的效果了。
http://localhost/magento/helloworld/index/index
http://localhost/magento/helloworld/index/goodbye
开始输出并getChildHtml
在标准的配置中,输出的开始是root命名的Block(这个是输出的特性)。我们已经重写了root的模版 template=”simple_page.phtml”
模版会从当前或者base主题的主目录里得到,如
app/design/frontend/base/default/template
通常你可以添加模版到你自己的主题或者默认主题
app/design/frontend/default/default/template
app/design/frontend/default/custom/template
base目录是最后才会去查找的目录,如果magento在其他主题下找不到,才会回到base目录。可是,象之前提到的哪有,你不想加入到这样的目录,因为magento的更新会覆盖他们。
增加内容block
红色的悲剧很无聊。所以我们在页面上增加点内容。改变local.xml里的<helloworldindexindex />,如下
<helloworld_index_index>
<reference name="root">
<block type="page/html"name="root" template="simple_page.phtml">
<blocktype="customer/form_register" name="customer_form_register"template="customer/form/register.phtml"/>
</block>
</reference>
</helloworld_index_index>
我在root里增加两个内嵌的Block。Magento会分配它,并且展示一个客户注册的页面。在root里内嵌这个Block,我们要在simple_page.html里面显式的调用。所以我们用Block的getChildHtml方法,如下
<body>
<?php echo $this->getChildHtml(‘customr_form_register’); ?>
</body>
清除Cache,重新加载页面。这个时候我们看到了注册页面在红色的背景上。下面还有一个Block叫top.links。添加如下
<body>
<h1>Links</ht>
<?php echo $this->getChildHtml(‘top.links’); ?>
</body>
当我们重新加载页面,就看到Links被渲染了,但是top.links没有任何渲染。这是因为我们没有在local.xml里添加它。在Layout里,getChildHtml只能包含显示的作为子block的Blocks。这容许Magento实例化它想要的blocks,同时让我们可以根据显示内容为Block设置不同的模版
我们可以在local.xml里为top.links增加Block
<helloworld_index_index>
<reference name="root">
<block type="page/html" name="root"template="simple_page.phtml">
<blocktype="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>
此时再清除cache,就可以看到top.links模块的效果了