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


magento 開發 -- 深入理解Magento第六章 – 高級Magento模型

 

第六章 – 高級Magento模型

我們講過Magento有兩種模型,簡單模型和EAV(Entity Attribute Value)模型。上一章我們講過所有的Magento模型都是繼承自Mage_Core_Model_Abstract / Varien_Object。簡單模型和EAV模型的區別在於資源模型(Model Resource)。雖然所有的資源模型都最終繼承“Mage_Core_Model_Resrouce_Abstract”,但是簡單模型是直接繼承“Mage_Core_Model_Mysql4_Abstract”,而EAV模型是直接繼承“Mage_Eav_Model_Entity_Abstract”。

Magento這麼做是由它的道理的。對於大部分開發人員或者用戶來說,他們隻需要知道一係列的方法能夠操作模型,獲得數據,數據到底是如何存儲的並不是很重要。

什麼是EAV模型?

Wikipedia是這麼定義的:
    EAV(Entity-Attribute-Value)模型,也作Object-Attribute-Value模型或者開放模型是一種數據模型。這種數據模型常常用在一個對象的屬性數目不是一定的情況下。在數學上,這種模型稱為鬆散矩陣。

換一種方式理解,EAV模型就是數據表的一種泛化。在傳統的數據庫中,數據表的列的數量是一定的
+——————+
| products         | 
+——————+
| product_id       | 
| name             | 
| price            | 
| etc..            | 
+——————+
+————+—————-+——————+———+
| product_id | name           | price            | etc…  |
+————+—————-+——————+———+
| 1          | Widget A       | 11.34            | etc…  |
+————+—————-+——————+———+
| 2          | Dongle B       | 6.34             | etc…  |
+————+—————-+——————+———+

在上麵這張表中,每一個商品都有名稱,價格等等。

在EAV模型中,每一個模型都有不同的屬性。這對於電子商務的應用來說是很合適的。比如說一個網店可以賣筆記本,擁有CPU速度,顏色,內存等屬性,但是網店也可以賣衣服,有顏色屬性,但是沒有CPU速度。即使是賣衣服的網店,也有上衣和褲子之分,它們的屬性也是不一樣的。

有很多開源的或者商業的數據庫是默認使用EAV模型的。但是一般的網站托管平台不提供這些數據庫。所以Varien開發了一套基於PHP和MySQL的EAV係統。換句話說,它們在傳統的關係型數據庫上麵開發了一套EAV數據庫係統。

在使用的時候,EAV模型的屬性是會分布在不同的MySQL數據表中。

上麵的這張圖是Magento中關於“catalog_product”的表。每一個產品都是“catalog_product_entity”中的一行。Magento係統中所有的屬性(不僅僅是商品)都存放在“eav_attribute”表中,而屬性的值都放在類似下麵的表中“catalog_product_entity_attribute_varchar”, “catalog_product_entity_attribute_decimal”, “catalog_product_entity_attribute_etc”。【譯者注:如果你仔細觀察上麵這幅數據表結構圖,你會發現明顯少了一張表,和“entity_type”有關。因為這裏有“entity_type_id”出現,但卻沒有定義這個屬性的表。這個表在Magneto中叫做“eav_entity_type”。由於EAV模型中所有的模型數據都混在一套數據表中了,實體類型(entity_type)就是用來把不同的模型區別開來的屬性。假如我們要找出係統中所有的產品數據,那麼Magento先通過“eav_entity_type”表獲得產品模型的“entity_type_id”,然後再通過上麵這幅圖的關係來拿到所有的數據。

在EAV係統下麵,當你需要添加一個屬性的時候,隻需要在“eav_attribute”表中添加一行就行了。而傳統的關係型數據庫則需要修改數據表調用“ALTER TABLE”語句,複雜而且有風險。EAV模型的缺點是你不能通過一個簡單的SQL語句就獲得一個模型的所有屬性。你往往需要調用多個SQL或者一個SQL包幹了多個join語句。

實戰EAV模型

我們已經介紹了EAV是怎麼工作的了。下麵我們要通過一個例子來說明要在Magento中創建一個EAV模型所需要的步驟。這部分內容大概是Magento中最令人頭疼的部分,95%的Magento用戶都不會和這些代碼打交道,但是理解EAV模型的原理能夠幫助你更好的理解Magento的代碼和架構。

因為EAV模型的內容太多了,所以我假設你已經熟悉了前幾章的內容,包括Magento MVC,組類名等等。在這一章我不會再重複這些內容。

EAV形式的Hello World

我們將為Hello World模塊創建另外一個模型,使用EAV形式的資源模型。首先我們為模塊創建一個新的模型叫做“Eavblogpost”。記住,簡單模型和EAV模型的區別是資源模型,所以我們創建一個模型的基本步驟是一樣的。
<global>
    <!– … –>
    <models>    
        <!– … –>
        <helloworld-eav>
           <class>Zhlmmc_Helloworld_Model</class>
           <resourceModel>helloworld-eav_mysql4</resourceModel>
        </helloworld-eav>
        <!– … –> 
    </models>
    <!– … –>
</global>

我想我不說你也應該知道,我們要創建一個新的模型文件。由於PHP 5.3和命名空間(namespaces)還沒有被廣泛采用,Magento中的類名仍然和文件的物理路徑相關。這就導致了很多時候不知道一個URI所對應的類究竟該放在什麼文件夾下麵。我發現我們可以利用Magento的異常信息來直接得到一個類的路徑。比如,這裏我們先不創建模型類,先來修改BlogController來直接使用模型類,這樣Magento就會報錯說找不到模型類,並給出路徑
public function eavReadAction(){
    $eavModel = Mage::getModel('helloworld-eav/eavblogpost');
    echo get_class($eavModel)."<br/>";
}

清空Magento緩存,訪問以下URL
https://127.0.0.1/Magento/helloworld/blog/eavRead
跟預計的一樣,你應該得到以下異常
Warning: include(Zhlmmc/Helloworld/Model/Eavblogpost.php) [function.include]: failed to open stream: No such file or directory
所以我們應該創建如下文件
File: app/code/local/Zhlmmc/Helloworld/Model/Eavblogpost.php
class Zhlmmc_Helloworld_Model_Eavblogpost extends Mage_Core_Model_Abstract 
{
    protected function _construct()
    {
        $this->_init('helloworld-eav/blogpost');
    }   
}

刷新頁麵,你應該看到下麵的輸出
Zhlmmc_Helloworld_Model_Eavblogpost
下麵我們來創建資源模型。先定義資源模型
<helloworld-eav_mysql4>
    <class>Zhlmmc_Helloworld_Model_Resource_Eav_Mysql4</class>               
    <entities>  
        <blogpost>   
            <table>eavblog_posts</table>    
        </blogpost>  
    </entities>
</helloworld-eav_mysql4>

這裏的標簽名字和我們上麵定義的模型的<resourceMode />是一致的。<entities />的定義和上一章是一樣的。下麵的適配器的定義
<resources>
    <!– … ->
    <helloworld-eav_write>
        <connection>
            <use>default_write</use>
        </connection>
    </helloworld-eav_write>
    <helloworld-eav_read>
        <connection>
            <use>default_read</use>
        </connection>
    </helloworld-eav_read>      
</resources>

然後再次利用Magento的異常,先修改“eavReadAction”
public function eavReadAction(){
    $eavModel = Mage::getModel('helloworld-eav/eavblogpost');
    $params = $this->getRequest()->getParams();
    echo("Loading the blogpost with an ID of ".$params['id']."<br/>");
    $eavModel->load($params['id']);
    $data = $eavModel->getData();
    var_dump($data);
}

清空Magento緩存,訪問URL
https://127.0.0.1/Magento/helloworld/blog/eavRead/id/1
你應該看到如下異常
Warning: include(Zhlmmc/Helloworld/Model/Resource/Eav/Mysql4/Blogpost.php) [function.include]: failed to open stream: No such file or directory
所以我們創建相應的資源模型類
File: app/code/local/Zhlmmc/Helloworld/Model/Resource/Eav/Mysql4/Blogpost.php
class Zhlmmc_Helloworld_Model_Resource_Eav_Mysql4_Blogpost extends Mage_Eav_Model_Entity_Abstract
{   
    public function _construct()
    {
        $resource = Mage::getSingleton('core/resource');
        $this->setType('helloworld_eavblogpost');
        $this->setConnection(
            $resource->getConnection('helloworld-eav_read'),
            $resource->getConnection('helloworld-eav_write')
             );
    }
}

這個類和簡單的資源模型就不一樣。首先,我們這裏繼承的是“Mage_Eav_Model_Entity_Abstract”。其次,我們沒有調用“_init”方法。在EAV模型中我們需要自己來完成資源模型初始化的過程,包括,告訴資源模型使用哪個適配器,以及實體類型(entity_type)。刷新URL,你應該看到如下異常
Invalid entity_type specified: helloworld_eavblogpost
根據我們上文所講的內容,那這個異常的原因很明顯,那就是“eav_entity_type”表中,沒有需要的“helloworld_eavblogpost”的數據。這裏的“helloworld_eavblogpost”就是我們“setType”的參數。讓我們來看一下這張表長什麼樣
mysql> select * from eav_entity_type/G
*************************** 1. row ***************************
             entity_type_id: 1
           entity_type_code: customer
               entity_model: customer/customer
            attribute_model: 
               entity_table: customer/entity
         value_table_prefix: 
            entity_id_field: 
            is_data_sharing: 1
           data_sharing_key: default
   default_attribute_set_id: 1
            increment_model: eav/entity_increment_numeric
        increment_per_store: 0
       increment_pad_length: 8
         increment_pad_char: 0
 additional_attribute_table: customer/eav_attribute
entity_attribute_collection: customer/attribute_collection
*************************** 2. row ***************************
             entity_type_id: 2
           entity_type_code: customer_address
               entity_model: customer/customer_address
            attribute_model: 
               entity_table: customer/address_entity
         value_table_prefix: 
            entity_id_field: 
            is_data_sharing: 1
           data_sharing_key: default
   default_attribute_set_id: 2
            increment_model: 
        increment_per_store: 0
       increment_pad_length: 8
         increment_pad_char: 0
 additional_attribute_table: customer/eav_attribute
entity_attribute_collection: customer/attribute_collection

正如我們前麵講過的,這張表包含了所有係統中的實體類型。我們的參數“helloworld_eavblogpost”就是實體類型的值,對應數據表列“entity_type_code”。

係統和應用程序

這一章講的內容是Magento最重要的一個概念,也是很多人覺得頭疼的概念。拿電腦來做比方。操作係統,比如Mac OS X,Windows,Linux等等,是軟件係統,而瀏覽器,比如FIrefox,Safari,IE等等是應用程序。Magento首先是一個係統,其次才是一個應用程序。你可以在Magento係統之上創建一個電子商務應用。令人感到困惑的是Magento的代碼在很多地方是以很原始的方式暴露給應用程序的。EAV係統的配置和你網店的數據存放在統一數據庫中就是一個例子。

隨著你越來越深入Magento,你需要把Magento當作老式的 IBM 650 機器。也就是說,你必須對Magento有很深的了解才能對它運用自如。【譯者注:這一段和上下文沒什麼關係,大概是作者有感而發】

創建資源配置

從理論上講,你可以手動的在數據庫中插入數據,讓我們的EAV模型工作,但我還是不建議你這麼做。所幸的是,Magento提供了一個特殊的資源配置類,包含了一些有用的方法能自動的創建一些數據,使得係統能工作。

我們先添加資源配置
<resources>
  <!– … –>
    <helloworld-eav_setup>
        <setup>
            <module>Zhlmmc_Helloworld</module>
            <class>Zhlmmc_Helloworld_Entity_Setup</class>
        </setup>
        <connection>
            <use>core_setup</use>
        </connection>
    </helloworld-eav_setup>   
  <!– … –>
</resources>

創建資源配置類文件
File: app/code/local/Zhlmmc/Helloworld/Model/Entity/Setup.php
class Zhlmmc_Helloworld_Model_Entity_Setup extends Mage_Eav_Model_Entity_Setup {
}

請注意,這裏我們繼承的父類是“Mage_Eav_Model_Entity_Setup”。最後,我們來創建安裝腳本。如果你不熟悉這部分內容,請你參考前麵章節的內容。
File: app/code/local/Zhlmmc/Helloworld/sql/helloworld-eav_setup/mysql4-install-0.1.0.php
<?php   
$installer = $this;
throw new Exception("This is an exception to stop the installer from completing");
?>

清空Magento緩存,訪問任何頁麵,你應該看到以上異常。如果你沒有看到異常,那說明你哪裏配置錯了。

請注意:我們將一步一步的創建安裝腳本。如果你閱讀了前麵的章節,你應該知道我們必須刪除“core_resource”數據表中的相應數據才能使得安裝腳本重新運行。所以在我們下麵的例子中,當我們修改了安裝腳本,我們都默認會刪除“core_resource”表中的數據。正常使用Magento的時候我們不需要這樣做的,教程中的例子是極端情況。

添加實體類型

首先我們修改安裝腳本如下
$installer = $this;
$installer->addEntityType('helloworld_eavblogpost',Array(
//entity_mode is the URL you'd pass into a Mage::getModel() call
'entity_model'          =>'helloworld-eav/eavblogpost',
//blank for now
'attribute_model'       =>'',
//table refers to the resource URI helloworld-eav/blogpost
//<helloworld-eav_mysql4>…<blogpost><table>eavblog_posts</table>
'table'         =>'helloworld-eav/blogpost',
//blank for now, but can also be eav/entity_increment_numeric
'increment_model'       =>'',
//appears that this needs to be/can be above "1" if we're using eav/entity_increment_numeric
'increment_per_store'   =>'0'
));

我們調用了資源配置對象的“addEntityType”方法。這個方法的參數是實體類型(helloworld_eavblogpost)還有和這個類型相關的參數。當你運行這個腳本以後,你會發現“eav_attribute_group”,“eav_attributeset”還有“eav_entity_type”數據表中有了新的數據。訪問以下URL
https://127.0.0.1/Magento/helloworld/blog/eavRead/id/1
你應該看到以下異常
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'zend-magento.eavblog_posts' doesn't exist

創建數據表

我們已經告訴Magento我們的實體類型。接下來,我們要創建用來存儲數據的數據表,並配置係統讓Magento知道我們要用這些表。

如果你研究過Magento核心模塊的資源配置腳本的話,比如core/Mage/CatalogInventory的配置腳本,你會看到很多用來創建數據表的SQL語句。所幸的是,我們已經不必要這樣做了。Magento提供的資源配置類有一個方法“createEntityTables”。我們可以用這個方法來創建我們需要的數據表。同時這個方法也會在Magento的係統數據表中添加相應的配置數據。
$installer->createEntityTables(
    $this->getTable('helloworld-eav/blogpost')
);

“createEntityTables”有兩個參數。第一個參數是基礎表名(base table name)。第二個參數是一係列選項。我們這裏忽略了第二個參數,這些參數都是一些高級配置,超出了我們討論的範圍。在運行了上述腳本以後,你會發現數據庫中添加了如下數據表
eavblog_posts
eavblog_posts_datetime
eavblog_posts_decimal
eavblog_posts_int
eavblog_posts_text
eavblog_posts_varchar

同時,你會發現在“eav_attribute_set”表中多了一條數據
mysql> select * from eav_attribute_set order by attribute_set_id DESC LIMIT 1 /G
*************************** 1. row ***************************
  attribute_set_id: 63
    entity_type_id: 31
attribute_set_name: Default
        sort_order: 3

清空Magento緩存,重新訪問如下URL
https://127.0.0.1/Magento/helloworld/blog/eavRead/id/1
你應該看到以下輸出
Loading the blogpost with an ID of 1
array(0) { }

添加屬性

創建資源配置的最後一步是告訴Magento我們的模型有哪些屬性。這就和為單獨的數據表添加列是一樣的。【譯者注:我們上麵的輸出是空的就是因為我們雖然創建了EAV數據表,但是卻沒有創建EAV屬性,就像創建了一張沒有任何列的數據表,當然是空的。】和上麵的步驟一樣,Magento的資源配置類提供了相應的幫助函數,“installEntities”和“getDefaultEntities”。

我們之前所做的是告訴Magento,我們創建了一個實體類型(Entity Type),而現在,我們要配置這個實體類型使它能夠和我們的模型相符合。這個方法名字有點搞“installEntities”,其實我們要做的是配置這個實體。修改類“Zhlmmc_Helloworld_Model_Setup_Entity_Setup”
class Zhlmmc_Helloworld_Model_Setup_Entity_Setup extends Mage_Eav_Model_Entity_Setup {
  public function getDefaultEntities()
    {           
        return array (
            'helloworld_eavblogpost' => array(
                'entity_model'      => 'helloworld-eav/eavblogpost',
                'attribute_model'   => '',
                'table'             => 'helloworld-eav/blogpost',
                'attributes'        => array(
                    'title' => array(
                        //the EAV attribute type, NOT a mysql varchar
                        'type'              => 'varchar',
                        'backend'           => '',
                        'frontend'          => '',
                        'label'             => 'Title',
                        'input'             => 'text',
                        'class'             => '',
                        'source'            => '',
                        // store scope == 0
                        // global scope == 1
                        // website scope == 2
                        'global'            => 0,
                        'visible'           => true,
                        'required'          => true,
                        'user_defined'      => true,
                        'default'           => '',
                        'searchable'        => false,
                        'filterable'        => false,
                        'comparable'        => false,
                        'visible_on_front'  => false,
                        'unique'            => false,
                    ),
                ),
            )
        );
    }
}

這裏我們構建了一個數組,數組的元素是“key/value”對。“key”就是實體類型的名字(以下代碼參數是一樣的“$installer->addEntityType('helloworld_eavblogpost',…)”),“value”是一個數組,用來描述這個實體類型。“value”數組的元素大部分你應該都見過,就不多解釋了。這裏要關注的是“attribute”元素,這個元素的值又是一個數組。這個數組的內容就是我們定義的實體類型的屬性,相當於普通數據表的列,比如這裏的“title”。很可惜,我無法完整解釋用來描述一個屬性的數組的內容。在這裏,我們隻要知道“type”就是這個屬性的數據類型“varchar”。也就是說,這個屬性的值將會被保存到“eavblog_posts_varchar”數據表中。其他的很多元素都是和Magento的後台管理有關。Magento很多地方的UI是由模型控製的,很多這些參數都是用來控製UI顯示和係統設置。這樣做的優點是靈活性提高,但是缺點是這些內容對於外部開發者都是不透明的。【譯者注:我們是可以在這個函數中返回多個實體類型的。如果返回多個實體類型,那就說明模塊擁有多個模型。】

順便說一下,Magento選擇使用數組嵌套數組的形式來表示實體類型的屬性很奇怪。因為Magento整個架構是非常麵向對象的。這裏的數據結構和係統的其他部分很不一樣。

接下來我們需要修改安裝腳本,添加如下代碼
$installer->installEntities();
“installEntities”會調用“getDefaultEntities”方法來獲取將要被配置的屬性。當然你也可以把屬性直接作為參數傳給“installEntities”,但是我覺得還是按照Magento的習慣來比較好。在調用“installEntitis”以後,Magento會做下麵兩件事

  1. 在“eav_attribute”表中添加“title”屬性
  2. 在“eav_entity_attribute”表中添加一行

清空Magento緩存,刷新頁麵,你應該看到如下異常
SQLSTATE[23000]: Integrity constraint violation: 1217 Cannot delete or update a parent row: a foreign key constraint fails 
那是因為我們之前已經調用過一次“createEntityTables”,再次調用的時候Magento會嚐試先刪除數據表,然後再創建。但是刪除的時候Magento沒有考慮到外鍵的關係,先嚐試刪除了主表,所以就有了以上異常。為了簡化教程的例子,我們暫時把“createEntityTables”語句刪了。再次刷新頁麵,你應該看到正常的輸出。

給EAV模型添加數據

到這裏為止,我們的EAV模型已經創建好了,下麵我們來為模型添加一些數據。在BlogController中添加以下方法
public function eavPopulateEntriesAction() {
    for($i=0;$i<10;$i++) {    
        $weblog2 = Mage::getModel('helloworld-eav/eavblogpost');
        $weblog2->setTitle('This is a test '.$i);
        $weblog2->save();
    }    
    echo 'Done';
}
public function eavShowcollectionAction() {
    $weblog2 = Mage::getModel('helloworld-eav/eavblogpost');
    $entries = $weblog2->getCollection()->addAttributeToSelect('title');        
    $entries->load();
    foreach($entries as $entry)
    {
        // var_dump($entry->getData());
        echo '<h1>'.$entry->getTitle().'</h1>';
    }
    echo '<br>Done<br>';
}

記得添加模型集合
class Zhlmmc_Helloworld_Model_Resource_Eav_Mysql4_Blogpost_Collection extends Mage_Eav_Model_Entity_Collection_Abstract
{
    protected function _construct()
    {
        $this->_init('helloworld-eav/eavblogpost', 'helloworld-eav/blogpost');
    }
}

訪問以下URL
https://127.0.0.1/Magento/helloworld/blog/eavPopulateEntries
你應該看到正確的輸出。細心一點的話你應該發現這裏有兩點比較特殊。第一,“$weblog2->getCollection()->addAttributeToSelect('title')”,這裏的“title”是幹什麼的?因為EAV模型在數據庫層麵比較複雜,一個簡單的查詢都需要好多個SQL才能完成。所以在查詢的時候你需要指明你想找什麼,這樣可以節省係統資源。不過你也可以傳入“*”,表示查找所有數據。第二,為什麼“$this->_init”有兩個參數?在我們以前的章節中,簡單模型的模型集合初始化的時候隻需要傳入模型的URI就可以了,為什麼這裏要兩個參數呢?其實如果你仔細看了模型集合抽象類的代碼的話,你會發現這樣一段
if (is_null($resourceModel)) {
    $resourceModel = $model;
}

所以其實是需要模型的URI和資源模型的URI,但是由於我們前麵章節的例子,這兩個URI是一樣的,所以省略了第二個參數。而這裏,資源模型的URI和模型的URI是不一樣的,所以不能省略。

總結

到這裏,你應該對Magento整個係統的運作有所了解了。起碼下一次你看到網店裏麵的某個商品部顯示了,或者什麼屬性不對了,你知道去哪裏找問題。除了本章介紹的內容以外EAV模型還有很多東西可以學習。下麵是我打算在以後的文章中介紹的一些內容

  1. EAV屬性:EAV模型的屬性類型不局限於datatime, decimal, int, text和varchar。你可以創建自定義的數據類型。
  2. 集合篩選:對EAV模型的數據進行篩選不是看起來的那麼簡單,特別是當屬性是自定義類型的情況下,我們需要在集合裝載之前調用“addAttributeToFilter”方法。
  3. Magento EAV模型繼承:Magento在基本的EAV模型之上又創建了模型的繼承關係,這些繼承關係可以和網店的功能直接相關,也可以優化EAV模型的查詢。

毫無疑問,EAV模型是Magento係統中最複雜的部分。不過你要始終相信一點,不管多複雜,它也就是程序。從哲學角度來講,任何事物的產生都有特定的理由,你隻需要搞清楚為什麼。

 

 

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

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

  上一篇:go magento -- 新聞訂閱(Newsletter)使用詳細介紹
  下一篇:go 寫的一個inter類模仿ruby整數的行為