天天看點

第四章 – 模型和ORM基礎 深入了解Magento

深入了解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代碼  

第四章 – 模型和ORM基礎 深入了解Magento
  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代碼  

第四章 – 模型和ORM基礎 深入了解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中,我們用以下的方式來執行個體化一個模型

Php代碼  

第四章 – 模型和ORM基礎 深入了解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

Xml代碼  

第四章 – 模型和ORM基礎 深入了解Magento
  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代碼  

第四章 – 模型和ORM基礎 深入了解Magento
  1. public function indexAction() {  
  2.     $blogpost = Mage::getModel('helloworld/blogpost');  
  3.     echo get_class($blogpost);  
  4. }  

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

Php代碼  

第四章 – 模型和ORM基礎 深入了解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

Php代碼  

第四章 – 模型和ORM基礎 深入了解Magento
  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代碼  

第四章 – 模型和ORM基礎 深入了解Magento
  1. <resourceModel>helloworld_mysql4</resourceModel>  

<resourceModel />的值将被用來執行個體化資源模型。我們不需要顯式的調用資源模型,但是當一個模型需要通路資料庫的時候,Magento會自動執行個體化一個資源模型來使用。

Php代碼  

第四章 – 模型和ORM基礎 深入了解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中添加資源模型的聲明

Xml代碼  

第四章 – 模型和ORM基礎 深入了解Magento
  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代碼  

第四章 – 模型和ORM基礎 深入了解Magento
  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代碼  

第四章 – 模型和ORM基礎 深入了解Magento
  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代碼  

第四章 – 模型和ORM基礎 深入了解Magento
  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代碼  

第四章 – 模型和ORM基礎 深入了解Magento
  1. Can't retrieve entity config: helloworld/blogpost  

那是因為我們的資源檔案現在還是一個空殼,并沒有和資料庫聯系起來。現在我們來把資源模型和我們的表聯系起來,修改config.xml如下

Xml代碼  

第四章 – 模型和ORM基礎 深入了解Magento
  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代碼  

第四章 – 模型和ORM基礎 深入了解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會為沒有擴充卡的資源模型啟用預設擴充卡。我們也可以顯式的配置預設的擴充卡

Xml代碼  

第四章 – 模型和ORM基礎 深入了解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”

Xml代碼  

第四章 – 模型和ORM基礎 深入了解Magento
  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代碼  

第四章 – 模型和ORM基礎 深入了解Magento
  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代碼  

第四章 – 模型和ORM基礎 深入了解Magento
  1. $model->getData();  
  2. $model->getData('title');  

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

Php代碼  

第四章 – 模型和ORM基礎 深入了解Magento
  1. $model->getOrigData();  
  2. $model->getOrigData('title');  

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

Php代碼  

第四章 – 模型和ORM基礎 深入了解Magento
  1. $model->getBlogpostId();  
  2. $model->setBlogpostId(25);  
  3. $model->unsetBlogpostId();  
  4. if($model->hasBlogpostId()){...}  

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

Php代碼  

第四章 – 模型和ORM基礎 深入了解Magento
  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代碼  

第四章 – 模型和ORM基礎 深入了解Magento
  1. $blogpost->load(1);  

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

Php代碼  

第四章 – 模型和ORM基礎 深入了解Magento
  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代碼  

第四章 – 模型和ORM基礎 深入了解Magento
  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代碼  

第四章 – 模型和ORM基礎 深入了解Magento
  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代碼  

第四章 – 模型和ORM基礎 深入了解Magento
  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代碼  

第四章 – 模型和ORM基礎 深入了解Magento
  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代碼  

第四章 – 模型和ORM基礎 深入了解Magento
  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代碼  

第四章 – 模型和ORM基礎 深入了解Magento
  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