汉字拆分
<?php $str = '我住在3号楼A座'; $list = array(); $start = 0; $lengh = mb_strlen($str,'utf8');//这里可以是指定的长度 while (count($list)<$lengh) { $list[] = mb_substr($str, $start,1,'utf8');//也可以用$list .= $start++; } print_r($list); ?>
<?php $str = '我住在3号楼A座'; $list = array(); $start = 0; $lengh = mb_strlen($str,'utf8');//这里可以是指定的长度 while (count($list)<$lengh) { $list[] = mb_substr($str, $start,1,'utf8');//也可以用$list .= $start++; } print_r($list); ?>
loadByIncrementId(100000001); // 100000001为订单编号
// 获取订单状态
$status = $order->getStatus();
$state = $order->getState();
echo $status;
echo "\r\n";
echo $state;
// 设置订单状态 $order->setStatus(Mage_Sales_Model_Order::STATE_PROCESSING);
$order->save();
Magento订单 有两个状态变量:state和status,这让人困惑,只有测试下了,于是下了个单,然后在Magneto后台处理订单,得出下面的Magento订单状态值。
1. 新订单
state : new
status : pending
2. 配送后
state : processing
status : processing
3. 收款后
state : processing
status : processing
4. 订单完成
state : complete
status : complete
5. 订单取消
state : canceled
status : canceled
6. 订单关闭
state : closed
status : closed
7. 订单挂起
state : holded
status : holded
Magento订单状态 是定义在Magento代码文件app\code\core\Mage\Sales\Model\Order.php中定义了订单的状态常量:
/** * Order model * * Supported events: * sales_order_load_after * sales_order_save_before * sales_order_save_after * sales_order_delete_before * sales_order_delete_after * * @author Magento Core Team <[email protected]> */ class Mage_Sales_Model_Order extends Mage_Sales_Model_Abstract {
/** * Order states */ const STATE_NEW = 'new'; const STATE_PENDING_PAYMENT = 'pending_payment'; const STATE_PROCESSING = 'processing'; const STATE_COMPLETE = 'complete'; const STATE_CLOSED = 'closed'; const STATE_CANCELED = 'canceled'; const STATE_HOLDED = 'holded'; const STATE_PAYMENT_REVIEW = 'payment_review'; // added magento 1.4
/** * Order flags */ const ACTION_FLAG_CANCEL = 'cancel'; const ACTION_FLAG_HOLD = 'hold'; const ACTION_FLAG_UNHOLD = 'unhold'; const ACTION_FLAG_EDIT = 'edit'; const ACTION_FLAG_CREDITMEMO= 'creditmemo'; const ACTION_FLAG_INVOICE = 'invoice'; const ACTION_FLAG_REORDER = 'reorder'; const ACTION_FLAG_SHIP = 'ship'; const ACTION_FLAG_COMMENT = 'comment';
// ... } </[email protected]>
其中,pending_payment, payment_review 是支付(Paypal, Amazon Pay)过程中引入的订单状态。
$categories = Mage::getModel('catalog/category')->getCollection() ->addAttributeToSelect('id') ->addAttributeToSelect('name') ->addAttributeToSelect('url_key') ->addAttributeToSelect('url') ->addAttributeToSelect('is_active');
foreach ($categories as $category) { if ($category->getIsActive()) { // Only pull Active categories $entity_id = $category->getId(); $name = $category->getName(); $url_key = $category->getUrlKey(); $url_path = $category->getUrl(); } }
<?php // load(level-number)?> <?php $categoryIds = Mage::getModel('catalog/category')->load(3)->getChildren() ?> <?php $categoryIds = explode(',', $categoryIds); ?> <?php $count = count($categoryIds); $i=0; ?> <?php foreach ($categoryIds as $categoryId): ?> <?php $category = Mage::getModel("catalog/category")->load($categoryId) ?> <li<?php if (++$i == $count): ?> class="last"<?php endif ?>><a href="<?php echo $category->getUrl() ?>" title="<?php echo $this->stripTags($category->getName()) ?>"><?php echo $this->stripTags($category->getName()) ?></a></li> <?php endforeach ?> $_categoryIds = $_product->getCategoryIds(); foreach ($_categoryIds as $_categoryId) { $_category = Mage::getModel('catalog/category')->load($_categoryId); $_category_name = $_category->getName(); $_category_url = $_category->getUrlPath(); break; }
模型层的实现是任何一个MVC框架的重要组成部分。它用来实现应用程序的数据,并且大部分应用程序在没有数据的情况下都是一堆废柴。相对于其他PHP MVC框架,Magento模型在系统中扮演了一个更为重要的角色,因为它包含了通常应用于控制器和助手方法中的业务逻辑。
如果说MVC架构的定义有些模糊,那么模型的定义就更为模糊了。早在MVC模式被PHP开发者普遍接受之前,数据的交互通常是使用原始的SQL语句或者SQL抽象进行。开发者必须很多数据库查询语句,而不用考虑在模型化哪个对象。
此处省略三段关于传统PHP MVC模型层以及ORM的探讨,直接进入正题。
毫无疑问Magento实现了ORM模式。尽管Zend Framework的SQL抽象层能够正常使用,大部分的数据交互依然是通过内置的Magento模型,以及用户自己构建的模型完成。Magento系统拥有一个高度灵活,高度抽象的模型层。
绝大部分Magento模型可以被分为两类。基础的,ActiveRecord,或者说是“一张表,一个对象”的模型;另外一种是Entity Attribute Value(EAV)模型。每个模型都包含一个模型收集(Model Collection)。收集(Collections)是用来同时操作多个Magento模型实例的对象。Magento团队通过实现PHP的IteratorAggregate标准库接口和Countable,从而允许每个模型类型拥有自己的收集类型。如果你对PHP标准库不是很熟悉,可以将模型收集想象成拥有方法可以使用的数组。
Magento模型不包含任何连接数据库的代码。取而代之,每个模型使用两个modelResource类(一个读取,一个写入),它们通过read and write adapter objects与数据库进行交互。通过解耦模型与数据库交互代码,理论上可以通过构建新的资源类来满足任意不同的数据库平台,并且保持模型的完整性。
下面我们开始创建一个基础的Magento模型,我们以简单的weblog博客为例,构建一个模型,总的分为以下几步。
通过之前几章的学习,创建一个新的空模块应该没有问题啦,这里我们跳过这些细节,假设你已经创建了一个名为Weblog的空模块。完成之后,我们为Index控制器设置路由规则。这里依然假设我们的Package命名为Magentotutorial。
在Magentotutorial/Weblog/etc/config.xml文件中,加入一下路由规则,
01 02 03 04 05 06 07 08 09 10 11 |
< frontend > < routers > < weblog > < use >standard</ use > < args > < module >Magentotutorial_Weblog</ module > < frontName >weblog</ frontName > </ args > </ weblog > </ routers > </ frontend > |
然后添加以下代码到Index控制器中,该文件位于Magentotutorial/Weblog/controller/IndexController.php。
01 02 03 04 05 |
class Magentotutorial_Weblog_IndexController extends Mage_Core_Controller_Front_Action { public function testModelAction() { echo 'Setup!' ; } } |
清空Magento缓存,根据你的安装路径,访问类似下面的地址,
http://example.com/weblog/index/testModel
Magento系统能够自动创建和更改数据库模式,这里为了演示,我们先手动为模型创建一个表。使用命令行或你最喜欢的MySQL GUI工具,创建下表,
01 02 03 04 05 06 07 08 |
CREATE TABLE `blog_posts` ( `blogpost_id` int (11) NOT NULL auto_increment, `title` text, `post` text, ` date ` datetime default NULL , ` timestamp ` timestamp NOT NULL default CURRENT_TIMESTAMP , PRIMARY KEY (`blogpost_id`) ) |
然后填充一些数据到表中,
01 |
INSERT INTO `blog_posts` VALUES (1, 'My New Title' , 'This is a blog post' , '2010-07-01 00:00:00' , '2010-07-02 23:12:30' ); |
创建Weblog的模型及其配置文件需要以下五步完成,
在Magento中实例化一个模型,可以使用如下语法,
01 |
$model = Mage::getModel( 'weblog/blogpost' ); |
getmodel()方法里的URI的第一部分叫做模型组名(Model Group Name)。考虑到Magento为类使用__autoload方法,所以该模型组名必须是模块的小写形式。该URI的第二部分是你的模型名的小写形式。
接着,我们开始添加模型的配置代码到模块的config.xml文件中。
01 02 03 04 05 06 07 08 09 10 11 12 13 14 |
< global > <!-- ... --> < models > < weblog > < class >Magentotutorial_Weblog_Model</ class > <!-- need to create our own resource, cant just use core_mysql4 --> < resourceModel >weblog_mysql4</ resourceModel > </ weblog > </ models > <!-- ... --> </ global > |
最外层的<weblog />标签是模型组名,应该匹配模块名。<class />中的值是weblog组中所有的模型都拥有的BASE名。<resourceModel />标签指定weblog组中的模型应该使用哪种模型资源,这里我们先记得它是由模型组名加“mysql4”。
现在让我们清理下Magento缓存,尝试下实例化这个blogpost模型。在testModelAction()中,添加如下代码。
01 02 03 04 |
public function testModelAction() { $blogpost = Mage::getModel( 'weblog/blogpost' ); echo get_class( $blogpost ); } |
刷新页面之后,你会看到系统抛出了异常,大概如下,
include(Magentotutorial/Weblog/Model/Blogpost.php) [function.include]: failed to open stream: No such file or directory
由于在上面那段代码中,试图引用‘weblog/blogpost’模型,Magento会实例化下面这个类,
Magentotutorial_Weblog_Model_Blogpost
但是此时我们还没有创建这个文件。所以系统会抛出上面的异常。下面我们来创建该类,文件路径位于,
File: app/code/local/Magentotutorial/Weblog/Model/Blogpost.php
01 02 03 04 05 06 07 |
class Magentotutorial_Weblog_Model_Blogpost extends Mage_Core_Model_Abstract { protected function _construct() { $this ->_init( 'weblog/blogpost' ); } } |
刷新页面之后,异常就被该类名所取代了。所有的基础模型都必须扩展Mage_Core_Model_Abstract类。这个抽象类强制你必须实现一个名为_construct的方法。此方法会调用该类的_init方法,并需要传递在getModel()方法中的参数。
到此为止,我们已经成功设置了自定义的模型。接着,我们需要设置它的模型资源。模型资源包含与数据库交互的代码。在上一小节中,我们在配置文件中添加了如下代码,
01 |
< resourceModel >weblog_mysql4</ resourceModel > |
在<resourceModel />中的值会实例化一个模型资源类。尽管你从不需要手动调用它,当任何在weblog组中的模型需要与数据库交互时,Magento会调用以下方法获取模型资源,
01 |
Mage::getResourceModel( 'weblog/blogpost' ); |
重申一次,weblog是模型组名,blogpost是模型名。Mage::getResourceModel方法使用weblog/blogpost URI来检查全局配置文件,并获取<resourceModel>中的值(在这里,是weblog_mysql4)。然后,下列URI地址的模型类将会被实例化。
weblog_mysql4/blogpost
资源模型的配置与模型的配置在XML配置文件中的相同节点呢,下面我们在<models>节点中添加下列代码,
01 02 03 04 05 06 07 08 09 |
< global > <!-- ... --> < models > <!-- ... --> < weblog_mysql4 > < class >Magentotutorial_Weblog_Model_Mysql4</ class > </ weblog_mysql4 > </ models > </ global > |
这里设置的<weblog_mysql4 />标签,就是刚刚在<resourceModel />标签中设置的值。<class />节点中的值是使用的资源模型的基础命名,它的命名方式大概如下
Packagename_Modulename_Model_Mysql4
现在,我们成功配置了资源模型,来试着从模型数据中读取一些信息吧。稍稍添加一些代码到testModelAction()方法中。
01 02 03 04 05 06 07 08 |
public function testModelAction() { $params = $this ->getRequest()->getParams(); $blogpost = Mage::getModel( 'weblog/blogpost' ); echo ( "Loading the blogpost with an ID of " . $params [ 'id' ]); $blogpost ->load( $params [ 'id' ]); $data = $blogpost ->getData(); var_dump( $data ); } |
清空Magento缓存,在浏览器中打开如下地址,
http://example.com/weblog/index/testModel/id/1
好吧,又一次看到系统抛出了异常,大概如下,
Warning: include(Magentotutorial/Weblog/Model/Mysql4/Blogpost.php) [function.include]: failed to open stream: No such file ….
上面我们提到过,当与数据库交互时,会实例化资源模型类,这里系统提示我们需要为该模型添加一个模型资源类。(译者注:本文提到过,Magento的模型本身与数据库连接及交互是相互独立的,所以在模型没有与数据库交互之前,例如在本篇第一次使用getModel()方法时,系统不会抛出关于实例化模型以外的异常。)每个模型都有模型资源类,添加该类到下列路径的文件中,
File: app/code/local/Magentotutorial/Weblog/Model/Mysql4/Blogpost.php
01 02 03 04 05 06 |
class Magentotutorial_Weblog_Model_Mysql4_Blogpost extends Mage_Core_Model_Mysql4_Abstract{ protected function _construct() { $this ->_init( 'weblog/blogpost' , 'blogpost_id' ); } } |
可以看到,_init方法的第一个参数依旧是模型组名/模型名。参数二是数据库字段,可以是任意唯一字段,大多数情况下,参数二可以指定为主键。清空缓存,刷新页面,页面中会显示如下内容,
Loading the blogpost with an ID of 1
array
empty
没有异常?可是也没有正常读取到数据!接着该做些什么呢?每一个模型组都拥有一个读取适配器和写入适配器。Magento允许模型使用默认的适配器,也可以使用开发者自己开发的适配器。无论使用哪一种,我们需要告诉Magento系统关于适配器的配置。这里,我们在配置文件中添加一个新的tag节点,<resources />到<global />节点中。
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 |
< global > <!-- ... --> < resources > < weblog_write > < connection > < use >core_write</ use > </ connection > </ weblog_write > < weblog_read > < connection > < use >core_read</ use > </ connection > </ weblog_read > </ resources > </ global > |
这里我们在<resources />中添加了两个子节点。一个用来写入,另一个用来读取。标签命名(<weblog_write />和<weblog_read />)根据上面定义的模型组名。完成改配置文件之后,清空Magento缓存,再次刷新页面,然后…
Can’t retrieve entity config: weblog/blogpost
又一次出现异常了!一起理清下思路,在使用模型URI weblog/blogpost时,Magento系统被告知我们想使用模型组weblog,以及blogpost实体。在扩展Mage_Core_Model_Mysql4_Abstract的简单模型中,实体相对应一张表。这里,该表即我们上面创建的blog_post表,添加该实体到配置文件中。
01 02 03 04 05 06 07 08 09 10 11 |
< models > <!-- ... ---> < weblog_mysql4 > < class >Magentotutorial_Weblog_Model_Mysql4</ class > < entities > < blogpost > < table >blog_posts</ table > </ blogpost > </ entities > </ weblog_mysql4 > </ models > |
在配置文件中的resource模型节点中,添加新的<entities />节点。现在,在配置文件中终于出现了以刚才创建的表名命名的节点,从而为该模型指定相关的数据库表。
清空Magento缓存,刷新页面,OK…
Loading the blogpost with an ID of 2
Loading the blogpost with an ID of 1
array
‘blogpost_id’ => string ’1′ (length=1)
‘title’ => string ‘My New Title’ (length=12)
‘post’ => string ‘This is a blog post’ (length=19)
‘date’ => string ’2009-07-01 00:00:00′ (length=19)
‘timestamp’ => string ’2009-07-02 16:12:30′ (length=19)
好啦!经过这么长一个过程我们终于成功从数据库中读取到了数据,更为重要的是,我们完成了一个崭新的Magento模型的配置!
Magento模型都继承自Varien_Object类。该类是Magento系统核心库中的一部分,而非Magento核心模块。可以在下列路径找到该对象。
lib/Varien/Object.php
Magento模型将数据保存在一个protected的_data属性中。Varien_Object类提供给我们很多方法,可以使用这些方法读取这些数据。你已经使用过了getData()方法,该方法返回一个包含字段/值的数组。你也可以通过传递字段名作为该方法的参数来获取相应字段的值
01 02 |
$model ->getData(); $model ->getData( 'title' ); |
还有一个getOrigData方法,which will return the Model data as it was when the object was initially populated, (working with the protected _origData method).这段就不翻译了。
01 02 |
$model ->getOrigData(); $model ->getOrigData( 'title' ); |
Varien_Object类通过PHP的魔术方法__call实现了一些特殊的方法。你可以通过get,set,unset以及has加上驼峰命名的字段名的方式,获取、设置、unset及查看任意存在的字段值。
01 02 03 04 |
$model ->getBlogpostId(); $model ->setBlogpostId(25); $model ->unsetBlogpostId(); if ( $model ->hasBlogpostId()){...} |
正因为如此,你可能会以小写字母及下划线来命名数据库字段。不过,最近版本的Magento已经舍弃了这种语法,转而实现PHP的数组连接(ArrayAccess)接口。
01 02 03 |
$id = $model ->[ 'blogpost_id' ]; $model ->[ 'blogpost_id' ] = 25; //etc... |
That said, you’re likely to see both techniques used throughout the Magento code base, as well as third party extensions.这段意思应该是说你可以在Magento或第三方扩展中看到上面两种语法格式。
Magento模型通过load(),sava(),delete()方法,提供基础的Create,Read,Update和Delete功能。在上面的控制器方法中,我们已经使用了load()方法。当传递一个参数到load()方法中,该方法会返回与该参数相对应的id字段(在模型资源中设置)的一条记录。
01 |
$blogpost ->load(1); |
save()方法允许你插入新数据到模型中,或更新已经存在的数据。添加如下代码到控制器中。
01 02 03 04 05 06 07 |
public function createNewPostAction() { $blogpost = Mage::getModel( 'weblog/blogpost' ); $blogpost ->setTitle( 'Code Post!' ); $blogpost ->setPost( 'This post was created from code!' ); $blogpost ->save(); echo 'post created' ; } |
然后在浏览器中访问以下地址,
http://example.com/weblog/index/createNewPost
这时你会看到数据库表中新增了一条数据,然后在控制器中加入编辑功能。
01 02 03 04 05 06 07 |
public function editFirstPostAction() { $blogpost = Mage::getModel( 'weblog/blogpost' ); $blogpost ->load(1); $blogpost ->setTitle( "The First post!" ); $blogpost ->save(); echo 'post edited' ; } |
最后,加入下列代码,实现删除功能。
01 02 03 04 05 06 |
public function deleteFirstPostAction() { $blogpost = Mage::getModel( 'weblog/blogpost' ); $blogpost ->load(1); $blogpost -> delete (); echo 'post removed' ; } |
对于单独一个模型的操作固然很有用,但是多数时候,我们会同时操作多个模型。比返回多个模型的一个多维嵌套数组更好的是,在Magento中,每个模型类型都有一个唯一的收集对象与其关联。这些对象实现了PHP IteratorAggregate和Countable接口,这意味着它们可以被传递到count函数,并使用for each结构循环出数据。
我们将在第八章具体介绍Magento的收集机制,现在我们先简要介绍下它的设置和使用。添加如下代码到控制器中,然后再浏览器中访问该地址。
01 02 03 04 05 06 07 |
public function showAllBlogPostsAction() { $posts = Mage::getModel( 'weblog/blogpost' )->getCollection(); foreach ( $posts as $blog_post ){ echo '<h3>' . $blog_post ->getTitle(). '</h3>' ; echo nl2br ( $blog_post ->getPost()); } } |
访问如下地址,
http://example.com/weblog/index/showAllBlogPosts
然后,是的,系统再一次抛出异常。
Warning: include(Magentotutorial/Weblog/Model/Mysql4/Blogpost/Collection.php) [function.include]: failed to open stream
看下上面的PHP代码,你就应该对系统抛出异常不会感到太惊讶了吧?我们需要添加一个类来定义Blogpost的模型收集。每个模型资源拥有一个_resourceCollectionName保护属性,它包含了用来识别收集的URI。
01 |
protected '_resourceCollectionName' => string 'weblog/blogpost_collection' |
默认的,该URI也用来识别模型资源,以字符串”_collection”结尾。Magento将收集归为模型资源的一部分,所以该URI转换为类名之后如下,
Magentotutorial_Weblog_Model_Mysql4_Blogpost_Collection
添加下面的模型收集类到如下路径,
File: app/code/local/Magentotutorial/Weblog/Model/Mysql4/Blogpost/Collection.php
01 02 03 04 05 06 |
class Magentotutorial_Weblog_Model_Mysql4_Blogpost_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract { protected function _construct() { $this ->_init( 'weblog/blogpost' ); } } |
和其他类一样,我们需要使用该模型的URI(weblog/blogpsot)来_init模型收集。最后,在浏览器中访问模型收集的地址,就能成功返回文章的数据信息了。
恭喜你,到这里,说明你已经完成了Magento模型的配置。在后面的教程中,我们会更深入的讲解Magento的高级EAV模型。
还要提到一点,上面文章中,我们说到所有的Magento模型都继承自Mage_Core_Model_Abstract类。这并不是100%正确。因为有些模型直接继承自Varien_Object。当然,这都不会影响到任何你创建的模型,说明这些只是为了让开发者能够更好的理解Magento的代码。
我们接着研究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
你将看到如下代码
这里“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
我们可以看到这个模板里面很多地调用了“$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
包含以下内容
再创建如下文件
app/design/frontend/default/default/template/helloworld/simple_page.phtml
包含以下内容
最后,我们要在执行控制器里面调用布局文件,开始输出HTML。修改执行方法如下
清空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类的代码应该比较容易理解这里的逻辑
从这里我们也可以看出,一个页面的布局文件可以拥有多个顶层块。】
下面我们要讲解块对象是如何被实例化的,这个布局文件时如何被生成的,最后我们将动手做一个例子来实践这一章讲的内容。
实例化块对象
在布局文件中,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),这些类都用相似的方法被实例化。我们将在以后的章节中详细介绍这个概念。
block和reference的区别
我们上面提到block和reference都会实例化块对象,那么它们究竟有什么区别呢? reference在布局文件中是用来表示替换一个已经存在的块,举个例子
<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 ... -->
</reference>
Magento首先创建了一个名叫“root”的块。然后,它有发现了一个引用(reference)的名字也叫“root”,Magento会把原来那个“root”块替换成reference标签里面的那个快。
再来看看我们之前创建那个local.xml
在这里,块“root”被我们用reference替换了,指向了一个不同的模板文件。
布局文件是如何生成的
现在我们对布局文件已经有所了解了,但是这个布局文件是那里来的呢?要回答这个问题,我们得引入Magento中的另外两个概念,操作(Handle)和包布局(Package Layout)。
操作
Magento会为每一个页面请求生成几个不同的操作。我们的Layout View模块可以显示这些处理器
http://example.com/helloworld/index/index?showLayout=handles
你应该看到类似如下列表的列表(和你的配置有关)
Handles For This Request
1. default
2. STORE_default
3. THEME_frontend_default_gap
4. helloworld_index_index
5. 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节点下面定义了所有将被装载的布局文件
<layout>
<updates>
<core>
<file>core.xml</file>
</core>
<page>
<file>page.xml</file>
</page>
...
</updates>
</layout>
当这些文件被装载以后,Magento还会装载最后一个布局文件,helloworld.xml,也就是我们之前新建的那个文件。我们可以通过这个文件来定制Magento的布局。
结合操作和包布局
在包布局文件中,我们可以看到一些熟悉的标签block,reference等等,但是他们都包含在一下这些标签中
<default />
<catalogsearch_advanced_index />
etc...
这些就是操作标签。对于每个特定的请求来说,针对这个请求的布局文件是由包布局中所有和这个请求相关的操作标签组成的。比如我们上面的例子,和请求相关的操作标签如下
<default />
<store_bare_us />
<theme_frontend_default_default />
<helloworld_index_index />
<customer_logged_out />
所以,针对请求
http://example.com/helloworld/index/index
布局文件就是包布局中上面这些标签的内容组合。在包布局文件中,还有一个标签值得我们注意。我们可以通过这个标签引入另外一个操作标签。比如
<customer_account_index>
<!-- ... -->
<update handle="customer_account"/>
!-- ... -->
</customer_account_index>
这段代码的意思是,如果一个请求包含了“customer_acount_index”操作,那么这个请求的布局文件也应该包含“customer_account”操作标签下面的block和reference。
更新我们的例子
好了,理论讲完了,让我们来修改我们的例子,把这一章的内容实践一下。我们重新来看local.xml
<layout version="0.1.0">
<default>
<reference name="root">
<block type="page/html" name="root" output="toHtml" template="helloworld/simple_page.phtml" />
</reference>
</default>
</layout>
我们用一个引用(reference)覆盖了名为“root”的块。然后定义了一个新的块,指向了一个不同的模板文件。我们把这个引用放在default 操作标签下面,那就说明这个Layout将对所有的请求有效。如果你访问Magento自带的一些页面,你会发现它们要么是空白,要么就是和我们 “hello world”例子的红色背景,但这并不是我们想要的效果。我们来修改一下local.xml,让我们的模板仅对“helloworld”的请求有效。
<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>
我们把操作标签换成了“helloworld_index_index”。清空Magento缓存,重新访问Magento的各个页面,你应该发现都恢复了正常,但是针对”hello world”模块的请求页面还是我们自定义的那个。
目前我们只实现了一个“index”执行函数,现在我们来实现“goodbye”执行函数。修改我们的执行控制器代码如下
public function goodbyeAction() {
$this->loadLayout();
$this->renderLayout();
}
但是你访问一下页面的时候你还是会看到Magento的默认布局
http://example.com/helloworld/index/goodbye
那是因为我们没有为这个请求定义布局。我们需要在local.xml中添加“helloworld_index_goodbye”标签。由于“index”请求和“goodbye”请求我们要套用的布局是一样的,所以我们将用标签来重用已有的配置
<layout version="0.1.0">
<!-- ... -->
<helloworld_index_goodbye>
<update handle="helloworld_index_index" />
</helloworld_index_goodbye>
</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="helloworld/simple_page.phtml"
模板文件的查找路径是当前主题(theme)的根目录,Magento默认设置时这里
app/design/frontend/base/default
为页面加入内容
到目前为止,我们的页面都比较无聊,啥也没有。我们来为页面加点有意义的内容。修改local.xml如下
<helloworld_index_index>
<reference name="root">
<block type="page/html" name="root" template="helloworld/simple_page.phtml">
<block type="customer/form_register" name="customer_form_register" template="customer/form/register.phtml"/>
</block>
</reference>
</helloworld_index_index>
我们在“root”块里面嵌套了一个块“customer_form_register”。这个块是Magento本来就有的,包含了一张用户注册表单。 我们把这个块嵌套进来,那么我们在模板文件里面就能用这个块的内容。使用方法如下,修改simple_page.phtml
<body>
<?php echo $this->getChildHtml('customer_form_register'); ?>
</body>
这里“getChildHtml”的参数就是要引入的块的名字,使用起来相当方便。清空Magento缓存,刷新hello world页面,你应该在红色背景上看到用户注册表单。Magento还有一个块,叫做“top.links”,让我们把它也加进来。修改 simple_page.html
<body>
<h1>Links</h1>
< ?php echo $this->getChildHtml('top.links'); ?>
< ?php echo $this->getChildHtml('customer_form_register'); ?>
</body>
刷新页面,你会发现Links显示出来了,但是“top.links”什么都没有显示。那是因为我们并没有把这个块引入到local.xml,所以 Magento找不到这个块。“getChildHtml”的参数一定要是当前页面的布局文件中声明过的块。这样的话Magento就可以只实例化需要用 到的块,节省了资源,我们也可以根据需要为块设置不同的模板文件。
我们修改helloworld.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">
<block type="customer/form_register" name="customer_form_register" template="customer/form/register.phtml"/>
<block type="page/template_links" name="top.links"/>
</block>
</reference>
</helloworld_index_index>
</layout>
清空Magento缓存,刷新页面,你会看到一排链接显示出来了。【注:如果你细心一点的话你会发现“top.links”块没有template属性,那是因为这个块的类中一定定义了默认的模板
protected function _construct()
{
$this->setTemplate('page/template/links.phtml');
}
总结
这一章我们讲解了布局的基础知识。你可能会觉得这个很复杂,但是你也不必过分担心,因为平常使用Magento是不会用到这些知识的,Magento提供 的默认布局应该可以满足大部分需求。对于想要深入研究Magento的开发者来说,理解Magento的布局是至关重要的。布局,块和模板构成了 Magento MVC架构中的View,这也是Magento的特色之一。