深入理解Magento

作者:Alan Storm 
翻译:Hailong Zhang

第四章 – 模型和ORM基础

对于任何一个MVC架构,模型(Model)层的实现都是占据了很大一部分。对于Magento来说,模型占据了一个更加重要的位置,因为它常常包含了一部分商业逻辑代码(可以说它对,也可以说它错)。这些代码在其他的MVC框架中往往出现在控制器或者帮助函数中。

传统的PHP MVC架构中的模型

本来MVC的定义就不是很清晰,不同的人有不同的看法,而对于模型的定义争议就更多了。在MVC模式被广泛采用之前,PHP程序员往往通过SQL语句直接操作数据库。也有些程序员通过一个SQL抽象层来操作数据库(比如AdoDB)。程序员往往关注SQL语句本身,而不是和数据相关的对象。

虽然直接操作SQL的方式一直被病诟,但是很多PHP框架还是以SQL为中心的。模型层提供了一系列对象,抽象/封装了数据操作,但是程序员最终还是需为模型层对象写SQL语句操作数据库。

还有一些框架回避了SQL,使用了对象关系映射(Object Relational Mapping,ORM)来解决这个问题。使用这个方法的话,程序员不用关注SQL,而只需要和对象打交道。我们可以操作一个对象的属性,当“Save” 方法被调用的时候,对象的属性会作为数据自动的被写入数据库。有些ORM框架会根据数据表的信息自动推测对象的属性,也有框架要求用户显示的生命(应为“声明”——博主注)对象属性和表的关系。比较有名的ORM框架有ActiveRecord等等。【译者注:ActiveRecord源自Ruby on Rails,不过现在PHP也有了】

关于ORM的概念,我就解释到这里。但是和许多计算机领域的其他概念一样,ORM的定义也越来越模糊了。我不想在这篇文章中讨论关于ORM的争议,所以我说的ORM就是那个最基本的ORM概念。

Magento的模型

Magento理所当然的也追随潮流应用了ORM。虽然Magento自带的Zend框架提供了SQL抽象层,但是在大多数情况下我们将通过 Magento自带的模型和我们自己的模型来进行数据访问。他和视图层(View)一样,Magento的模型层也不是简单的ORM,而是一个高度灵活, 高度抽象甚至有点令人费解。

解剖Magento的模型

大部分的Magento模型分为两类。第一类是基本的ActiveRecord类型,一张表一个对象的模型。第二类是Entity Attribute Value(EAV)模型。【译者注:EAV翻译成“实体属性值”有点词不达意,还是就叫EAV的好】Magento自己定义了一个数据类型叫做模型集合 (Model Collection)。顾名思义,模型集合就是一个对象里面包含了很多模型对象。Magento的创造者Varien团队实现了PHP类库的标准接口,“IteratorAggregate”,“Countable”。这样模型集合就能调用这些方法,这也是模型集合和数组的区别。

Magento的模型并不直接访问数据库。每一个模型都有一个资源模型(Resource Model),每一个资源模型拥有两个适配器(Adapter),一个读,一个写。这样的话逻辑模型和数据库访问就分开了,所以从理论上讲更改底层数据库只需要重写适配器就可以了,所有上层代码都不需要更改。

创建一个基本模型

【译者注:从这一章开始我用我自己的例子替换了Alan的例子】继续我们Hello World的例子。在Hello World模块中创建BlogController.php如下

  1. class Zhlmmc_Helloworld_BlogController extends Mage_Core_Controller_Front_Action {
  2. public function indexAction()
  3. {
  4. echo 'Hello Blog';
  5. }
  6. }

访问以下URL
http://127.0.0.1/Magento/helloworld/blog

你应该看到“Hello Blog”输出。

创建数据表

我们可以通过Magento自带的方法创建或者修改数据库,但是为了不引入过多新内容,我们暂且手工创建一张表。在你的数据库中执行以下语句

  1. CREATE TABLE `blog_posts` (
  2. `blogpost_id` int(11) NOT NULL auto_increment,
  3. `title` text,
  4. `post` text,
  5. `date` datetime default NULL,
  6. `timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP,
  7. PRIMARY KEY  (`blogpost_id`)
  8. );
  9. INSERT INTO `blog_posts` VALUES (1,'My New Title','This is a blog post','2009-07-01 00:00:00','2009-07-02 23:12:30');

这里我们创建了一张名为“blog_posts”的表,并填充了一条数据。

创建模型

要设置一个模型一共有以下四个步骤

  1. 启用模型
  2. 启用资源模型
  3. 在资源模型中添加实体(Entity)。对于简单的模型来说,实体就是数据表的名字
  4. 为资源模型设置读、写适配器

在进行这些步骤之前,我们先来看假设这些步骤已经做完了,我们怎么用一个模型。在Magento中,我们用以下的方式来实例化一个模型

  1. $model = Mage::getModel('helloworld/blogpost');

和我们以前讲过的“Mage::getHelper()”的原理类似,这里Magento也是通过全局配置去查找模型的类名。模型的类名和我们以前讲过的块类名一样,都是分组类名。这里参数的前半部分“helloworld”是组名(Group Name),后半部分“blogpost”是半类名(Class Name)【译者注:我将“Class Name”翻译成半类名是为了和类名区分开来】。具体步骤如下

  1. 从全局配置“/global/models/GROUP_NAME/class”获得基本类名“Zhlmmc_Helloworld_Model”
  2. 检查全局配置“/global/models/GROUP_NAME/rewrite/CLASS_NAME”是否设置,如果有那么这个节点的值将被作为类名实例化
  3. 否则,最终的类名将是基本类名加上半类名,也就是“Zhlmmc_Helloworld_Model_Blogpost”

启用模型

修改模块的config.xml

  1. <global>
  2. <!-- ... -->
  3. <models>
  4. <helloworld>
  5. <class>Zhlmmc_Helloworld_Model</class>
  6. <!--
  7. need to create our own resource, can't just
  8. use core_mysql4
  9. -->
  10. <resourceModel>helloworld_mysql4</resourceModel>
  11. </helloworld>
  12. </models>
  13. <!-- ... -->
  14. </global>

标签<helloworld />就是组名,也应该和模块名一致。<class />标签的内容是基本类名,所有Helloworld模块的模型都用这个基本类名,命名方式如下
Packagename_Modulename_Model

<resourceModel />标签指明了这个模块的模型要用哪个资源模型。这个标签的内容是组名加上“mysql4”我们将在后面详细介绍资源模型。

现在让我们来实例化一个模型看看,修改indexAction方法

  1. public function indexAction() {
  2. $blogpost = Mage::getModel('helloworld/blogpost');
  3. echo get_class($blogpost);
  4. }

清空Magento缓存,刷新页面,你应该看到一个类似这样的异常(请先打开Magento的开发模式 )

  1. include(Zhlmmc\Helloworld\Model\Blogpost.php) [<a href='function.include'>function.include</a>]: failed to open stream: No such file or directory

原因很简单,就是Magento尝试去实例化“Zhlmmc_Helloworld_Model_Blogpost”,但是它在Helloworld模块的文件夹里面找不到这个类。所以我们现在来创建这个类
File: app/code/local/Zhlmmc/Helloworld/Model/Blogpost.php

  1. class Zhlmmc_Helloworld_Model_Blogpost extends Mage_Core_Model_Abstract
  2. {
  3. protected function _construct()
  4. {
  5. $this->_init('helloworld/blogpost');
  6. }
  7. }

刷新页面,你应该看到页面上显示“Zhlmmc_Helloworld_Model_Blogpost”。所有的模型都必须继承 “Mage_Core_Model_Abstract”类。这个抽象类强制你实现一个方法“_construct”(注意:这个不是PHP的构造行数 “__construct”)。这个方法应该调用父类已经定义好的“_init”方法,参数是资源模型的URI,也就是我们要告诉模型使用哪个资源模型。 我们将在解释资源模型的时候再解释这个URI。

启用资源模型并添加实体

好了,我们设置好了模型,下面我们要为模型设置资源模型。资源模型才是真正和数据库对话的组件。在模型的配置中,有一段这样的代码

  1. <resourceModel>helloworld_mysql4</resourceModel>

<resourceModel />的值将被用来实例化资源模型。我们不需要显式的调用资源模型,但是当一个模型需要访问数据库的时候,Magento会自动实例化一个资源模型来使用。

  1. Mage::getResourceModel('helloworld/blogpost');

这里“helloworld/blogpost”就是我们给模型的“_init”传入的参数。“helloworld”是组名,“blogpost”是模型的半类名。“Mage::getResourceModel”方法将以“helloworld/blogpost”为URI在全局配置中找到<resourceModel>标签的值,在这里是“helloworld_mysql4”。然后Magento会用 URI“helloworld_mysql4/blogpost”去实例化资源模型类。实例化的过程和我们前面讲的模型的实例化是一样的,所以我们也需要 在config.xml中添加资源模型的声明

  1. <global>
  2. <!-- ... -->
  3. <models>
  4. <!-- ... -->
  5. <helloworld_mysql4>
  6. <class>Zhlmmc_Helloworld_Model_Resource_Mysql4</class>
  7. </helloworld_mysql4>
  8. </models>
  9. </global>

这里我们可以看到,资源模型的声明也是放在<models />下面的。有点搞,但是也不必深究了,Magento就这么定义的。<class />标签的值是所有资源模型类的基本类名,命名方式如下
Packagename_Modulename_Model_Resource_Mysql4

好了,我们已经配置了资源模型,我们来试试装载一些数据。修改indexAction如下

  1. public function indexAction() {
  2. $params = $this->getRequest()->getParams();
  3. $blogpost = Mage::getModel('helloworld/blogpost');
  4. echo("Loading the blogpost with an ID of ".$params['id']."<br/>");
  5. $blogpost->load($params['id']);
  6. $data = $blogpost->getData();
  7. var_dump($data);
  8. }

清空Magento缓存,访问下面的页面
http://127.0.0.1/Magento/helloworld/blog/index/id/1

你应该看到一个类似下面这样的异常

  1. include(Zhlmmc\Helloworld\Model\Resource\Mysql4\Blogpost.php) [function.include]: failed to open stream: No such file or directory

我想你看到这里也明白了,我们要为模型添加一个资源类,添加如下文件
File: app/code/local/Zhlmmc/Helloworld/Model/Resource/Mysql4/Blogpost.php

  1. class Zhlmmc_Helloworld_Model_Resource_Mysql4_Blogpost extends Mage_Core_Model_Mysql4_Abstract{
  2. protected function _construct()
  3. {
  4. $this->_init('helloworld/blogpost', 'blogpost_id');
  5. }
  6. }

这里“_init”方法的第一个参数这个资源模型将要使用的数据表的URI,第二个参数是数据表中的列名。这个列的内容必须唯一,往往是数据表的主键。

为资源模型添加实体

刷新页面,你是不是得到下面的异常?

  1. Can't retrieve entity config: helloworld/blogpost

那是因为我们的资源文件现在还是一个空壳,并没有和数据库联系起来。现在我们来把资源模型和我们的表联系起来,修改config.xml如下

  1. <global>
  2. <!-- ... -->
  3. <models>
  4. <!-- ... -->
  5. <helloworld_mysql4>
  6. <class>Zhlmmc_Helloworld_Model_Resource_Mysql4</class>
  7. <entities>
  8. <blogpost>
  9. <table>blog_posts</table>
  10. </blogpost>
  11. </entities>
  12. </helloworld_mysql4>
  13. </models>
  14. </global>

我们前面设置了资源模型使用的数据表的URI是“helloworld/blogpost”,那么Magento会把“helloworld”作为组 名,“blogpost”作为实体名,也就是<blogpost>。在Magento的简单模型中(也就是继承 Mage_Core_Model_Mysql4_Abstract的模型),一个实体对应一张数据表。我们的数据表是“blog_posts”,所以这 里<table />标签的内容就是“blog_posts”。

清空Magento缓存,再次刷新页面,你应该看到以下内容

  1. Loading the blogpost with an ID of 1
  2. array(5) { ["blogpost_id"]=> string(1) "1" ["title"]=> string(12) "My New Title" ["post"]=> string(19) "This is a blog post" ["date"]=> string(19) "2009-07-01 00:00:00" ["timestamp"]=> string(19) "2009-07-02 23:12:30" }

设置读写适配器

在上面的例子中,我们已经可以从数据库中取数据了,但是我们却没有为资源模型设置读写适配器,怎么回事呢?原因很简单,那就是因为Magento会为没有适配器的资源模型启用默认适配器。我们也可以显式的配置默认的适配器

  1. <global>
  2. <!-- ... -->
  3. <resources>
  4. <helloworld_write>
  5. <connection>
  6. <use>default_write</use>
  7. </connection>
  8. </helloworld_write>
  9. <helloworld_read>
  10. <connection>
  11. <use>default_read</use>
  12. </connection>
  13. </helloworld_read>
  14. </resources>
  15. </global>

在<resources />标签下面有两个部分,一个读,一个写。标签名字中的“hellworld”是我们定义的组名【译者注:在资源模型的“_init”函数中传入的 数据表的URI “helloworld/blogpost”的前半部分就是适配器名字的前半部分】。从这里我们也可以看出来一个资源组对应一对适配器。清空 Magento缓存,刷新浏览器,你应该看到和刚才相同的页面。【译者注:如果你去全局配置中找“core_read”你会发现 “default_read”,然后是“default_setup”

  1. <default_setup>
  2. <connection>
  3. <model>mysql4</model>
  4. <initStatements>SET NAMES utf8</initStatements>
  5. <type>pdo_mysql</type>
  6. <host>localhost</host>
  7. <username>root</username>
  8. <password>admin</password>
  9. <dbname>zend-magento</dbname>
  10. <active>1</active>
  11. </connection>
  12. </default_setup>

这才是最终和数据库连接的详细信息。如果你再往下深究,你会发现全局配置有这么一段

  1. <resource>
  2. <connection>
  3. <types>
  4. <pdo_mysql>
  5. <class>Mage_Core_Model_Resource_Type_Db_Pdo_Mysql</class>
  6. </pdo_mysql>
  7. </types>
  8. </connection>
  9. </resource>

所以,“Mage_Core_Model_Resource_Type_Db_Pdo_Mysql”才是最终连接数据库的类。如果我们更换数据库的话,我们要重写一个相似的类来连接别的数据库。

基本模型操作

所有的模型最终都继承自类“Varien_Object”。这个类属于Magento的系统类库,不属于Magento的核心模块。你可以在以下位置找到这个类
lib/Varien/Object.php

Magento模型的数据保存在“_data”属性中,这个属性是“protected”修饰的。父类“Varian_Object”定义了一些函数用来 取出这些数据。我们上面的例子用了“getData”,这个方法返回一个数组,数组的元素是“key/value”对。【译者注:其实就是数据表中一行的 数据,“key”就是列名,“value”就是值】我们可以传入一个参数获取某个具体的“key”的值。

  1. $model->getData();
  2. $model->getData('title');

还有一个方法是“getOrigData”,这个方法会返回模型第一次被赋予的值。【译者注:因为模型在初始化以后,值可以被修改,这个方法就是拿到那个最原始的值】

  1. $model->getOrigData();
  2. $model->getOrigData('title');

“Varien_Object”也实现了一些PHP的特殊函数,比如神奇的“__call”。你可以对任何一个属性调用“get, set, unset, has”方法

  1. $model->getBlogpostId();
  2. $model->setBlogpostId(25);
  3. $model->unsetBlogpostId();
  4. if($model->hasBlogpostId()){...}

这里的方法名中的属性名字符合“camelcase”命名规则 【译 者注:简单的说就是Java的命名规则,每个单词的第一个字母大写,第一个字母可以大写也可以小写】。为了有效的利用这些方便的方法,我们在定义数据表列 名的时候要用小写,并用下划线作为分隔符,比如“blogpost_id”。在最近的Magento版本中,这个规则已经被弱化,为了实现PHP的 “ArrayAccess”接口

  1. $id = $model->['blogpost_id'];
  2. $model->['blogpost_id'] = 25;
  3. //etc...

也就是说,你会在Magento中同时看到这两种技巧的使用。

Magento中的CRUD操作

Magento模型通过“load, save, delete”三个方法来支持基本的Create,Read,Update和Delete操作。我们在上面已经使用过“load”方法了。这个方法的参数就是要装在的数据记录的“id”。

  1. $blogpost->load(1);

“save”方法可以用来创建新数据或者修改已有数据。我们在BlogController.php中添加如下方法

  1. public function createNewPostAction() {
  2. $blogpost = Mage::getModel('helloworld/blogpost');
  3. $blogpost->setTitle('Code Post!');
  4. $blogpost->setPost('This post was created from code!');
  5. $blogpost->save();
  6. echo 'post created';
  7. }

访问以下URL
http://127.0.0.1/Magento/helloworld/blog/createNewPost

现在你数据表中应该有两条数据了。下面来修改一条数据

  1. public function editFirstPostAction() {
  2. $blogpost = Mage::getModel('helloworld/blogpost');
  3. $blogpost->load(1);
  4. $blogpost->setTitle("The First post!");
  5. $blogpost->save();
  6. echo 'post edited';
  7. }

最后,我们来删除一条数据

  1. public function deleteFirstPostAction() {
  2. $blogpost = Mage::getModel('helloworld/blogpost');
  3. $blogpost->load(1);
  4. $blogpost->delete();
  5. echo 'post removed';
  6. }

模型集合

上面的例子我们只是演示了对单个数据操作,现在我们来看看如何同时操作多条记录。我们上面已经讲过,每个Magento的模型都有一个独特的模型集 合。这些模型集合实现了PHP的“IteratorAggregate”和“Countable”接口,也就是他们可以作为“count”函数的参数,并 且可以在“for each”语句中使用。

现在让我们来看看如何使用模型集合,在Blog控制器中添加如下方法

  1. public function showAllBlogPostsAction() {
  2. $posts = Mage::getModel('helloworld/blogpost')->getCollection();
  3. foreach($posts as $blog_post){
  4. echo '<h3>'.$blog_post->getTitle().'</h3>';
  5. echo nl2br($blog_post->getPost());
  6. }
  7. }

访问如下URL
http://127.0.0.1/Magento/helloworld/blog/showAllBlogPosts

你应该看到以下异常

  1. include(Zhlmmc\Helloworld\Model\Resource\Mysql4\Blogpost\Collection.php) [function.include]: failed to open stream: No such file or directory

我想你不会被这个异常吓到,已经熟门熟路了。我们需要添加一个PHP类,定义Blogpost的模型集合。每个模型都有一个“protected”属性 “_resourceCollectionName”【译者注:从父类“Mage_Core_Model_Abstract”继承来的】。这个属性的值是 这个模型对应的模型集合的URI。

  1. protected '_resourceCollectionName' => string 'helloworld/blogpost_collection'

在默认情况下,这个值是模型的URI加上“_collection”。Magento把模型集合也看做是一种资源(Resrouce),所以运用资源模型的命名规则,模型集合的全名是
Zhlmmc_Helloworld_Model_Resource_Mysql4_Blogpost_Collection

然后我们要创建如下文件
File: app/code/local/Zhlmmc/Helloworld/Model/Resource/Mysql4/Blogpost/Collection.php

  1. class Zhlmmc_Helloworld_Model_Resource_Mysql4_Blogpost_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract {
  2. protected function _construct()
  3. {
  4. $this->_init('helloworld/blogpost');
  5. }
  6. }

这里的参数是模型的UR,用来I来初始化模型集合。刷新页面,你应该看到数据库中的Blog都显示出来了。

总结

首先我要恭喜你,到这里你已经创建并配置了你的第一个Magento模型。在后面的章节中我们将讲解Magento的另外一种模型Entity Attribute Value Model。

在这章开始的时候,我撒了一个小谎。其实在Magento中,并不是所有的模型都继承自“Mage_Core_Model_Abstract”。在 Magento最初的版本中,这个抽象类并不存在。所以有很多模型是直接继承自“Varien_Object”。不过这些并不影响我们创建Magento 模型,了解一下就可以了,方便阅读Magento的代码。

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

深入理解Magento – 第四章 – 模型和ORM基础的更多相关文章

  1. Magento模型和ORM基础

    对于任何一个MVC架构,模型(Model)层的实现都是占据了很大一部分.对于Magento来说,模型占据了一个更加重要的位置,因为它常常包含了一部分商业逻辑代码(可以说它对,也可以说它错).这些代码在 ...

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

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

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

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

  4. 深入理解Magento – 第三章 – 布局,块和模板

    深入理解Magento 作者:Alan Storm 翻译:Hailong Zhang 第三章 – 布局,块和模板 我们接着研究Magento.根据我们第二章讲的Magento MVC的架构,我们接下来 ...

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

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

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

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

  7. 第四章 Java并发编程基础

    线程简介 什么是线程? 现代操作系统在一个运行程序时,会为其创建一个进程.例如,启动一个Java程序,操作系统就会创建一个Java进程.现代操作系统调度的最小单元是线程,也叫轻量进程(Light We ...

  8. DTD文档模型和HTML基础

    html是超文本标记语言,现在常用到的2中文档格式是html5和XHTML 1.0 Transitiona(过渡). <!DOCTYPE html> <!--当前文档为html5-- ...

  9. Django模型和ORM

    一.ORM ORM介绍 ORM概念 对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术. 简单的说,ORM是 ...

随机推荐

  1. pipeline语法之判断一个文件存在与否

    先看一个例子,原理,根据命令返回的状态值来判断它的存在与否 pipeline { agent any stages { stage("check file if exists"){ ...

  2. linux下oracle数据库服务和监听的启动停止

    oracle数据库是重量级的,其管理非常复杂,将其在linux平台上的启动和关闭步骤整理一下. 安装完毕oracle以后,需要创建oracle系统用户,并在/home/oracle下面的.bash_p ...

  3. windows系统下tomcat启动startup.bat一闪而过

    遇到运行startup.bat后,一个窗口一闪而过的问题 tomcat在启动时,会读取环境变量的信息,需要一个CATALINA_HOME 与JAVA_HOME的信息,CATALINA_HOME即tom ...

  4. LUOGU P4587 [FJOI2016]神秘数(主席树)

    传送门 解题思路 如果区间内没有\(1\),那么答案就为\(1\),从这一点继续归纳.如果区间内有\(x\)个\(1\),设区间内\([2,x+1]\)的和为\(sum\),如果\(sum=0\),那 ...

  5. [CSP-S模拟测试]:金(king)(高精度+模拟)

    题目传送门(内部题36) 输入格式 第一行一个整数$T$,表示数据组数. 接下来$T$行,每行两个空格隔开的整数$n,m$. 输出格式 对于每组数据,输出一行$"Yes"$或$&q ...

  6. python singleton 4种单例

    def singleton(cls, *args, **kwargs): instances = {} def inner(cls, *args, **kwargs): if cls not in i ...

  7. dotnet core项目的nuget存储路径

    Where's the package location in aspnet core For project.json the nuget directory is in the user prof ...

  8. HTML5: HTML5 介绍

    ylbtech-HTML5: HTML5 介绍 1. 什么是 HTML5?返回顶部   HTML5 是下一代 HTML 标准. HTML,HTML 4.01的上一个版本诞生于1999年.自从那儿以后, ...

  9. js类型问题

    js比较数据一定要记得鉴别类型是否一致,number类型转换string 需要用到toString方法

  10. processing模拟三角级数合成方波过程

    代码 1: int radius = 2; 2: int[] accumys; 3: int times = 0; 4: 5: float scale = 1; 6: int origin = 400 ...