模型层级的应用在MVC框架中是一个非常重要的部分,它包含应用程序所需的数据。在Magento中,Model起的作用甚至还要更大,因为其中还包含业务逻辑,在其它的MVC框架中,业务逻辑通常是放在Controller或Helper方法中的。
注:在调试过程中如果没有出现我们所提到的报错,请开启报错,步骤如下:
a> 打开根目录下的index.php文件并取消如下注释:
#ini_set('display_errors', 1);
b> 打开.htaccess文件并在最后添加
SetEnv MAGE_IS_DEVELOPER_MODE “true”
MVC模型
首先我们来看看PHP传统的MVC模型,如果说MVC的定义不够清楚,那么模型的定义就更加模糊了。在PHP开发者大规模采用MVC框架前,数据是通过原生的SQL语句或SQL抽象层来获取的。这时开发者需要书写SQL语句并考虑为哪些对象建模。
如今很多人已不太愿意去书写原生SQL语句了,但很多PHP框架依然是在大量使用SQL。Model是提供抽象层的对象,但事实上程序员还是书写SQL语句或者通过抽象方法来读写数据以调用SQL。
也有一些框架避开SQL的使用而采用ORM(Object Relational Mapping)来完成相关操作。这种方法开发者通过对象设定属性,在调用对象中的save方法时,数据会被自动写入数据库。
我们再来看看Magento的模型,Magento是支持ORM的,同时Zend框架中的SQL抽象也可用。Magento中大多数的数据通过其内置的模型或自开发的模型来处理。Magento中Model的概念也是超级灵活、高度抽象的。
Magento中大多数的模型可以被划分为两类,一种是类似ActiveRecord的一个对象一张表的模型,另一种就是大名鼎鼎的EAV(Entity Attribute Value)模型。每个模型都有一个模型集合,集合(Collection)是用于包含一系列Magento模型实例的PHP对象。Magento开发团队在PHP标准库接口中加入了IteratorAggregate和Countable来让每个Model类型获取它自己的集合类型。如果你不太了解PHP标准库,可以把模型集合看作带有方法的数组。
Magento模型中没有代码去连接数据库,而是通过模型资源类来与数据库服务器进行交互(读或写adapter对象)。通过分离逻辑模型与数据库交互代码,理论上就可以在保持模型不变的情况下写一个新的资源类来操作不同类型的数据库。
首先我们要创建一个基本的Magento模型,按照传统我们来做一个weblog博客建模,步骤如下:
- 创建一个Weblog模块
- 为我们的Model创建一个数据表
- 在配置文件中添加一个Blogpost模型
- 在配置文件中为Blogpost模型添加模型资源信息
- 在配置文件中为Blogpost模型添加一个读适配器(Adapter)
- 在配置文件中为Blogpost模型添加一个写适配器
- 为Blogpost模型添加一个PHP类文件
- 为Blogpost资源模型添加一个PHP类文件
- 实例化该模型
那下面我们就按这个步骤来进行操作吧,通过前面的学习你应该对创建Weblog模块已经相当熟悉了,不过考虑到有些朋友没有从头开始看,这里再赘述一下,首先创建如下目录
app/code/local/Alanhou/Weblog/Block app/code/local/Alanhou/Weblog/controllers app/code/local/Alanhou/Weblog/etc app/code/local/Alanhou/Weblog/Helper app/code/local/Alanhou/Weblog/Model app/code/local/Alanhou/Weblog/sql
创建app/code/local/Alanhou/Weblog/etc/config.xml文件并加入如下代码
<config> <modules> <Alanhou_Weblog> <version>0.1.0</version> </Alanhou_Weblog> </modules> </config>
接着创建app/etc/modules/Alanhou_Weblog.xml文件并加入如下代码
<config> <modules> <Alanhou_Weblog> <active>true</active> <codePool>local</codePool> </Alanhou_Weblog> </modules> </config>
执行以下步骤
- 清除缓存
- 在后台中,点击System > Configuration > Advanced > Advanced
- 在Disable modules output面板中查看Alanhou_Weblog是否出现
模块添加成功后我们来创建一个Action方法名为testModel的index控制器,首先在config.xml里添加router即加入如下代码:
<frontend> <routers> <weblog> <use>standard</use> <args> <module>Alanhou_Weblog</module> <frontName>weblog</frontName> </args> </weblog> </routers> </frontend>
在app/code/local/Alanhou/Weblog/controllers/IndexController.php中加入如下代码:
<?php class Alanhou_Weblog_IndexController extends Mage_Core_Controller_Front_Action{ public function testModelAction(){ echo 'Setup!'; } }
清除缓存并访问http://localhost/magento/weblog/index/testModel页面上会输出Setup!
Magento拥有一套可自动创建和修改数据库的系统,不过现在我们先手动为这个模型创建数据表,在phpMyAdmin或任何数据库客户端中进入数据库执行如下语句创建数据表blog_posts
CREATE TABLE <code>blog_posts</code> ( <code>blogpost_id</code> int(11) NOT NULL auto_increment, <code>title</code> text, <code>post</code> text, <code>date</code> datetime default NULL, <code>timestamp</code> timestamp NOT NULL default CURRENT_TIMESTAMP, PRIMARY KEY (<code>blogpost_id</code>) )
接着执行如下语句手动插入一条数据
INSERT INTO <code>blog_posts</code> VALUES (1,'My New Title','This is a blog post','2010-08-08 00:00:00','2010-08-08 23:12:30');
配置模型文件我们需要在配置文件中
- 在模块中开启Model
- 在模块中开启Model资源
- 为Model资源添加一个entity数据表配置
在实例化一个模型时需要进行类似下面这样的调用
$model = Mage::getModel('weblog/blogpost');
在传入的URI中前半部分是Model分组名(weblog),按照惯例它是我们模块名的小写形式,如果想更加保险些,可以加上命名空间,同样也需要使用小写;第二部分则是模块名称的小写形式(blogpost)。
在config.xml的config标签内添加如下代码
<global> <models> <weblog> <class>Alanhou_Weblog_Model</class> <resourceModel>weblog_resource</resourceModel> </weblog> </models> </global>
外部的<weblog />标签是组名,和模块名相同,<class />标签中内容为weblog分组中所有模型的基础名称,或称为类前缀。<resourceModel />标签指定weblog分组模型所使用的资源模型,还有更多的内容,但这里我们只要知道这个分组名接resource即可。
我们的操作还没完成,不过先清除缓存并通过如下代码尝试实例化blogpost模型:
public function testModelAction(){ // echo 'Setup!'; $blogpost = Mage::getModel('weblog/blogpost'); echo get_class($blogpost); }
重新加载页面就会出现类似下面的报错
Warning: include(Alanhou\Weblog\Model\Blogpost.php): failed to open stream: No such file or directory
可以看到这里尝试实例一个Mage_Weblog_Model_Blogpost类,Magento通过__autoload来包含这个模型,但无法找到文件,下面我们来创建app/code/local/Alanhou/Weblog/Model/Blogpost.php并加入如下代码:
<?php class Alanhou_Weblog_Model_Blogpost extends Mage_Core_Model_Abstract{ protected function _construct(){ $this->_init('weblog/blogpost'); } }
重新加载页面,提示信息应该会变成刚刚创建的类名,即Alanhou_Weblog_Model_Blogpost
所有与数据库交互的基础模型都继承Mage_Core_Model_Abstract类,这个抽象类会强制应用一个_construct方法(注意:这不是构造方法__construct);该方法会调用与Mage::getModel调用URI相符类的_init方法。
上面我们创建好了模型,下一步就是建立模型资源,模型资源中的代码将会操作数据库,本例中我们在配置文件中包含了
<resourceModel>weblog_resource</resourceModel>
<resourceModel />中的值会被用来实例化Model资源类,虽然这里没有调用该类,但任何weblog分组中的模型要和数据库进行交互时,都会调用如下方法来获取模型资源
Mage::getResourceModel('weblog/blogpost');
再强调一下,weblog是分组名,blogpost是模型名称,Mage::getResourceModel方法会使用weblog/blogpost来查看全局配置并获取<resourceModel>标签中的值(本例中即为weblog_resource)。接下来模型类会实例化如下URI:
weblog_resource/blogpost
因而,资源模型和常规模型一样都在config.xml中同处配置,因此,我们在config.xml中进行如下修改
<global> <!-- ... --> <models> <!-- ... --> <weblog_resource> <class>Alanhou_Weblog_Model_Resource</class> </weblog_resource> </models> </global>
这里我们从resourceModel标签中获取了值weblog_resource并作为标签添加到配置文件中,class标签中的值通过如下格式获取:
Packagename_Modulename_Model_Resource
下面我们修改testModel方法并重置加载页面
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); }
加载http://localhost/magento/weblog/index/testModel/id/1页面,会出现类似如下报错:
Warning: include(Alanhou\Weblog\Model\Resource\Blogpost.php): failed to open stream: No such file or directory
因此我们需要为模型添加一个资源类,每个模型都有自己的资源类,下面我就创建app/code/local/Alanhou/Weblog/Model/Resource/Blogpost.php文件并加入如下代码:
<?php class Alanhou_Weblog_Model_Resource_Blogpost extends Mage_Core_Model_Resource_Db_Abstract{ protected function _construct(){ $this->_init('weblog/blogpost','blogpost_id'); } }
同样的_init方法中前半段即个URL用于指定模型,而后半部分可以是任意可唯一标识数据表列的字段,大多数情况下我们会使用主键,刷新缓存,重新加载页面,会出现如下报错:
Can't retrieve entity config: weblog/blogpost
在为weblog/blogpost建模时,我们在告诉Magento模块分组是weblog,Entity是blogpost,例中继承了Mage_Core_Model_Resource_Db_Abstract,因而entity对应的是一张数据表,我们前面创建的数据表名为blog_post,所以要对config.xml进行如下修改来添加entity(models标签内):
<weblog_resource> <class>Alanhou_Weblog_Model_Resource</class> <entities> <blogpost> <table>blog_posts</table> </blogpost> </entities> </weblog_resource>
这样就成功添加了<entities />代码段也就为模型指定了数据表,清除缓存重新加载页面,如果一切正常的话将会在浏览器中输出如下数组:
Loading the blogpost with an ID of 1 array(5) { ["blogpost_id"]=> string(1) "1" ["title"]=> string(12) "My New Title" ["post"]=> string(19) "This is a blog post" ["date"]=> string(19) "2010-08-08 00:00:00" ["timestamp"]=> string(19) "2010-08-08 23:12:30" }
这样就成功的从数据库中读取了数据并完成了Model的配置。
Magento中所有的模型都继承自Varien_Object类,该类是Magento系统库的一部分,但不属于任何core模块,可以在lib/Varien/Object.php中找到这个对象。模型将数据存储在一个类型为protected的_data数组中,Varien_object类提供了一些可获取数据的方法,比如前面用到的getData()将返回一个带有键值对的数组,也可以传一个键值来获取指定字段:
$model->getData(); $model->getData('title');
该类中还有一个getOrigData方法,它将返回对象起初被实例化时的模型数据:
$model->getOrigData(); $model->getOrigData('title');
Vairen_Object还通过PHP的魔术方法__call组成了一些特殊方法,可以通过以get, set, unset或has开头的方法来get, set, unset或检查一个属性是否存在,这些关键词后采用驼峰法即首字母大写:
$model->getBlogpostId(); $model->setBlogpostId(25); $model->unsetBlogpostId(); if($model->hasBlogpostId()){...}
因此,建议所有数据表中的列名采用小写或下划线分隔单词的方式命名。
Magento中的增删改查
一提到数据库免不了会用到增删改查(英文称为CRUD,即Create, Read, Update, Delete),这些都是数据库中的基本功能,Magento的模型通过load, save和delete方法来实现这些功能。前面我们已经用到了load方法,在传入一个参数时,load方法会返回模型资源里指定列与传入值相匹配的记录。
$blogpost->load(1);
save方法可以对数据库进行INSERT插入新记录和UPDATE更新已有记录的操作,在我们的Index控制器添加如下方法:
public function createNewPostAction(){ $blogpost = Mage::getModel('weblog/blogpost'); $blogpost->setTitle('Code Post!'); $blogpost->setPost('This post was created from code!'); $blogpost->save(); echo 'post with ID '.$blogpost->getId().' created'; }
访问http://localhost/magento/weblog/index/createNewPost页面,些时在数据表中就会新增一行内容
我们同样在Index控制器中再添加一个方法用于编辑数据表内容:
public function editFirstPostAction(){ $blogpost = Mage::getModel('weblog/blogpost'); $blogpost->load(1); $blogpost->setTitle("The First Post"); $blogpost->save(); echo 'post edited'; }
访问http://localhost/magento/weblog/index/editFirstPost完成对数据表的修改
同样地我们也可以创建一个方法来删除行:
public function deleteFirstPostAction(){ $blogpost = Mage::getModel('weblog/blogpost'); $blogpost->load(1); $blogpost->delete(); echo 'post removed'; }
访问http://localhost/magento/weblog/index/deleteFirstPost即可完成对行的删除。
一个模型固然非常有用,但有时我们想要获取一系列的模型,每个模型类型都有一个特殊的集合对象,这些对象采用PHP的IteratorAggregate和Countable接口,也就是说可以传递给count函数并在foreach函数中使用。
关于集合我们会在后面进行全面的探讨,这里先讨论一下基本设置和使用。在Index控制器中添加如下方法
public function showAllBlogPostsAction(){ $posts = Mage::getModel('weblog/blogpost')->getCollection(); foreach($posts as $blogpost){ echo '<h3>'.$blogpost->getTitle().'</h3>'; echo nl2br($blogpost->getPost()); } }
访问http://localhost/magento/weblog/index/showAllBlogPosts页面,会提示如下报错:
Warning: include(Alanhou\Weblog\Model\Resource\Blogpost\Collection.php): failed to open stream: No such file or directory
这表示我们还需要添加一个类文件来定义Blogpost集合,每个模型都有一个protected类型的属性_resourceCollectionName,它包含一个用于标识集合的URI
protected '_resourceCollectionName' => string 'weblog/blogpost_collection'
默认情况下它与我们用于标识资源模型的URI相同,只是在最后面添加一段_collection。Magento把Collection看作资源的一部分,所以以上URI组合成如下类名
Alanhou_Weblog_Model_Resource_Blogpost_Collection
接下来在app/code/local/Alanhou/Weblog/Model/Resource/Blogpost/Collection.php中添加如下类
<?php class Alanhou_Weblog_Model_Resource_Blogpost_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract{ protected function _construct(){ $this->_init('weblog/blogpost'); } }
和其它类一样,我们需要使用传入weblog.blogpost来init我们的集合。重新访问http://localhost/magento/weblog/index/showAllBlogPosts就会得到文章信息。