第四章 – 模型和ORM基礎

深入理解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如下

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自帶的方法創建或者修改數據庫,但是爲了不引入過多新內容,我們暫且手工創建一張表。在你的數據庫中執行以下語句

Sql代碼  收藏代碼
  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.   `timestamptimestamp 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中,我們用以下的方式來實例化一個模型

Php代碼  收藏代碼
  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

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方法

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

清空Magento緩存,刷新頁面,你應該看到一個類似這樣的異常(請先打開Magento的開發模式 )

Php代碼  收藏代碼
  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

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。


啓用資源模型並添加實體

好了,我們設置好了模型,下面我們要爲模型設置資源模型。資源模型纔是真正和數據庫對話的組件。在模型的配置中,有一段這樣的代碼

Xml代碼  收藏代碼
  1. <resourceModel>helloworld_mysql4</resourceModel>  
 

<resourceModel />的值將被用來實例化資源模型。我們不需要顯式的調用資源模型,但是當一個模型需要訪問數據庫的時候,Magento會自動實例化一個資源模型來使用。

Php代碼  收藏代碼
  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中添加資源模型的聲明

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如下

Php代碼  收藏代碼
  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


你應該看到一個類似下面這樣的異常

Php代碼  收藏代碼
  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

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,第二個參數是數據表中的列名。這個列的內容必須唯一,往往是數據表的主鍵。


爲資源模型添加實體

刷新頁面,你是不是得到下面的異常?

Php代碼  收藏代碼
  1. Can't retrieve entity config: helloworld/blogpost  

 

那是因爲我們的資源文件現在還是一個空殼,並沒有和數據庫聯繫起來。現在我們來把資源模型和我們的表聯繫起來,修改config.xml如下

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緩存,再次刷新頁面,你應該看到以下內容

Php代碼  收藏代碼
  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會爲沒有適配器的資源模型啓用默認適配器。我們也可以顯式的配置默認的適配器

Xml代碼  收藏代碼
  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”

Xml代碼  收藏代碼
  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>  
 

這纔是最終和數據庫連接的詳細信息。如果你再往下深究,你會發現全局配置有這麼一段

Xml代碼  收藏代碼
  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”的值。

Php代碼  收藏代碼
  1. $model->getData();  
  2. $model->getData('title');  
 

還有一個方法是“getOrigData”,這個方法會返回模型第一次被賦予的值。【譯者注:因爲模型在初始化以後,值可以被修改,這個方法就是拿到那個最原始的值】

Php代碼  收藏代碼
  1. $model->getOrigData();  
  2. $model->getOrigData('title');  
 

“Varien_Object”也實現了一些PHP的特殊函數,比如神奇的“__call”。你可以對任何一個屬性調用“get, set, unset, has”方法

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

這裏的方法名中的屬性名字符合“camelcase”命名規則 【譯 者注:簡單的說就是Java的命名規則,每個單詞的第一個字母大寫,第一個字母可以大寫也可以小寫】。爲了有效的利用這些方便的方法,我們在定義數據表列 名的時候要用小寫,並用下劃線作爲分隔符,比如“blogpost_id”。在最近的Magento版本中,這個規則已經被弱化,爲了實現PHP的 “ArrayAccess”接口

Php代碼  收藏代碼
  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”。

Php代碼  收藏代碼
  1. $blogpost->load(1);  
 

“save”方法可以用來創建新數據或者修改已有數據。我們在BlogController.php中添加如下方法

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


現在你數據表中應該有兩條數據了。下面來修改一條數據

Php代碼  收藏代碼
  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. }  
 

最後,我們來刪除一條數據

Php代碼  收藏代碼
  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控制器中添加如下方法

Php代碼  收藏代碼
  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


你應該看到以下異常

Php代碼  收藏代碼
  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。

Php代碼  收藏代碼
  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

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章