閱讀739 返回首頁    go 阿裏雲 go 技術社區[雲棲]


magento 開發 -- 深入理解Magento第四章 – 模型和ORM基礎

第四章 – 模型和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如下
class Zhlmmc_Helloworld_BlogController extends Mage_Core_Controller_Front_Action {
    public function indexAction()         
    {
         echo 'Hello Blog';
    }
}

訪問以下URL
https://127.0.0.1/Magento/helloworld/blog
你應該看到“Hello Blog”輸出。

創建數據表

我們可以通過Magento自帶的方法創建或者修改數據庫,但是為了不引入過多新內容,我們暫且手工創建一張表。在你的數據庫中執行以下語句
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-01 00:00:00','2009-07-02 23:12:30');
這裏我們創建了一張名為“blog_posts”的表,並填充了一條數據。

創建模型

要設置一個模型一共有以下四個步驟

  1. 啟用模型
  2. 啟用資源模型
  3. 在資源模型中添加實體(Entity)。對於簡單的模型來說,實體就是數據表的名字
  4. 為資源模型設置讀、寫適配器

在進行這些步驟之前,我們先來看假設這些步驟已經做完了,我們怎麼用一個模型。在Magento中,我們用以下的方式來實例化一個模型
$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
<global>
    <!-- ... -->
    <models>
        <helloworld>
            <class>Zhlmmc_Helloworld_Model</class>
            <!-- 
            need to create our own resource, can't just
            use core_mysql4
            -->
            <resourceModel>helloworld_mysql4</resourceModel>
        </helloworld>   
    </models>
    <!-- ... -->
</global>

標簽<helloworld />就是組名,也應該和模塊名一致。<class />標簽的內容是基本類名,所有Helloworld模塊的模型都用這個基本類名,命名方式如下
Packagename_Modulename_Model
<resourceModel />標簽指明了這個模塊的模型要用哪個資源模型。這個標簽的內容是組名加上“mysql4”我們將在後麵詳細介紹資源模型。

現在讓我們來實例化一個模型看看,修改indexAction方法
public function indexAction() {
    $blogpost = Mage::getModel('helloworld/blogpost');
    echo get_class($blogpost);
}

清空Magento緩存,刷新頁麵,你應該看到一個類似這樣的異常(請先打開Magento的開發模式
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
class Zhlmmc_Helloworld_Model_Blogpost extends Mage_Core_Model_Abstract 
{
    protected function _construct()
    {
        $this->_init('helloworld/blogpost');
    }   
}

刷新頁麵,你應該看到頁麵上顯示“Zhlmmc_Helloworld_Model_Blogpost”。所有的模型都必須繼承“Mage_Core_Model_Abstract”類。這個抽象類強製你實現一個方法“_construct”(注意:這個不是PHP的構造行數“__construct”)。這個方法應該調用父類已經定義好的“_init”方法,參數是資源模型的URI,也就是我們要告訴模型使用哪個資源模型。我們將在解釋資源模型的時候再解釋這個URI。

啟用資源模型並添加實體

好了,我們設置好了模型,下麵我們要為模型設置資源模型。資源模型才是真正和數據庫對話的組件。在模型的配置中,有一段這樣的代碼
<resourceModel>helloworld_mysql4</resourceModel>
<resourceModel />的值將被用來實例化資源模型。我們不需要顯式的調用資源模型,但是當一個模型需要訪問數據庫的時候,Magento會自動實例化一個資源模型來使用。
Mage::getResourceModel('helloworld/blogpost');
這裏“helloworld/blogpost”就是我們給模型的“_init”傳入的參數。“helloworld”是組名,“blogpost”是模型的半類名。“Mage::getResourceModel”方法將以“helloworld/blogpost”為URI在全局配置中找到<resourceModel>標簽的值,在這裏是“helloworld_mysql4”。然後Magento會用URI“helloworld_mysql4/blogpost”去實例化資源模型類。實例化的過程和我們前麵講的模型的實例化是一樣的,所以我們也需要在config.xml中添加資源模型的聲明
<global>
    <!-- ... -->
    <models>        
        <!-- ... -->
        <helloworld_mysql4>
           <class>Zhlmmc_Helloworld_Model_Resource_Mysql4</class>
        </helloworld_mysql4>
    </models>
</global>

這裏我們可以看到,資源模型的聲明也是放在<models />下麵的。有點搞,但是也不必深究了,Magento就這麼定義的。<class />標簽的值是所有資源模型類的基本類名,命名方式如下
Packagename_Modulename_Model_Resource_Mysql4
好了,我們已經配置了資源模型,我們來試試裝載一些數據。修改indexAction如下
public function indexAction() {
    $params = $this->getRequest()->getParams();
    $blogpost = Mage::getModel('helloworld/blogpost');
    echo("Loading the blogpost with an ID of ".$params['id']."<br/>");
    $blogpost->load($params['id']);     
    $data = $blogpost->getData();
    var_dump($data);    
}

清空Magento緩存,訪問下麵的頁麵
https://127.0.0.1/Magento/helloworld/blog/index/id/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
class Zhlmmc_Helloworld_Model_Resource_Mysql4_Blogpost extends Mage_Core_Model_Mysql4_Abstract{
    protected function _construct()
    {
        $this->_init('helloworld/blogpost', 'blogpost_id');
    }   
}

這裏“_init”方法的第一個參數這個資源模型將要使用的數據表的URI,第二個參數是數據表中的列名。這個列的內容必須唯一,往往是數據表的主鍵。

為資源模型添加實體

刷新頁麵,你是不是得到下麵的異常?
Can't retrieve entity config: helloworld/blogpost
那是因為我們的資源文件現在還是一個空殼,並沒有和數據庫聯係起來。現在我們來把資源模型和我們的表聯係起來,修改config.xml如下
<global>
    <!-- ... -->
    <models>        
        <!-- ... -->
        <helloworld_mysql4>
           <class>Zhlmmc_Helloworld_Model_Resource_Mysql4</class>
           <entities>
               <blogpost>
                   <table>blog_posts</table>
               </blogpost>
           </entities>
        </helloworld_mysql4>
    </models>
</global>

我們前麵設置了資源模型使用的數據表的URI是“helloworld/blogpost”,那麼Magento會把“helloworld”作為組名,“blogpost”作為實體名,也就是<blogpost>。在Magento的簡單模型中(也就是繼承Mage_Core_Model_Mysql4_Abstract的模型),一個實體對應一張數據表。我們的數據表是“blog_posts”,所以這裏<table />標簽的內容就是“blog_posts”。

清空Magento緩存,再次刷新頁麵,你應該看到以下內容
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) "2009-07-01 00:00:00" ["timestamp"]=> string(19) "2009-07-02 23:12:30" }

設置讀寫適配器

在上麵的例子中,我們已經可以從數據庫中取數據了,但是我們卻沒有為資源模型設置讀寫適配器,怎麼回事呢?原因很簡單,那就是因為Magento會為沒有適配器的資源模型啟用默認適配器。我們也可以顯式的配置默認的適配器
<global>
    <!-- ... -->
    <resources>
        <helloworld_write>
            <connection>
                <use>default_write</use>
            </connection>
        </helloworld_write>
        <helloworld_read>
            <connection>
                <use>default_read</use>
            </connection>
        </helloworld_read>      
    </resources>        
</global>

在<resources />標簽下麵有兩個部分,一個讀,一個寫。標簽名字中的“hellworld”是我們定義的組名【譯者注:在資源模型的“_init”函數中傳入的數據表的URI “helloworld/blogpost”的前半部分就是適配器名字的前半部分】。從這裏我們也可以看出來一個資源組對應一對適配器。清空Magento緩存,刷新瀏覽器,你應該看到和剛才相同的頁麵。【譯者注:如果你去全局配置中找“core_read”你會發現“default_read”,然後是“default_setup”
<default_setup>
    <connection>
        <model>mysql4</model>
        <initStatements>SET NAMES utf8</initStatements>
        <type>pdo_mysql</type>
        <host>localhost</host>
        <username>root</username>
        <password>admin</password>
        <dbname>zend-magento</dbname>
        <active>1</active>
    </connection>
</default_setup>

這才是最終和數據庫連接的詳細信息。如果你再往下深究,你會發現全局配置有這麼一段
<resource>
    <connection>
        <types>
            <pdo_mysql>
                <class>Mage_Core_Model_Resource_Type_Db_Pdo_Mysql</class>
            </pdo_mysql>
        </types>
    </connection>
</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”的值。
$model->getData();
$model->getData('title');

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

“Varien_Object”也實現了一些PHP的特殊函數,比如神奇的“__call”。你可以對任何一個屬性調用“get, set, unset, has”方法
$model->getBlogpostId();
$model->setBlogpostId(25);
$model->unsetBlogpostId();
if($model->hasBlogpostId()){...}

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

也就是說,你會在Magento中同時看到這兩種技巧的使用。

Magento中的CRUD操作

Magento模型通過“load, save, delete”三個方法來支持基本的Create,Read,Update和Delete操作。我們在上麵已經使用過“load”方法了。這個方法的參數就是要裝在的數據記錄的“id”。
$blogpost->load(1);
“save”方法可以用來創建新數據或者修改已有數據。我們在BlogController.php中添加如下方法
public function createNewPostAction() {
    $blogpost = Mage::getModel('helloworld/blogpost');
    $blogpost->setTitle('Code Post!');
    $blogpost->setPost('This post was created from code!');
    $blogpost->save();
    echo 'post created';
}

訪問以下URL
https://127.0.0.1/Magento/helloworld/blog/createNewPost
現在你數據表中應該有兩條數據了。下麵來修改一條數據
public function editFirstPostAction() {
    $blogpost = Mage::getModel('helloworld/blogpost');
    $blogpost->load(1);
    $blogpost->setTitle("The First post!");
    $blogpost->save();
    echo 'post edited';
}

最後,我們來刪除一條數據
public function deleteFirstPostAction() {
    $blogpost = Mage::getModel('helloworld/blogpost');
    $blogpost->load(1);
    $blogpost->delete();
    echo 'post removed';
}

模型集合

上麵的例子我們隻是演示了對單個數據操作,現在我們來看看如何同時操作多條記錄。我們上麵已經講過,每個Magento的模型都有一個獨特的模型集合。這些模型集合實現了PHP的“IteratorAggregate”和“Countable”接口,也就是他們可以作為“count”函數的參數,並且可以在“for each”語句中使用。

現在讓我們來看看如何使用模型集合,在Blog控製器中添加如下方法
public function showAllBlogPostsAction() {
    $posts = Mage::getModel('helloworld/blogpost')->getCollection();
    foreach($posts as $blog_post){
        echo '<h3>'.$blog_post->getTitle().'</h3>';
        echo nl2br($blog_post->getPost());
    }
}

訪問如下URL
https://127.0.0.1/Magento/helloworld/blog/showAllBlogPosts
你應該看到以下異常
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。
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
class Zhlmmc_Helloworld_Model_Resource_Mysql4_Blogpost_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract {
    protected function _construct()
    {
            $this->_init('helloworld/blogpost');
    }
}

這裏的參數是模型的UR,用來I來初始化模型集合。刷新頁麵,你應該看到數據庫中的Blog都顯示出來了。

總結

首先我要恭喜你,到這裏你已經創建並配置了你的第一個Magento模型。在後麵的章節中我們將講解Magento的另外一種模型Entity Attribute Value Model。

在這章開始的時候,我撒了一個小謊。其實在Magento中,並不是所有的模型都繼承自“Mage_Core_Model_Abstract”。在Magento最初的版本中,這個抽象類並不存在。所以有很多模型是直接繼承自“Varien_Object”。不過這些並不影響我們創建Magento模型,了解一下就可以了,方便閱讀Magento的代碼。

源文:https://www.zhlmmc.com/?p=659

最後更新:2017-04-02 05:21:04

  上一篇:go 可伸縮性最佳實踐:來自eBay的經驗
  下一篇:go 大型門戶網站的可伸縮性架構設計準則