我们接着研究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 class="note-msg"><?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.phtm
<?php
/**
* Template for Mage_Page_Block_Html
*/
?>
<!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>
我们可以看到这个模板里面很多地调用了“$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
包含以下内容
<?xml version ="1.0" encoding ="UTF-8" ?>
<layout version ="0.1.0" >
<helloworld_index_index>
<reference name ="root" >
<block type ="page/html" name ="root" output ="toHtml" template ="helloworld/simple_page.phtml" />
</reference>
</helloworld_index_index>
</layout>
再创建如下文件
app/design/frontend/default/default/template/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>Untitled</title>
<style type="text/css">
body {
background-color:#f00;
}
</style>
</head>
<body>
<h4>Links</h4>
<?php
echo
$this
->
getChildHtml
(
'top.links'
)
;
?>
<?php
echo
$this
->
getChildHtml
(
'customer_form_register'
)
;
?>
</body>
</html>
最后,我们要在执行控制器里面调用布局文件,开始输出HTML。修改执行方法如下
public
function
indexAction(
)
{
//remove our previous echo
//echo 'Hello Index!';
$this
->
loadLayout
(
)
;
$this
->
renderLayout
(
)
;
}
清空Magento缓存,访问URL “http://exmaple.com/helloworld/index/index”。你应该看到一个纯红色背景的页面。这个页面的源代码应该和我们创建的文件“simple_page.phtml”一模一样。
究竟是怎么回事呢?
也许你看到这里一头雾水,没关系,我们来慢慢解释。首先你得安装一个 Layout Viewer 模块,这和我们第一章讲的 Config Viewer 模块很相似,都是查看Magento的内部信息。安装完这个模块之后【注:你需要参照第一章的内容,为这个模块创建“app/etc/modules/App_Layoutviewer.xml”】,打开如下URL
http://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),这些类都用相似的方法被实例化。我们将在以后的章节中详细介绍这个概念。