Magento模型与数据保存

这创建模型之前,我们需要明确地定义将要创建的模型的类型和个数,就我们的礼品登记插件这个例子来说,看起来需要两种不同的模型:

  • Registry Model:这个模型用于存储礼品登记信息,如礼品登记类型、地址和收件人信息
  • Registry Item:这个模型用于存储每个登记的礼品的信息,如需要的数量、购买的数量和产品ID等

尽管这种方法没错,但并没有满足这个插件的所有需求,如果将所有信息存储在一张表中,只要添加更多的登记类型就需要修改代码。所以本例中最好将数据分别存放在多个表中:

  • Registry Entity:此表用于存储礼品登记信息以及事件信息
  • Registry Type:通过将礼品登记类型放到一个单独的表中,可以添加或删除事件类型
  • Registry Item:本表用于存储每个登记的礼品的信息,如需要的数量、购买的数量和产品ID等

确定好了数据结构,就可以开始创建获取各操作数据的相关模型了。

创建模型

我们首先来创建礼品登记的类型模型,用于管理登记类型,如婚礼、生日、小孩满月等礼品类型。要创建这一模型,应按照如下步骤来完成:

  1. 在我们的模块目录中找到Model文件夹
  2. 创建一个名为Type.php的文件,并将如下内容拷贝到文件中(app/code/local/Mdg/Giftregistry/Model/Type.php)
<?php

class Mdg_Giftregistry_Model_Type extends Mage_Core_Model_Abstract

{

public function __construct()

{

$this->_init('mdg_giftregistry/type');

parent::__construct();

}

}

我们还需要创建一个资源类,每个Magento的Data模型都有自己的资源类。另外不管是简单数据模型还是EAV模型这类直接处理数据的模型都要包含一个资源类,这需要按如下步骤来完成:

  1. 在我们的模块目录中找到Model文件夹
  2. 在Model下创建一个名为Mysql4的文件夹
  3. 创建一个名为Type.php的文件并将下面的代码拷贝到该文件中(app/code/local/Mdg/Giftregistry/Model/Mysql4/Type.php)
<?php

class Mdg_Giftregistry_Model_Mysql4_Type extends Mage_Core_Model_

Mysql4_Abstract

{

public function __construct()

{

$this->_init('mdg_giftregistry/type', 'type_id');

}

}

最后,我们需要创建一个用于获取所有可用事件类型的collection类:

  1. 在我们的模块目录中找到刚刚创建的Mysql4文件夹
  2. 在Mysql4文件夹下创建一个Type文件夹,然后再文件夹内创建一个名为Collection.php的文件(app/code/local/Mdg/Giftregistry/Model/Mysql4/
    Type/Collection.php)并拷入如下代码:
class Mdg_Giftregistry_Model_Mysql4_Type_Collection extends Mage_

Core_Model_Mysql4_Collection_Abstract

{

public function __construct()

{

$this->_init('mdg_giftregistry/type');

parent::_construct();

}

}

下面我们再创建一个处理礼品登记产品的模型,该模型存放所有与商品有关的产品信息,具体步骤如下:

  1. 在模块目录下找到Model文件夹
  2. 创建一个全新的Item.php文件,并将事下代码拷入该文件中(app/code/local/Mdg/Giftregistry/Model/Item.php):
class Mdg_Giftregistry_Model_Item extends Mage_Core_Model_Abstract

{

public function __construct()

{

$this->_init('mdg_giftregistry/item');

parent::_construct();

}

}

下一步创建资源类:

  1. 进入模块中的Model文件夹
  2. 打开Mysql4文件夹
  3. 创建一个全新的Item.php并将如下代码拷入该文件中(app/code/local/Mdg/Giftregistry/Model/Mysql4/
    Item.php):
class Mdg_Giftregistry_Model_Mysql4_Item extends Mage_Core_Model_

Mysql4_Abstract

{

public function __construct()

{

$this->_init('mdg_giftregistry/item', 'item_id');

}

}

最后创建一个对应的集合类:

  1. 找到本模块下的Model文件夹
  2. 创建一个Collection.php文件,并将以下代码拷入该文件中(app/code/local/Mdg/Giftregistry/Model/
    Mysql4/Item/Collection.php)
 class Mdg_Giftregistry_Model_Mysql4_Item_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract

{

public function __construct()

{

$this->_init('mdg_giftregistry/item');

parent::_construct();

}

}

下一步创建Registry实体,这是礼品登记插件的重要组成部分,因为它是将所有文件整合在一起的模型,要实现这点需按如下步骤进行操作:

  1. 找到本模块下的Model文件夹
  2. 创建一个Entity.php文件,并将如下代码拷入文件中(app/code/local/Mdg/Giftregistry/Model/
    Entity.php)
class Mdg_Giftregistry_Model_Entity extends Mage_Core_Model_Abstract

{

public function __construct()

{

$this->_init('mdg_giftregistry/entity');

parent::_construct();

}

}

然后再创建resource类

  1. 找到本模块中的Model文件夹
  2. 打开Mysql4目录
  3. 创建一个名为Entity.php的文件并将如下代码拷入该文件中(app/code/local/Mdg/Giftregistry/Model/
    Mysql4/Entity.php)
 class Mdg_Giftregistry_Model_Mysql4_Entity extends Mage_Core_Model_Mysql4_Abstract

{

public function __construct()

{

$this->_init('mdg_giftregistry/entity', 'entity_id');

}

}

然后创建一个对应的collection类:

  1. 找到本模块下的Model文件夹
  2. 创建一个Collection.php并如下代码(app/code/local/Mdg/Giftregistry/Model/
    Mysql4/Entity/Collection.php)
 class Mdg_Giftregistry_Model_Mysql4_Entity_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract

{

public function __construct()

{

$this->_init('mdg_giftregistry/entity');

parent::_construct();

}

}

以上我们加入了大量代码来为这一模块加入了多个模型类,下面让我们通过IMC(Interactive Magento Console,安装方法参见Magento ORM与数据集合一节)来测试一下这个新创建的模型。首先在Magento安装目录下输入如下指令来打开IMC测试我们上面创建的模型:

php shell/imc.php

以下假设您安装了测试数据或者已经拥有了相关数据:

  1. 首先加载客户模型
    magento > $customer = Mage::getModel('customer/customer')->load(1);
    
  2. 其次实例化一个新的礼品登记对象:
    magento > $registry = Mage::getModel('mdg_giftregistry/entity');
    
  3. Magento自带模型中有一个较为易用的方法getData(),它返回一个所有对象属性的数组,下面在礼品登记和客户两个对象中分别运行这个方法来对比输出结果
    magento > print_r($customer->getData());
    
    magento > print_r($registry->getData());
    
  4. 您可能已经注意到,customer对象返回示例客户的数据集,而registry对象返回一个穿宽的$regiarray数组,我们再来试试如下代码:
    magento > $registry->setCustomerId($customer->getId());
    
    magento > $registry->setTypeId(1);
    
    magento > $registry->setWebsiteId(1);
    
    magento > $registry->setEventDate('2012-12-12');
    
    magento > $registry->setEventCountry('CA');
    
    magento > $registry->setEventLocation('Toronto');
    
  5. 下面能过如下代码尝试再次打印registry数据
    magento > print_r($registry->getData());
    
  6. 最后,为了让更改生效,调用模型save方法
magento > $registry->save();

但是会发现在保存产品时出现了报错,报错信息如下:

Fatal error: Call to a member function beginTransaction() on a nonobject

in …/app/code/core/Mage/Core/Model/Abstract.php on line 313

究竟是什么原因呢?所调用的save()方法是父类Mage_Core_Model_Mysql4_Abstract中的一部分,该类又会调用抽象类save()方法,但在config.xml配置文件中却缺少了一个重要内容。要让Magento正确识别要使用的是哪一个资源类,我们需要指定资源模型类以及每个实体相匹配的数据表。下面就对该配置文件进行相应的修改:

  1. 找到插件下的etc/文件夹
  2. 打开config.xml配置文件
  3. 使用如下代码对>model>节点进行修改(app/code/local/Mdg/Giftregistry/etc/config.xml)
...

<models>

	<mdg_giftregistry>

		<class>Mdg_Giftregistry_Model</class>

		<resourceModel>mdg_giftregistry_mysql4</resourceModel>

	</mdg_giftregistry>

	<mdg_giftregistry_mysql4>

		<class>Mdg_Giftregistry_Model_Mysql4</class>

		<entities>

			<entity>

				<table>mdg_giftregistry_entity</table>

			</entity>

			<item>

				<table>mdg_giftregistry_item</table>

			</item>

			<type>

				<table>mdg_giftregistry_type</table>

			</type>

		</entities>

	</mdg_giftregistry_mysql4>

</models>

...

在将产品保存到数据库中之前,我们要先在数据库中创建表格,然后学习如何使用结构资源来创建所需的表结构并建立默认数据。这部分内容将在下一节Magento结构资源来进行探讨。

Magento ORM与数据集合

集合(collection)和模型(model)对于Magento的开发来说就像是一日三餐一样不可或缺。接下来我们会讲解Magento的ORM系统(Object Relational Mapping-对象关系映射),并学习如何正确地使用数据集合以及EAV系统。Magento像大多的当代系统一样使用了对象关系映射(ORM)。
对象关系映射(Object-relational mapping,常缩写为ORM,O/RM或O/R mapping)是一种将类型相互不兼容的系统间面向对象编程语言数据进行相互转化的计算机软件。它创建一个”虚拟对象数据库”(virtual object database)以供编程语言在内部使用。
在后面的文章中,我们会着重讲解:

  • Magento模型
  • Magento数据模型解析
  • EAV和EAV模型
  • 如何使用SQL查询

我们还会使用一些代码来创建一个简易框架以供实验
首先下载https://github.com/amacgregor/mdg_imc文件然后上传到Magento的根目录,如果您在Linux上安装了GitHub的话(yum install git -y)的话,可以直接使用git clone https://github.com/amacgregor/mdg_imc.git将文件下载到当前目录,再拷贝整个shell文件夹到Magento根目录。
然后cd到Magento根目录,执行shell/imc.php,如果执行成功的话,会出现magento >字样。

Magento模型解析

前面已经讲到,Magento的数据模型用于操作和获取数据,数据层分为简单模型和EAV两种:

  • 简单模型:这些模型应用是简单的一个对象对应一张表的映射,也就是说对象属性与表结构内容一一对应。
  • EAV模型(Entity Attribute Value Models):这些模型用于描述一个动态数量属性的实体。

注:并非所有的Magento模型都通过ORM来进行继承。Observer就是例子,这种简易模型类并不映射某一特定数据库表或实体。
除此之外,每个模型类型由如下层组成:

  • 模型类:大部分业务逻辑都存放在这里。模型用于操作数据,但不会直接连接数据。
  • 资源模型类:资源模型(Resource Model)用于与数据库进行交互,实际控制增删改查(CRUD-Create, Read, Update, Delete)的操作。
  • 模型集合类:每个数据模型都有一个集合类,集合是一组包含多个Magento模型实例的对象。

Magento模型并不包含与数据库连接的逻辑,它们是并不了解数据库,事实上连接数据库的代码存放在资源模型层中。这使得Magento可以兼容多种类型的数据库和平台。尽管当前只正式支持MySQL,但却可以重写一个针对其它数据库的资源类,而无需修改其它模型逻辑代码。
模型层级关系
下面我们通过实例化一个产品对象并设置一些产品的属性来进行进一步的了解:

  1. 打开刚刚添加的Magento互动控制器(即前述的magento >界面)
    cd /var/www/html
    php shell/imc.php
  2. 第一步输入如下指令来创建一个新的产品对象实例
    $product = Mage::getModel(‘catalog/product’);
  3. 通过如下指令可查看这个产品类是否为空实例
    echo get_class($product);
  4. 如果运行正常的话您将得到如下内容:
    Magento_Catalog_Model_Product
  5. 如果我们想要深入了解该类的方法的话,可以运行如下指令:
    print_r(get_class_methods($product));

该指令将返回一个包含该类中所有方法的数组,下面让我们通过运行如下代码来修改产品价格和名称:

$product = Mage::getModel('catalog/product')->load(2);
$name = $product->getName() . '-TEST';
$price = $product->getPrice();
$product->setPrice($price + 15);
$product->setName($name);
$product->save();

上面第一行代码会实例化一个具体对象,然会从对象中获取name属性,之后设置价格和名称,最终保存对象。
如果您打开Magento产品类Mage_Catalog_Model_Product (app/code/core/Mage/Catalog/Model/product.php),首先就会看到该类中定义了getName()和getPrice()方法,却没有setPrice()和setName()方法。那么Magento使用什么方法定义产品对象setter和getter方法的呢?虽然定义了getPrice()和getName()方法,但却没有定义color或manufacturer等产品属性的getter和setter方法。那么下一节我们就一起来看看Magento的魔术方法

Magento MVC详解

模型-Models

Magento采用对象关系映射(Object Relational Mapping,简称ORM)方法,尽管还可以使用Zend_Db来直接连接数据库,但大多数的时候我们将使用模型来获取数据。要实现这一任务,Magento提供了如下两种模型:

  • 简易模型(Simple models):这种模型通过简单的一个对象对应一张表的映射方法,也就是说对象属性对应各字段以及表结构
  • EAV模型(Entity Attribute Value):此类模型用于能通过动态的属性数量来描述实体(entities)

Magento中模型层被分成两个部分:一个是处理业务逻辑的模型,另一个是处理数据交互的资源。这种设计使得Magento可以最终支持多个数据库平台,而无需修改模型内的任何逻辑。
Magento ORM采用PHP的魔术方法来动态连接对象属性。后面我们会更加深入地探讨模型、Magento ORM以及数据集合方法的内容。
注:Magento的模型不一定要与任何数据库中的表或EAV实体来关联,后面我们将要讲到的observer就是这方面的例子。

视图-Views

与其它的MVC应用相比,Magento的视图层可以说是独树一帜,不同于传统的MVC模型,Magento的视图层分为如下三个部分:

  • Layouts:布局文件(layout)是一些用于定义块结构、像name之类的属性以及我们可以使用的模板文件等的XML文件。每个Magento模块都有其自己的布局文件。
  • Blocks:Magento通过将大多数的逻辑转移到块(block)中来降低控制器的压力。
  • Templates:模板是一些包含HTML和PHP代码的phtml文件。

布局文件让Magento前端具有极大的灵活性,每个模块都有自己的XML布局文件,用于告诉Magento在哪里包含和处理各个页面请求。通过使用布局文件,我们可以在store中移动、添加或者删除块,完全无需修改对应XML以外的任何文件。

布局文件详解

下面我们来看看一个核心布局文件catalog.xml:

<layout version="0.1.0">

	<default>

	<reference name="left">

		<block type="core/template" name="left.permanent.callout"

		template="callouts/left_col.phtml">

			<action method="setImgSrc">

			<src>images/media/col_left_callout.jpg</src></action>

			<action method="setImgAlt" translate="alt"

			module="catalog"><alt>

			Our customer service is available 24/7.

			Call us at (555) 555-0123.</alt></action>

			<action method="setLinkUrl">

			<url>checkout/cart</url></action>

		</block>

	</reference>

	<reference name="right">

		<block type="catalog/product_compare_sidebar"

		before="cart_sidebar" name="catalog.compare.sidebar"

		template="catalog/product/compare/sidebar.phtml"/>

		<block type="core/template" name="right.permanent.callout"

		template="callouts/right_col.phtml">

		<action method="setImgSrc">

		<src>images/media/col_right_callout.jpg</src></action>

		<action method="setImgAlt" translate="alt"

		module="catalog"><alt>

		Visit our site and save A LOT!</alt></action>

		</block>

	</reference>

	<reference name="footer_links">

		<action method="addLink" translate="label title"

		module="catalog" ifconfig="catalog/seo/site_map">

		<label>Site Map</label><url

		helper="catalog/map/getCategoryUrl" />

		<title>Site Map</title></action>

	</reference>

	<block type="catalog/product_price_template"

	name="catalog_product_price_template" />

</default>

可以看出布局文件由如下三个主要XML节点(node)组成:

  • handle:每个页面请求都会包含多个句柄(handle),布局通过这些句柄来通知Magento对于每个页面加载并渲染哪些块。最常用的句柄为default和
    [frontname]_[controller]_[action]。default句柄在设置全局块时尤为重要,比如在每个页面的header块中加入CSS和JavaScript
  • reference:节点用于引用一个块,多用于指定一个嵌套块或修改一个已存在的块。上面的示例代码中,内部就指定了一个新的子块。
  • block:节点用于加载块,每个块节点可带有如下属性:
    • type:它是实际块类的一个标识符,例如catalog/product_list引用了Mage_Catalog_Block_Product_List
    • name:这是其它块用于引用当前块的名称。
    • before/after:这两个属性用于指定当前块相对于其它块的位置,它们两个还可通以使用破折号-作值来指定模块在顶部或底部显示。
    • template:这一属性用于指定渲染块的.phtml模板文件
    • action:每个块类型都有可影响前端功能的动作。比如page/html_head就可通过addJs和addCss这两个动作来添加CSS和JavaScript
    • as:用于指定我们在模块中调用块的唯一标识符,比如通过getChildHtml(‘block_name’)来调用子块。

块是Magento采用的一个新的概念,用于减少对控制器的加载。通常是一些直接连接模块的数据资源,在需要的时候可以操作这些数据并传递给视图。
此外,PHTML模板文件中可包含html和php标签,用于控制模型中数据的格式以及显示。下面让我们来看看产品视图模板文件中的一段代码:

<div class="product-view">

...

	<div class="product-name">

		<h1><?php echo $_helper->productAttribute

		($_product, $_product->getName(), 'name') ?></h1>

	</div>

...

	<?php echo $this->getReviewsSummaryHtml

	($_product, false, true)?>

	<?php echo $this->getChildHtml('alert_urls') ?>

	<?php echo $this->getChildHtml('product_type_data') ?>

	<?php echo $this->getTierPriceHtml() ?>

	<?php echo $this->getChildHtml('extrahint') ?>

...

	<?php if ($_product->getShortDescription()):?>

		<div class="short-description">

			<h2><?php echo $this->__('Quick Overview') ?></h2>

			<div class="std"><?php echo $_helper->

			productAttribute($_product, nl2br($_product->

			getShortDescription()), 'short_description') ?></div>

		</div>

	<?php endif;?>

...

</div>

下面是有关MVC结构的图表:
MVC块流程图

控制器-Controllers

Magento中的MVC控制器采用轻量级控制器设计,这种轻量级控制器包含较少的业务逻辑,多用于驱动应用的请求。一个基本的Magento控制器动作会加载并渲染布局:

public function viewAction()

{

	$this->loadLayout();

	$this->renderLayout();

}

这以后就通过块来处理显示逻辑、从模型中获取数据、准备数据并将数据发送给视图。