magento 开发笔记6

原文链接

http://alanstorm.com/magento_advanced_orm_entity_attribute_value_part_1


Magento ORM:Entity Attribute Value

之前我们聊到的是简单模型,现在进入了另外一种model,就是EAV。

所有的Magento Model都是继承自Mage_Core_Model_Abstract/Varien_Object。Model Resource决定了是简单Model还是EAV Model。而所有的resource继承Mage_Core_Model_Resource_Abstract类,简单model继承自Mage_Core_Model_Mysql4_Abstract, 而EAV model则是继承自Mage_Eav_Model_Entity_Abstract

作为一个业务逻辑开发者,其实不想去关心存储究竟用的是什么,其实只要能获取数据,执行方法即可。

EAV定义

Wiki上的定义如下

Entity-Attribute-Value model (EAV), also known as object-attribute-valuemodel and open schema is a data model that is used in circumstances where thenumber of attributes (properties, parameters) that can be used to describe athing (an “entity” or “object”) is potentially very vast, but the number thatwill actually apply to a given entity is relatively modest. In mathematics,this model is known as a sparse matrix.

 

简而言之就是熟悉作为entity或者object。有个比喻是,“EAV 是应用到数据库的表策略”。在传统的数据库中

+------------------+

 | products         | 

+------------------+

 | product_id      | 

 | name               |

 | price                | 

 | etc..                 | 

+------------------+ 

+------------+----------------+------------------+---------+

| product_id | name           |price            | etc...  |

+------------+----------------+------------------+---------+

| 1          | Widget A       | 11.34            | etc...  |

+------------+----------------+------------------+---------+

| 2          | Dongle B       | 6.34             | etc...  |

+------------+----------------+------------------+---------+

每个product都有name,每个product都有价格,等等。

在EAV model里,每个entity(product)会有不同的属性集合。EAV可以尽量做到e-commerce解决方案的通用化。卖笔记本的电脑(里面有CPU速度,颜色,内存大小等等)会有不同的要求,如果跟卖yarn(有颜色,但是没有CPU速度)的相比。即使在我们假想的yarn商店里,一些物品有长度(舞厅),而其他有直径(织针)

把EAV作为默认存储方式的开源数据库和商业数据库都不多。在广大的web平台上也不会得到。正因为如此,Varien工程师基于PHP 对象和Mysql存储开发了EAV系统。换句话说,他们是在传统的关系型数据库上构建的EAV数据库系统。

在实践中,这意味着任何使用EAV source的Model是把它的属性分布在Mysql的几张表里的。

上面的图是一副粗略图,展示了Magento在查找catalogp_product实体的EAV,会去找出哪些表格。每个product在catalog_product_entity都有一条记录。在系统中的所有属性都存在eav_attribute,真是的属性值存在下面的tables里:catalog_product_entity_attribute_varchar,catalog_product_entity_attribute_decimal, catalog_product_entity_attribute_etc。

除了EAV系统带来的灵活性,它同时也避免了我们alter table的操作。每当未product新增加一个熟悉时,只要在eav_attribute里面增加一条记录。在传统的关系型/单表系统中,我们需要alter数据表的结构,这可能会对大规模数据来说有风险或者耗时。

不足之处是我们不能通过一条语句就获得我们想要的产品数据。至少要几条查询语句或者一个大的join语句。

高级EAV

下面我们重点讨论如何在Magento里创建一个EAV model。这可能是Magento里最复杂的东西,可能95%的人不会用到。但是,理解在构建EAV Model Resource时发生了什么,对我们理解Magento使用的EAV Resource是有帮助的。

EAV风格的weblog

我们创建另外一种的weblog post模型,这次用EAV Resource。

Http://localhost/magento/complexworld/

如果不确定怎么搞定,请参考前面的部分。

现在我们创建了一个新的叫Weblogeav的模型。记住,使用EAVResource.wo配置config.xml文件

    

             

                     

                         

                                    

                          XStarX_Complexworld_Model             

            complexworld_resource_eav_mysql4        

                

                      

            

       

跟普通的Model相比,在下面稍微更复杂一点(weblog_resource_eav_mysql4)

我们仍然需要让Magento知道该resource. 类似于简单Model, EAV Resources在下面而配置其他的

    

                                      

                             

                 XStarX_Complexworld_Model_Resource_Eav_Mysql4                                

                                   

                                               

                                   

eavblog_posts
                    

                                         

                             

                           

                         

此处的配置和普通的Model Resource一样,我们提供了配置PHP类,同时也有部分让Magento知道该我们创建的Model对应于哪张数据表。标签是我们想创建Model的名字,它里面的

是具体表的名字。

我们同时还需要。跟设置普通Model一样。这个resource也是用于和数据库交互的。

    

                   

                       

                                   

                             core_write            

                            

                   

                       

                                   

                           core_read            

                           

                    

            

何去何从

在PHP 5.3和命名空间的广泛应用下,Magento的一个小技巧是知道如何把和文件路径关联,然后在正确的文件目录结构下创建文件。配置或者URI,我们发现实例化一个controller而不去创建类文件是很有的。如果php找不到文件,在它提示的路径下穿件文件,并输入代码

public function indexAction() {    

            $weblog2 =Mage::getModel('complexworld/eavblogpost');    

           $weblog2->load(1);         

             var_dump($weblog2);

}

如我所预测一样,会包含下面的告警

Warning: include(XStarX/Complexworld/Model/Eavblogpost.php)[function.include]: failed to open stream: No such file or directory  in…/lib/Varien/Autoload.php on line 93

不仅告诉我们在哪里创建新resource 类,同事也做了配置检查。如果我们遇到下面的告警

Warning: include(Mage/Complexworld/Model/Eavblogpost.php)[function.include]: failed to open stream: No such file or directory  in ../lib/Varien/Autoload.php on line 93

我们就可以知道Model配置错了,因为Magento没有在local下找反而去了core。根据错误提示,我们创建我们的Model 类

XStarX/Complexworld/Model/Eavblogpost.php

class XStarx_Complexworld_Model_Eavblogpost extendsMage_Core_Model_Abstract

{    

          protected function_construct()    

          {        

                  $this->_init('complexworld/eavblogpost');    

          }       

}

注意Model本身是resource独立的。一个普通的Model和EAV Model都是继承自同样的类。只是resource导致了他们的不同。

清除cache,加载面,会得到新的warning

Warning:include(XStarX/Complexworld/Model/Resource/Eav/Mysql4/Eavblogpost.php)

正如我们猜到的那样,我们缺少了resource Model

class XStarX_Complexworld_Model_Resource_Eav_Mysql4_Eavblogpost extendsMage_Eav_Model_Entity_Abstract

{       

           public function_construct()    

          {        

                    $resource =Mage::getSingleton('core/resource');        

                    $this->setType('complexworld_eavblogpost');          

                    $this->setConnection(                

                                $resource->getConnection('complexworld_read'),                

                                $resource->getConnection('complexworld_write')             );    

           }

 }

我们已经看到了简单Model Resource和EAV ModelResource之间的不同。首先,EAV继承自Mage_Eav_Model_Entity_Abstract类。当然他们都用了同一个_construct, 注意没有_init方法。相反,我们需要自己去处理init。这意味着,告诉资源它应该用哪种连接资源方式,如何传递一个不同的标识到我们对象的setType方法。

另外一个不同之处在于,Mage_Eav_Model_Entity_Abstract是_construct不是一个抽象的方法。这对我们深入了解Magento SystemCode有帮助

再次清除缓存,加载页面得到下面的异常

Invalid entity_type specified: complexworld_eavblogpost

Magento抱怨它找不到叫complexworld_eavblogpost的entity_type. 我们上面调用了

                   $this->setType('complexworld_eavblogpost');          

每个实体有类型。类型是让EAV系统知道Model用了哪个属性,让系统去把存有属性的值的表做连接。我们需要告诉Magento我们添加了一个新的实体type。先看看下面

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: defaultdefault_attribute_set_id: 1         increment_model: eav/entity_increment_numeric      increment_per_store: 0     increment_pad_length: 8       increment_pad_char: 0*************************** 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

这个表包含了系统了所有的entity_type。这个唯一的标识complexworld_eavblogpost对应于entity_type_code列。

 系统和应用

这个部分展示了一个Magento中最重要的概念。

我们有的操作系统是一个系统,浏览器是一个应用。Magento首先可以作为一个系统,其次才是一个应用。我们的e-commerce应用使用了Magento 系统。令人困惑的是,在Magento里,系统代码会直接暴露给应用代码。EAV系统配置和我们的商店数据就在同样的数据库里,这个也是上面的例子。

如果继续深入Magento,你应该把它当做一台老式的650(IBM)机器。这意味着,如果你对它没有深入的了解,你将无法有效的开发应用程序。

创建一个Setup Resource

理论上来说我们可以只插入一条数据就可以让我们的Model运行,但是并不建议这样做。幸运的是,Magento提供了一个特别的Setup Resource来提供一些Helper方法,可以自动的创建记录来保证系统运行。

作为初始者,Setup Resource看上去如下

      

                  

                     

                                   

                          XStarX_Complexworld                 

                          XStarX_Complexworld_Entity_Setup            

                               

                                   

                          core_setup            

                            

                     

                

然后创建类文件

File: app/code/local/XStarX/Complexworld/Entity/Setup.php

Class XStarX_Complexworld_Entity_Setup extens Mage_Eav_Model_Entity_Setup

{}

注意我们是从Mage_Eav_Model_Entity_Setup继承,而不是Mage_Core_Resource_Setup。

最后,我们设置我们的安装脚本。命名惯例参照前面的部分。

File:app/code/local/XStarX/Complexworld/sql/complexworld_setup/mysql4-install-0.1.0.php

清除缓存,重新加载页面,会出现上面的异常,这就意味着我们正确设置了Setup Resource。

注意:前面也提到了,要移除core_resource的setup row  table

增加实体类型

在Setup Resource 安装脚本里,增加下面的代码,并移除之前的exception

$installer = $this; $installer->addEntityType('complexworld_eavblogpost',Array(

      //entity_mode is the URLyou'd pass into a Mage::getModel() call     

      'entity_model'          =>'complexworld/eavblogpost',//blank for now

      'attribute_model'       =>'',

     //table refers to theresource URI complexworld/eavblogpost           

     //...

     //

eavblog_posts

       'table'         =>'complexworld/eavblogpost',

    //blank for now, but can alsobe eav/entity_increment_numeric

      'increment_model'       =>'',

    //appears that this needs tobe/can be above "1" if we're using

    //eav/entity_increment_numeric

       'increment_per_store'   =>'0'

));

我们在我们的安装对象里调用addEntityType方法。这个方法容许我们把实体类型(complexworld_eavblogpost)和一串参数来设置默认值。如果我们执行这个脚本,我们会发现eav_attribute_group, eav_attribute_set, 和eav_entity_type表里都增加了新的记录。

我们再次执行加载页面会得到以下的错误

SQLSTATE[42S02]: Base table or view not found: 1146 Table'magento.eavblog_posts' doesn't exist

创建Data Table

我们已经告诉了Magento新的实体类型。下面,我们需要增加msyql tables,可以用来储存实体值,同时也需要配置系统让它知道这些表格。

如果看过Magento的安装文件,你会发现很多手工的SQL用来创建EAV Models。幸运的是,这些不再必须了。在我们的Setup Resource里,有个方法叫createEntityTable,可以自动的设置我们需要的表。增加下面的代码到setup resource。

$installer->createEntityTables(

         $this->getTable('complexworld/eavblogpost')

);

createEntityTables方法可以接受两个参数。第一个是表名,第二个是一串参数。我们使用Setup Resource的getTable从config里获得表名。这次它得到的是eavblog_posts。第二个参数通常在高级应用里采用,此处略掉。

执行完上面的脚本后,可以在数据库里发现以下的表

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 DESCLIMIT 1 \G *************************** 1. row ***************************  

attribute_set_id: 65    

entity_type_id: 37

attribute_set_name: Default        

sort_order: 6

再次加载http://localhost/magento/complexworld

会发现成功了。因为数据库里没有数据,所以应该也不会有数据展示。

创建安装脚本---问题

上面的安装可能不会那么顺利,在magento 1.7下面会报错

Mage_Eav_Exception: Can't create table: module_entity

如何解决呢?

Debug createEntityTables()方法,可以在结尾处看到

$connection->beginTransaction(); try {      foreach ($tables as $tableName => $table) {         $connection->createTable($table);     }     $connection->commit(); } catch (Exception $e) {    Zend_Debug::dump($e->getMessage());    $connection->rollBack();    throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Can\'t create table: %s', $tableName)); }

查看底层错误是:User Error: DDLstatements are not allowed in transactions

然后跟进commit函数

/**  * Check transaction level in case of DDL query  *  *@param string|Zend_Db_Select $sql  * @throws Zend_Db_Adapter_Exception */ protected function _checkDdlTransaction($sql) {     if(is_string($sql) && $this->getTransactionLevel() > 0) {        $startSql = strtolower(substr(ltrim($sql), 0, 3));        if (in_array($startSql, $this->_ddlRoutines)) {           trigger_error(Varien_Db_Adapter_Interface::ERROR_DDL_MESSAGE, E_USER_ERROR);        }     } }

结论是Mysql不支持DDL Transaction。

因此在app/code/local/{CompanyName}/{ModuleName}/Setup/Helper.php里重写createEntityTable方法

{         ...           /**          * Remove transaction code due to issues with errors.          */         //$connection->beginTransaction();         try {              foreach ($tables as $tableName => $table) {                 $connection->createTable($table);             }             $connection->commit();        } catch (Exception $e) {            //$connection->rollBack();            throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Can\'t create table: %s', $tableName));        }     } }

然后问题解决。

增加属性

在Setup Resource中的最后一步应该是告诉Mageno,我们要给Model什么样的属性。这相当于在单表中增加列。而Setup Resource会继续帮助我们。我们感兴趣的两个方法是installEntities和getDefaultEntities。

我们可以执行一些code告诉Magento我们的entities,但是我们怎么做呢?

之前部分的代码是简单的告诉Magento一个entity type可能会被我们加入系统。下面的是实际增加type到系统。如果你愿意,可添加很多种同样类型的enities。

为Setup Resource添加方法

class XStarX_Complexworld_Entity_Setup extendsMage_Eav_Model_Entity_Setup {    

              public functiongetDefaultEntities()    

              {                    

                      die("Calling ".__METHOD__);    

              }

}

增加下面的 代码到setup script里

$installer->installEntities();

加载页面,可以看到die/exit里的消息

Calling XStarX_Complexworld_Entity_Setup::getDefaultEntities

你可能看到了下面的异常

[message:protected] => SQLSTATE[23000]: Integrity constraintviolation: 1217 Cannot delete or update a parent row: a foreign key constraintfails

如果发生了,你试着DROP/CREATE主要的实体table。而且createEntityTables 只会执行一次。

新实体的配置

当调用installEntities, Magento需要做几件事情来安装entities。首先它需要知道entities是什么。getDefaultEnities告诉了这个消息。

下面是一个简单的小例子

class XStarX_Complexworld_Entity_Setup extendsMage_Eav_Model_Entity_Setup

{    

           public functiongetDefaultEntities()    

           {                   

                   return array (

                             'complexworld_eavblogpost' =>array(

                            'entity_model'      =>'complexworld/eavblogpost',                

                            'attribute_model'   => '',                

                            'table'             =>'complexworld/eavblogpost',                

                            '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,

                     ),

                 ),

             )

         );

     }

 }

这一坨代码头大了吧,下面慢慢分析。

期望的返回值

getDefaultEnities方法可以返回一个php的key/value对的数组。每个key应该是entity类型的名字($installer->addEntityType(‘complexworld_eavblogpost’,..)。每个值应该是一个描述entity的数组

描述entity的array

这个array同样也是key/value对。下面的应该看起来熟悉

'entity_model'      =>'complexworld/eavblogpost',

'attribute_model'   => '',

'table'             =>'complexworld/eavblogpost',

'attributes'        => array(

这些应该和$installer->addEntityType(.. 中一致,另外最后一个key,attributes,应该包含了另外一个描述属性的数组。

描述属性的数组

接下来的数组呢,也是key/value的对。这次key变成了属性的名字,value是最后的key/value对来定义属性。为了简单说明,我们只用了一个简单的属性,但是你可以自己定义你想要的

最后定义属性的Key/Value数组

//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,

很不幸的事情是,Mageno绑定了很多后台UI,如Label, input等。Varien工程师选择紧密的把UI实现和后台Model结构绑定。这样做有一定好处,但是这意味着很大一部分对外是透明的,特别是web开发者来说,他们几十年来都是前后台分开进行的。

我们需要注意的一个属性是

‘type’=>’varchar’

它定义了属性值的类型。回忆上面的属性类型表

eavblog_posts_datetime

eavblog_posts_decimal

eavblog_posts_int

eavblog_posts_text

eavblog_posts_varchar

这不是Mysql列的类型,而是EAV属性类型,他们的名字显示了他们包含的值。

现在万事俱备了,最后再执行一次installer script。再调用installEntities后,我们应该

1.     eav_attribute表中会有一个新的记录

2.     eav_entity_attribute有一个新的记录

把所有的整合

添加下面遍历的代码,在Index Controller里

public function populateEntriesAction() {    

           for($i=0;$i<10;$i++) {            

                      $weblog2 =Mage::getModel('complexworld/eavblogpost');        

                      $weblog2->setTitle('Thisis a test '.$i);        

                      $weblog2->save();    

            }         

            echo 'Done';

}

 publicfunction showcollectionAction() {    

             $weblog2 = Mage::getModel('complexworld/eavblogpost');    

             $entries =$weblog2->getCollection()->addAttributeToSelect('title');            

             $entries->load();    

             foreach($entries as $entry)    {        

                        //var_dump($entry->getData());        

                        echo'

'.$entry->getTitle().'

';    

             }    

             echo '
Done
';

}

加载下面的URL

http://localhost/magento/index.php/complexworld/index/populateEntries

如果查看数据库,会发现下面多了10条记录

mysql> select * from eavblog_posts order byentity_id DESC;

+-----------+----------------+------------------+--------------+-----------+----------+---------------------+---------------------+-----------+

| entity_id | entity_type_id | attribute_set_id| increment_id | parent_id | store_id | created_at          | updated_at          | is_active |

+-----------+----------------+------------------+--------------+-----------+----------+---------------------+---------------------+-----------+

|       10 |             31 |                0 |              |         0 |        0 | 2009-12-06 08:36:41 | 2009-12-0608:36:41 |         1 | 

|        9 |             31 |                0 |              |         0 |       0 | 2009-12-06 08:36:41 | 2009-12-06 08:36:41 |         1 | 

|        8 |             31 |                0 |              |         0 |        0 | 2009-12-06 08:36:41 | 2009-12-0608:36:41 |         1 | 

|        7 |             31 |                0 |              |         0 |        0 | 2009-12-06 08:36:41 | 2009-12-0608:36:41 |         1 | 

|        6 |             31 |                0 |              |         0 |        0 | 2009-12-06 08:36:41 | 2009-12-0608:36:41 |         1 | 

|        5 |             31 |                0 |              |         0 |        0 | 2009-12-06 08:36:41 | 2009-12-0608:36:41 |         1 | 

|        4 |             31 |                0 |              |         0 |        0 | 2009-12-06 08:36:41 | 2009-12-0608:36:41 |         1 | 

|        3 |             31 |                0 |              |         0 |        0 | 2009-12-06 08:36:41 | 2009-12-0608:36:41 |         1 | 

|        2 |             31 |                0 |              |         0 |       0 | 2009-12-06 08:36:41 | 2009-12-06 08:36:41 |         1 | 

|        1 |             31 |                0 |              |         0 |        0 | 2009-12-06 08:36:41 | 2009-12-0608:36:41 |         1 | 

+-----------+----------------+------------------+--------------+-----------+----------+---------------------+---------------------+-----------+

同样在eavblog_posts_varchar里10条记录

mysql> select * from eavblog_posts_varcharorder by value_id DESC;

+----------+----------------+--------------+----------+-----------+------------------+

| value_id | entity_type_id | attribute_id |store_id | entity_id | value            |

+----------+----------------+--------------+----------+-----------+------------------+

 |       10 |             31 |          933 |        0 |        10 | This is a test 9 | 

|        9|             31 |          933 |        0 |         9 | This is a test 8 | 

|        8|             31 |          933 |        0 |         8 | This is a test 7 | 

|        7|             31 |          933 |        0 |         7 | This is a test 6 | 

|        6|             31 |          933 |        0 |         6 | This is a test 5 |

 |        5 |             31 |          933 |        0 |         5 | This is a test 4 | 

|        4|             31 |          933 |        0 |         4 | This is a test 3 | 

|        3|             31 |          933 |        0 |         3 | This is a test 2 | 

|        2|             31 |          933 |        0 |         2 | This is a test 1 | 

|        1 |             31 |          933 |        0 |         1 | This is a test 0 | 

+----------+----------------+--------------+----------+-----------+------------------+

注意evablog_posts_varchar使用entity_id索引到eavblog_posts。

最后加载下面的URL

http://localhost/magento/complexworld/index/showCollection

显示

Warning: include(XStarX/Complexworld/Model/Resource/Eav/Mysql4/Eavblogpost/Collection.php)[function.include]: failed to open stream: No such file or directory  in/Users/alanstorm/Sites/magento.dev/lib/Varien/Autoload.php on line 93

差一点就结束了

File: XStarx/Complexworld/Model/Resource/Eav/Mysql4/Eavblogpost/Collection.php

class XStarX_Complexworld_Model_Resource_Eav_Mysql4_Eavblogpost_Collectionextends Mage_Eav_Model_Entity_Collection_Abstract

{     

     protectedfunction _construct()

     {

        $this->_init('complexworld/eavblogpost');

     }

 }

这段代码是普通的Mageno初始话Model。此时重新加载页面,我们应该可以看到输出。

哪个属性?

可能会注意到下面的不同之处

$entries = $weblog2->getCollection()->addAttributeToSelect('title');        

因为EAV查询的时候是需要明确查询哪个属性的。这样只需查必要的表。如果不担心性能的额问题,可以

$entries =$weblog2->getCollection()->addAttributeToSelect('*');        

更进一步

还有更多EAV东西值的学习

1.    EAV属性不仅只有datetime, decimal, int, text and varchar我们可以创建自己的属性例如attribute_model

2.    Collection过滤对于复杂的collection在加载前我们需要增加自己的addAttributeToFilter方法给我们的collection

3.    EAV 等级Magento有基本的EAV模型并以此构建了商店功能相关的可以减少查询次数flat Model

EAV model毫无争议的是Mageno中开发者需要处理的最复杂的部分

 

你可能感兴趣的:(php,magento)