深入理解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

你将看到如下代码

  1. <?php $_productCollection=$this->getLoadedProductCollection() ?>
  2. <?php if(!$_productCollection->count()): ?>
  3. <p class="note-msg"><?php echo $this->__('There are no products matching the selection.') ?></p>
  4. <?php else: ?>

这里“getLoadedProductCollection”方法可以在这个模板的块对象“Mage_Catalog_Block_Product_List”中找到 File: app/code/core/Mage/Catalog/Block/Product/List.php

  1. ...
  2. public function getLoadedProductCollection()
  3. {
  4. return $this->_getProductCollection();
  5. }
  6. ...

块的“_getProductCollection”方法会实例化模型,并读取数据然后返回给模板。

嵌套块

Magento把视图分离成块和模板的真正强大之处在于“getChildHtml”方法。这个方法可以让你实现在块中嵌套块的功能。顶层的块调用第二层的块,然后是第三层……这就是Magento如何输出HTML的。让我们来看一下单列的顶层模板 File: app/design/frontend/base/default/template/page/1column.phtml

  1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  2. <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php echo $this->getLang() ?>" lang="<?php echo $this->getLang() ?>">
  3. <head>
  4. <?php echo $this->getChildHtml('head') ?>
  5. </head>
  6. <body<?php echo $this->getBodyClass()?' class="'.$this->getBodyClass().'"':'' ?>>
  7. <?php echo $this->getChildHtml('after_body_start') ?>
  8. <div class="wrapper">
  9. <?php echo $this->getChildHtml('global_notices') ?>
  10. <div class="page">
  11. <?php echo $this->getChildHtml('header') ?>
  12. <div class="main-container col1-layout">
  13. <div class="main">
  14. <?php echo $this->getChildHtml('breadcrumbs') ?>
  15. <div class="col-main">
  16. <?php echo $this->getChildHtml('global_messages') ?>
  17. <?php echo $this->getChildHtml('content') ?>
  18. </div>
  19. </div>
  20. </div>
  21. <?php echo $this->getChildHtml('footer') ?>
  22. <?php echo $this->getChildHtml('before_body_end') ?>
  23. </div>
  24. </div>
  25. <?php echo $this->getAbsoluteFooter() ?>
  26. </body>
  27. </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 包含以下内容

  1. <layout version="0.1.0">
  2. <default>
  3. <reference name="root">
  4. <block type="page/html" name="root" output="toHtml" template="../../../../../code/local/Alanstormdotcom/Helloworld/simple_page.phtml" />
  5. </reference>
  6. </default>
  7. </layout>

再创建如下文件 app/code/local/Alanstormdotcom/Helloworld/simple_page.phtml 包含以下内容

  1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  2. "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  3. <html xmlns="http://www.w3.org/1999/xhtml">
  4. <head>
  5. <title>Untitled</title>
  6. <meta name="generator" content="BBEdit 9.2" />
  7. <style type="text/css">
  8. body {
  9. background-color:#f00;
  10. }
  11. </style>
  12. </head>
  13. <body>
  14. </body>
  15. </html>

最后,我们要在执行控制器里面调用布局文件,开始输出HTML。修改执行方法如下

  1. public function indexAction() {
  2. //remove our previous echo
  3. //echo 'Hello Index!';
  4. $this->loadLayout();
  5. $this->renderLayout();
  6. }

清空Magento缓存,访问URL “http://exmaple.com/helloworld/index/index”。你应该看到一个纯红色背景的页面。这个页面的源代码应该和我们创建的文件“simple_page.phtml”一模一样。

究竟是怎么回事呢?

也许你看到这里一头雾水,没关系,我们来慢慢解释。首先你得安装一个 Layout Viewer 模块,这和我们第一章讲的 Config Viewer 模块很相似,都是查看Magento的内部信息。安装完这个模块之后【译者注:你需要参照第一章的内容,为这个模块创建“app/etc/modules/Alanstormdotcom_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

  1. public function getOutput()
  2. {
  3. $out = '';
  4. if (!emptyempty($this->_output)) {
  5. foreach ($this->_output as $callback) {
  6. $out .= $this->getBlock($callback[0])->$callback[1]();
  7. }
  8. }
  9. return $out;
  10. }

从这里我们也可以看出,一个页面的布局文件时可以拥有多个顶层块。

下面我们要讲解块对象是如何被实例化的,这个布局文件时如何被生成的,最后我们将动手做一个例子来实践这一章讲的内容。

实例化块对象

在布局文件中,<block />和<reference />标签有一个“type”属性,这个属性其实是一个URI

  1. <block type="page/html" ...
  2. <block type="page/template_links"

Magento就是通过这个URI是用来查找块对应的类名。这个URI分为两部分,第一部分“page”是用来在全局配置中查找一个基本类名,第二部分“html”或者“template_link”将被添加到基本类名后面生成一个具体的将被实例化的类名。

我们以“page/html”为例。首先Magento在全局配置中找到节点 /global/blocks/page 有以下内容

  1. <page>
  2. <class>
  3. Mage_Page_Block
  4. </class>
  5. </page>

这里我们拿到了一个基本类名“Mage_Page_Block”,然后添加URI的第二部分“html”到基本类名后面,我们就得到最终的块对象的类名 “Mage_Page_Block_Html”。块的类名在Magento中被称为“分组类名”(Grouped Class Names),这些类都用相似的方法被实例化。我们将在以后的章节中详细介绍这个概念。

<block />和<reference />的区别

我们上面提到<block />和<reference />都会实例化块对象,那么它们究竟有什么区别呢? <reference />在布局文件中是用来表示替换一个已经存在的块,举个例子

  1. <block type="page/html" name="root" output="toHtml" template="page/2columns-left.phtml">
  2. <!-- ... sub blocks ... -->
  3. </block>
  4. <!-- ... -->
  5. <reference name="root">
  6. <block type="page/someothertype" name="root" template="path/to/some/other/template" />
  7. <!-- ... sub blocks ... -->
  8. </block>
  9. </reference>

Magento首先创建了一个名叫“root”的块。然后,它有发现了一个引用(reference)的名字也叫“root”,Magento会把原来那个“root”块替换成<reference />标签里面的那个快。

再来看看我们之前创建那个local.xml

  1. <layout version="0.1.0">
  2. <default>
  3. <reference name="root">
  4. <block type="page/html" name="root" output="toHtml" template="../../../../../code/local/Alanstormdotcom/Helloworld/simple_page.phtml" />
  5. </reference>
  6. </default>
  7. </layout>

在这里,块“root”被我们用<reference />替换了,指向了一个不同的模板文件。

布局文件是如何生成的

现在我们对布局文件已经有所了解了,但是这个布局文件是那里来的呢?要回答这个问题,我们得引入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”处理器的名字是frontname “helloworld”加上执行控制器的名字“index”再加上执行方法的名字“index”。这说明执行控制器的每一个执行方法都有一个相应的操作。

我们说过“index”是Magento默认的执行控制器和执行方法的名字,所以以下请求的操作名字也是“helloworld_index_index”。
http://example.com/helloworld/?showLayout=handles

包布局

包布局和我们以前讲过的全局配置有些相似。它是一个巨大的XML文档包含了Magento所有的布局配置。我们可以通过以Layout View模块来查看包布局,请求一下URL
http://example.com/helloworld/index/index?showLayout=package

你可能要等一会儿才能看到输出,因为文件很大。如果你的浏览器在渲染XML的时候卡死了,建议你换成text格式的
http://example.com/helloworld/index/index?showLayout=package&showLayoutFormat=text

假设你选择的是XML格式输出,那么你应该看到一个巨大的XML文件,这就是包布局。这个文件时Magento动态生成的,合并当前主题(theme)下面所有的布局文件。如果你用的是默认安装的话,这些布局文件在以下目录
app/design/frontend/base/default/layout/

其实在全局配置中,有一个<updates />节点下面定义了所有将被装载的布局文件

  1. <layout>
  2. <updates>
  3. <core>
  4. <file>core.xml</file>
  5. </core>
  6. <page>
  7. <file>page.xml</file>
  8. </page>
  9. ...
  10. </updates>
  11. </layout>

当这些文件被装载以后,Magento还会装载最后一个布局文件,local.xml,也就是我们之前新建的那个文件。我们可以通过这个文件来定制Magento的布局。

结合操作和包布局

在包布局文件中,我们可以看到一些熟悉的标签<block />,<reference />等等,但是他们都包含在一下这些标签中

  1. <default />
  2. <catalogsearch_advanced_index />
  3. etc...

这些就是操作标签。对于每个特定的请求来说,针对这个请求的布局文件是由包布局中所有和这个请求相关的操作标签组成的。比如我们上面的例子,和请求相关的操作标签如下

  1. <default />
  2. <STORE_bare_us />
  3. <THEME_frontend_default_default />
  4. <helloworld_index_index />
  5. <customer_logged_out />

所以,针对请求
http://example.com/helloworld/index/index

布局文件就是包布局中上面这些标签的内容组合。在包布局文件中,还有一个标签<update />值得我们注意。我们可以通过这个标签引入另外一个操作标签。比如

  1. <customer_account_index>
  2. <!-- ... -->
  3. <update handle="customer_account"/>
  4. <!-- ... -->
  5. </customer_account_index>

这段代码的意思是,如果一个请求包含了“customer_acount_index”操作,那么这个请求的布局文件也应该包含“customer_account”操作标签下面的<block />和<reference />。

更新我们的例子

好了,理论讲完了,让我们来修改我们的例子,把这一章的内容实践一下。我们重新来看local.xml

  1. <layout version="0.1.0">
  2. <default>
  3. <reference name="root">
  4. <block type="page/html" name="root" output="toHtml" template="../../../../../code/local/Alanstormdotcom/Helloworld/simple_page.phtml" />
  5. </reference>
  6. </default>
  7. </layout>

我们用一个引用(reference)覆盖了名为“root”的块。然后定义了一个新的块,指向了一个不同的模板文件。我们把这个引用放 在<default />操作标签下面,那就说明这个Layout将对所有的请求有效。如果你访问Magento自带的一些页面,你会发现它们要门是空白,要么就是和我 们“hello world”例子的红色背景,但这并不是我们想要的效果。我们来修改一下local.xml,让我们的模板仅对“hello world”的请求有效。

  1. <layout version="0.1.0">
  2. <helloworld_index_index>
  3. <reference name="root">
  4. <block type="page/html" name="root" output="toHtml" template="../../../../../code/local/Alanstormdotcom/Helloworld/simple_page.phtml" />
  5. </reference>
  6. </helloworld_index_index>
  7. </layout>

我们把操作标签换成了“helloworld_index_index”。清空Magento缓存,重新访问Magento的各个页面,你应该发现都恢复了正常,但是针对"hello world"模块的请求页面还是我们自定义的那个。

目前我们只实现了一个“index”执行函数,现在我们来实现“goodbye”执行函数。修改我们的执行控制器代码如下

  1. public function goodbyeAction() {
  2. $this->loadLayout();
  3. $this->renderLayout();
  4. }

但是你访问一下页面的时候你还是会看到Magento的默认布局
http://example.com/helloworld/index/goodbye

那是因为我们没有为这个请求定义布局。我们需要在local.xml中添加“helloworld_index_goodbye”标签。由于 “index”请求和“goodbye”请求我们要套用的布局是一样的,所以我们将用<update>标签来重用已有的配置

  1. <layout version="0.1.0">
  2. <!-- ... -->
  3. <helloworld_index_goodbye>
  4. <update handle="helloworld_index_index" />
  5. </helloworld_index_goodbye>
  6. </layout>

清空Magento缓存,请求以下URL

http://example.com/helloworld/index/index

http://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如下

  1. <helloworld_index_index>
  2. <reference name="root">
  3. <block type="page/html" name="root" template="../../../../../code/local/Alanstormdotcom/Helloworld/simple_page.phtml">
  4. <block type="customer/form_register" name="customer_form_register" template="customer/form/register.phtml"/>
  5. </block>
  6. </reference>
  7. </helloworld_index_index>

我们在“root”块里面嵌套了一个块“customer_form_register”。这个块是Magento本来就有的,包含了一张用户注册表单。 我们把这个块嵌套进来,那么我们在模板文件里面就能用这个块的内容。使用方法如下,修改simple_page.phtml

  1. <body>
  2. <?php echo $this->getChildHtml('customer_form_register'); ?>
  3. </body>

这里“getChildHtml”的参数就是要引入的块的名字,使用起来相当方便。清空Magento缓存,刷新hello world页面,你应该在红色背景上看到用户注册表单。Magento还有一个块,叫做“top.links”,让我们把它也加进来。修改 simple_page.html

  1. <body>
  2. <h1>Links</h1>
  3. <?php echo $this->getChildHtml('top.links'); ?>
  4. <?php echo $this->getChildHtml('customer_form_register'); ?>
  5. </body>

刷新页面,你会发现<h1>Links</h1>显示出来了,但是“top.links”什么都没有显示。那是因为我们并没有把 这个块引入到local.xml,所以Magento找不到这个块。“getChildHtml”的参数一定要是当前页面的布局文件中声明过的块。这样的 话Magento就可以只实例化需要用到的块,节省了资源,我们也可以根据需要为块设置不同的模板文件。

我们修改local.xml文件如下

  1. <helloworld_index_index>
  2. <reference name="root">
  3. <block type="page/html" name="root" template="../../../../../code/local/Alanstormdotcom/Helloworld/simple_page.phtml">
  4. <block type="page/template_links" name="top.links"/>
  5. <block type="customer/form_register" name="customer_form_register" template="customer/form/register.phtml"/>
  6. </block>
  7. </reference>
  8. </helloworld_index_index>

清空Magento缓存,刷新页面,你会看到一排链接显示出来了。【译者注:如果你细心一点的话你会发现“top.links”块没有template属性,那是因为这个块的类中一定定义了默认的模板

  1. protected function _construct()
  2. {
  3. $this->setTemplate('page/template/links.phtml');
  4. }

总结

这一章我们讲解了布局的基础知识。你可能会觉得这个很复杂,但是你也不必过分担心,因为平常使用Magento是不会用到这些知识 的,Magento提供的默认布局应该可以满足大部分需求。对于想要深入研究Magento的开发者来说,理解Magento的布局是至关重要的。布局, 块和模板构成了Magento MVC架构中的View,这也是Magento的特色之一。

from: http://www.zhlmmc.com/?p=594(已无法访问,博主注)

深入理解Magento – 第三章 – 布局,块和模板的更多相关文章

  1. 深入理解Magento – 第四章 – 模型和ORM基础

    深入理解Magento 作者:Alan Storm 翻译:Hailong Zhang 第四章 – 模型和ORM基础 对于任何一个MVC架构,模型(Model)层的实现都是占据了很大一部分.对于Mage ...

  2. 深入理解Magento – 第七章 – 自定义Magento系统配置

    Magento拥有十分强大的后台管理系统.作为一名开发人员,这套后台管理系统可以让你的用户简单直接的配置Magento系统或者你创建的模块.和Magento的其他功能一样,你第一次使用这套管理系统的时 ...

  3. 深入理解Magento - 第六章 - 高级Magento模型

    我们讲过Magento有两种模型,简单模型和EAV(Entity Attribute Value)模型.上一章我们讲过所有的Magento模型都是继承自Mage_Core_Model_Abstract ...

  4. 深入理解Magento-第九章-修改、扩展、重写Magento代码

    (博主提示:本章应该不是原作者的第九章,仅作补充和参考) 作为一个开发者的你,肯定要修改Magento代码去适应你的业务需求,但是在很多时候我们不希望修改Magento的核心代码,这里有很多原因,例如 ...

  5. 深入理解Magento - 第五章 Magento资源配置

    对于任何一个更新频繁的项目来说,保持开发环境和生产环境的数据库同步是件很头疼的事情.Magento提供了一套系统,用版本化的资源迁移脚本来解决这个问题. 上一章,我们为 Helloworld Blog ...

  6. 深入理解计算机系统 第三章 程序的机器级表示 part1

    如题所示,这一章讲解了程序在机器中是怎样表示的,主要讲汇编语言与机器语言. 学习什么,为什么学,以及学了之后有什么用 我们不用学习如何创建机器级的代码,但是我们要能够阅读和理解机器级的代码. 虽然现代 ...

  7. 深入理解计算机系统 第三章 程序的机器级表示 Part2 第二遍

    第一遍对应笔记链接 https://www.cnblogs.com/stone94/p/9943779.html 本章汇编代码中常出现的几个指令及其含义 1.push 操作数的个数:1 将操作数(一般 ...

  8. 深入理解计算机系统 第三章 程序的机器级表示 Part1 第二遍

    第一遍对应笔记链接 https://www.cnblogs.com/stone94/p/9905345.html 机器级代码 计算机系统使用了多种不同形式的抽象,利用更简单的抽象模型来隐藏实现的细节. ...

  9. 深入理解计算机系统 第三章 程序的机器级表示 part2

    这周由于时间和精力有限,只读一小节:3.4.4  压入和弹出栈数据 栈是一种特殊的数据结构,遵循“后进先出”的原则,可以用数组实现,总是从数组的一端插入和删除元素,这一端被称为栈顶. 栈有两个常用指令 ...

随机推荐

  1. Java Annotation 总结

    Annotation 被称为注解,在Java开发中是相当常见的,通过注解,我们可以简化代码提高开发效率.例如Override Annotation,这个应该算是在开发过程中使用最多的注解了.下面这个例 ...

  2. 分享一个Jquery 分页插件 Jquery Pagination

    分页插件来说,我觉得适用就行,尽量简单然后能够根据不同的应用场景能够换肤.展现形式等. 对于初学者想写分页插件的同学,也可以看下源码,代码也挺简单明了的,也助于自己写个小插件. 不过我比较懒,一般直接 ...

  3. Zend Guard Run-time support missing问题的解决

    Zend Guard不仅可以实现对PHP应用的脚本进行加密保护和对PHP应用的产品进行商业许可证管理,还可以为许多软件生产商.IT服务提供商提供完善的加密和安全的产品发布系统. 虽然现在可以成功加密p ...

  4. C# 使用NPlot绘图

    首先要将下载的NPlot.dll加到工具箱里,拖一个控件到窗体上,声明using NPlot. 一.入门 1. 对所绘的图进行打印与保存 private void print() { myPlot.P ...

  5. java_method_Log输出日志的方法

    package cn.com.qmhd.tools; import org.apache.log4j.Logger; import org.apache.log4j.PropertyConfigura ...

  6. 测试linux和window下 jdk最大能使用多大内存

    在命令行下用 java -XmxXXXXM -version 命令来进行测试,然后逐渐的增大XXXX的值,如果执行正常就表示指定的内存大小可用,否则会打印错误信息. 发现在linux先 最多用java ...

  7. 深入了解回调函数Java

    打回来.我第一次看到Java编程思想,后来Observer模式也适用于一个回调函数的想法.但是,一直没有重视,在处于劣势的最终面试,越来越明白为什么那么多人说Java编程思想,这本书应该是一遍又一遍, ...

  8. Java面向对象 IO (四)

     Java面向对象  IO  (四) 知识概要:                 (1)打印流 (2)序列流 SequenceInputStream (3)ObjectInputStream与Ob ...

  9. Entity Framework DBContext 增删改查深度解析

    Entity Framework DBContext 增删改查深度解析 有一段时间没有更新博客了,赶上今天外面下雨,而且没人约球,打算把最近对Entity Framework DBContext使用的 ...

  10. linux 设备驱动分类

    1. 字符设备 :在I/O传输过程中以字符为单位. 2. 块设备: 在传输过程中以块为单位,相关概念有 缓冲,I/O调度,请求队列.主要体现出与字符设备在操作硬件的接口实现方式上是不一样的. 3. 网 ...