原文链接
http://alanstorm.com/magento_models_orm
Models Tier的实现是任何一个MVC模式里重要的部分。它表示了应用的数据部分,并且大部分应用是不可能没有数据的。MagentoModels的作用更大,因为典型上来说他们包含了“Business Logic”,而在其他PHPMVC框架里,这些逻辑通常会被分配给Controller或者Helper。
虽然MVC的定义有些模糊,但是Model的定义更模糊。之前PHP开发者广为接受的MVC模型中,数据的存储通常是raw SQL声明或者SQL抽象层(如AdoDB)。开发者应该写queries,并且不应该对他们的创建的model考虑太多。
在2009,rawSQL是不太受欢迎了,但是很多PHP框架仍旧是以SQL为中心的。Model对象是作为抽象层,但在这个理念后面,开发者仍旧是写着SQL或者调用SQL的抽象方法来存取数据。
另外一些框架隐藏了SQL,采用了Object Relation Mapping(ORM)方法。在这种情况下,一个开发者严格的与对象打交道。我们设置属性,然后调用对象的save方法,数据就会自动写入数据库。一些ORM会试图把对象属性和数据库分开,另一些则要求用户明确他们的关系(例如抽象语言YAML)。这种方式里最有名的一种是ActiveRecord。
ORM的定义应该比较清楚了,但是正如其他计算机科学里的概念一样,ORM的严格定义这么多年来还是没有明确。这些就是本文之外的东西了。
Magento也采用了ORM模型。当ZendFramework SQL抽象可以利用,大部分的数据存储就可以通过MagentoModels,或者是我们自建models的内建模型来处理。Magento同时有高度的灵活性,抽象性,也带来了一定的迷惑性。
Magento Model剖析
大部分的MagentoModels可以被分为两类。基础的:ActiveRecord-like/one-object-one-table模型;EAV模型:EntityAttribute Value Model。同样还有Model集合。集合是存有一些单独MagentoModel实例的PHP对象。Varian小组实现了IteratorAggregate 和 Countable 的PHPStandard Library 的标准接口,可以让每个Model类型有它自建的collection类型。如果不熟悉PHP Standard Library,可以认为Model集合就是一个数组,同时还有一些方法和它绑定。
Magento模型没有任何SQL代码与数据库相连。相反,每个模型有两个ModelResources,(一个读,一个写),用以和数据库交互(通过读和写的适配器对象)。通过把逻辑Model和数据库交互代码的解耦,理论上来说,就可以为新的databaseplatform写新的resource class时,不必动Models。
我们将会创建一个基本的Magento Model。PHP MVC传统来说,我们会创建一个weblogpost。我们将会
1. 创建一个“Weblog” 模块
2. 为模块创建一个对应的数据库表
3. 增加Model—Blogpost config的Model信息
4. 增加Model—Blogpost config的ModelResource 信息
5. 增加Blogpost Model config的ReadAdapter
6. 增加Blogpost Model config的WriteAdapter
7. 增加Blogpost Model的php文件
8. 增加Blogpost Model的php文件
9. 实例化Model
创建Weblog Module
创建过程跟之前类似,略过不再废话。直接进入以“testModel”命名的IndexAction Controller设置route的步骤。
在XStarX/Weblog/etc/config.xml, 设置以下路径
然后在Action Controller里增加下面的方法
XStarX/Weblog/controllers/IndexController.php
classAlanstormdotcom_Weblog_IndexController extendsMage_Core_Controller_Front_Action {
public function testModelAction() {
echo 'Setup!';
}
}
清除Magento的缓存,加载下面的URL来保证每个设置的正确
http://localhost/magento/weblog/index/testModel
可以看到“Setup”在白色的背景上。
创建数据库表
Magento在系统上可以自动创建和改变数据库,但是作为说明还是还是手动建立数据表。
在MySQL命令行下,用下面的脚本创建数据库表
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`)
);
然后插入一条数据
INSERT INTO`blog_posts` VALUES (1,'My New Title','This is a blog post','2009-07-0100:00:00','2009-07-02 23:12:30');
全局配置和创建Model
创建Model需要在配置文件中做下面4件事情
1. 在我们的模块中Enabling Models
2. 在我们的模块中Enabling Model Resources
3. 给我们的ModelSource增加实体entity。对简单的Model来说,就是表名。
4. 为我们的specificModel Resource 明确读接口
5. 为我们的specificModel Resource 明确写接口
然后实例化Model时,就可以用
$model =Mage::getModel('weblog/blogpost');
获取model的URI的第一个部分是Model Group Name。因为Magento的_autoloads类,这个是模块的小写名字即可。URI的第二个部分,是Model的名字。
因此在config.xml里增加下面的XML
外部标签
虽然我们还没有完成,但是先看看发生了什么。如果清除cache,获取blogpostModel。在testModelAction里,改成下面的代码
public functiontestModelAction() {
$blogpost = Mage::getModel('weblog/blogpost');
echo get_class($blogpost);
}
重新加载页面。会得到一个异常(确保打开了开发者模式)
include(XStarX/Weblog/Model/Blogpost.php)[function.include]: failed to open stream: No such file or directory
要得到weblog/blogpost Model, 你告诉Magento要实例化类
XStarX_Weblog_Model_Blogpost
Magento试图用__autoload加载该模型,但是找不到文件。所以我们来创建函数
XStarX/Weblog/Model/Blogpost.php
class XStarX_Weblog_Model_Blogpostextends Mage_Core_Model_Abstract {
protected function _construct() {
$this->_init('weblog/blogpost');
}
}
重新加载页面,就会出现类的名字。
所有的Model都是继承自Mage_Core_Model_Abstract类。这个类强制你要自己实现_construct(与PHP默认的构造函数不同,__construct).
我们设置了Model。下面,我们需要设置Model Resource。ModelResource包含了直接和数据库对话的代码。在上一部分,我们在config里包含了下面的
Mage::getResourceModel(‘weblog/blogpost’);
Weblog是组名,blogpost是Model。方法是Mage::getResourceModel方法会用weblog/blogpostURI来查找全局config,然后获得
Weblog_mysql/blogpost
因此,如果通过这种方式,就意味着,和普通Model一样,resourcemodels是在XML config中同样的部分配置的。这可能会带来混淆。
然后来配置我们的资源,在
增加了
Packagename_Modulename_Model_Mysql4
一次我们有了配置好的资源,让我们试着加载一些Model data。修改action如下
public functiontestModelAction() {
$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);
}
清空cache,然后加载下面的url
http://localhost/magento/weblog/index/testModel/id/1
又会遇到下面的异常
Warning: include(Star/Weblog/Model/Mysql4/Blogpost.php)[function.include]: failed to open stream: No such file ....
正如你可能感觉到的,我们需要增加一个resource类到我们的模型。每个model都有自己的resource class。 在下列位置增加类
app/code/local/XStarX/Weblog/Model/Mysql4/Blogpost.php
class XStarX_Weblog_Model_Mysql4_Blogpostextends Mage_Core_Model_Mysql4_Abstract{
protected function _construct() {
$this->_init('weblog/blogpost', 'blogpost_id');
}
}
_init方法里的第一个参数是用来定位Model的URL。第二个参数是database的field,这个域用于区分其他column。在大多数情况下,这个应该是primary key。清除cache,重新加载,
Loading theblogpost with an ID of 1
array
empty
因此我们解决了exception,但是我们仍然没有读到数据。为什么?
每个Model group都有一个读 adapter,和一个写adapter。Magento容许Model使用默认的adapter,或者开发者使用自己的。无论哪种方式,我们需要告诉Magento。我们增加新的叫做
我们在
这些准备好后,清除cache,重新加载页面
Can't retrieveentity config: weblog/blogpost
又发生了一个新的异常。我们用Model URI weblog/blogpost, 我们告诉了Magento我们想要ModelGroup weblog,和blogpost实体。在继承Mage_Core_Model_Mysql4_Abstract的简单Models里,一个实体对应一个表。在这种情况下,我们上面创建的blog_post表。让我们把这个实体增加到XML配置文件里。
我们增加新的
清除缓存,重新加载页面
Loading theblogpost with an ID of 2
Loading theblogpost 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 Model的配置。
所有的Magento Models继承自Varien_Object类。这个类是Magento系统库的一部分,不是core module。你可以在下面的路径找到它
lib/Varien/Object.php
Magento Models存储数据在一个叫做protected data的属性了。Varian_Object类给了我们几个方法,这样我们可以用来获取data。你已经看到了getData, 它返回一个key/value对的数组。这个方法同样可以传入一个stringkey,来得到这个特定域的值。
$model->getData();
$model->getData(‘title’);
还有一个方法叫getOrigData,它返回对象初始话时的Modeldata(配合_origData方法)
$model->getOrigData();
$model->getOrigData(‘title’);
Varien_Object同样通过PHP的magic __call方法,实现了一些特殊的方法。你可以对任何属性进行get,set,unset,或者check操作。
$model->getBlogpostId();
$model->setBlogpostId(25);
$model->unsetBlogpostId();
if($model->hasBlogpostId()){...}
由于这个原因,我们最好把数据库的列名用小写来命名,并且在单词之间用下划线连接。
$id = $model[‘blogpost_id’];
$model[‘blogpost_id’] = 25;
June 1, 2011 magento可能在ArrayAccess 方法上有bug(但是未经验证)。比较Varien_Object的setData方法(用magic方法)
public function setData($key, $value=null) {
$this->_hasDataChanges = true;
if(is_array($key)) {
$this->_data =$key;
} else {
$this->_data[$key] =$value;
}
return $this;
}
下面是offsetSet
public function offsetSet($offset, $value) {
$this->_data[$offset] = $value;
}
在setData方法里,_hasDataChanges属性被置为真。如果这个标志是false,Magento不会执行Update Code。这个可能是性能优化,但是没有考虑到数组的存储。可能好的标志是核心小组成员喜欢的magicmethod。
如果我们创建了自己的Models,你可能要去重写offsetSet,然后再调用父类的,
public function offsetSet($offset, $value) {
$this->_hasDataChanges = true;
return parent::offsetSet($offset, $value);
}
下面是Varien_Object
/**
* Attribute getter (deprecated) *
* @param string $var
* @return mixed
*/
public function __get($var) {
$var = $this->_underscore($var);
return $this->getData($var);
}
/**
* Attribute setter (deprecated) *
* @param string $var
* @param mixed $value
*/
public function __set($var, $value) {
$var = $this->_underscore($var);
$this->setData($var, $value);
}
Magento模型支持CRUD基本的Create,Read,Update,和Delete功能,是通过load, save, 和delete方法实现的。你已经在上面的Action里见到了load方法。当传入一个单参数时,load方法返回一条记录,就是id=传入值的那条
$blogpost->load(1);
Save方法可以让我们INSERT一条新记录,也可以更新一条老记录。
public functioncreateNewPostAction() {
$blogpost = Mage::getModel('weblog/blogpost');
$blogpost->setTitle('Code Post!');
$blogpost->setPost('This post was created from code!');
$blogpost->save();
echo 'post created';
}
然后执行网址
http://localhost/magento/weblog/index/createNewPost
会发现多了几条记录。接着,增加一个方法
public functioneditFirstPostAction() {
$blogpost = Mage::getModel('weblog/blogpost');
$blogpost->load(1);
$blogpost->setTitle("The First post!");
$blogpost->save();
echo 'post edited';
}
最后,你可以使用类似的方式删除你的post
public functiondeleteFirstPostAction() {
$blogpost = Mage::getModel('weblog/blogpost');
$blogpost->load(1);
$blogpost->delete();
echo 'post removed';
}
单个的model很有用,但是有时候我们需要一串model。不是简单的返回一个Model的数组,每个MagentoModel类型有一个不同的collection object与之关联。这些对象实现了php的iteratorAggregate和countable接口,这意味这他们可以传给count函数,或者拿each来构造。
我们后续详细讨论Collection,现在先看看基本的。在Controller里加入下面的方法,
public functionshowAllBlogPostsAction() {
$posts = Mage::getModel('weblog/blogpost')->getCollection(); foreach($posts as $blog_post){
echo'
echo nl2br($blog_post->getPost());
}
}
载入下面的URL
http://localhost/magento/weblog/index/showAllBlogPosts
会得到一个类似的异常
Warning:include(Alanstormdotcom/Weblog/Model/Mysql4/Blogpost/Collection.php)[function.include]: failed to open stream
你应该不会感到意外吧。我们需要增加一个PHP 类文件,来定义Blogpost collection。每个模块有一个保护的属性叫_resourceCollectionName包含了URI来标志我们的集合
protected '_resourceCollectionName' =>string 'weblog/blogpost_collection'
在下面的位置增加文件
app/code/local/XStarX/Weblog/Model/Mysql4/Blogpost/Collection.php
class XStarX_Weblog_Model_Mysql4_Blogpost_Collectionextends Mage_Core_Model_Mysql4_Collection_Abstract {
protected function _construct() {
$this->_init('weblog/blogpost');
}
}
类似于其他类,我们需要用Model URI(weblog/blogpost)初始化我们的集合。重新加载Controller Action,你可以看到post信息。
重要提示
all basic MagentoModels inherit from Mage_Core_Model_Abstract. This isn’t 100% true. ThisAbstract method hasn’t existed for Magento’s entire life, and there are stillmany Models in the system that inherit directly fromVarien_Object.