1、Magento 开发文档(七) :Magento EAV 模型+6Magento 中文手册(七):Magento EAV 模型在第一篇介绍 Magento ORM 的文章中,我们提到过 Magento 拥有两类模型。普通的模型及Entity Attribute Value(EAV)模型。这里首先搞清楚它们之前的一些关系。所有的 Magento 模型都继承自 Mage_Core_Model_Abstract/Varien_Object 类链。真正区别普通模型和 EAV 模型的关键是该模型使用的模型资源(Model Resource)。尽管所有的资源类都继承自 Mage_Core_Model_Re
2、source_Abstract 类,普通模型拥有继承自该类的子类 Mage_Core_Model_Mysql4_Abstract,同时 EAV 模型拥有继承自该类的另外一个子类Mage_Eav_Model_Entity_Abstract。为什么要这样设计呢?仔细想想,不难得出结论。作为终端程序员,你需要的只是如何与数据库交互的方法,而不用在意底层是如何实现的。EAV 模型这里我们引用维基百科的定义,这段暂时就不翻译了,太多术语。Entity-Attribute-Value model (EAV), also known as object-attribute-value model and o
3、pen schema is a data model that is used in circumstances where the number of attributes (properties, parameters) that can be used to describe a thing (an “entity” or “object”) is potentially very vast, but the number that will actually apply to a given entity is relatively modest. In mathematics, th
4、is model is known as a sparse matrix.在一个普通的数据库中,表中一般包含多列,像下表所示,+| products |+| product_id | name | price | etc |+-+| product_id | name | price | etc |+-+| 1 | Widget A | 11.34 | etc |+-+| 2 | Dongle B | 6.34 | etc |+-+每个产品都有,name,price 等等字段。在 EAV 模型中, 每个模型化的实体/entity(比如说产品)拥有一系列不同的属性。EAV 模型几乎可以提供给电子
5、商务一个通用的数据库解决方案。一个出售电脑(属性:CPU 速度,颜色,内存)的网店与一个出售衣服(颜色,尺码,性别)的网店对于商品属性的需要肯定大不相同。即使是在电脑网店中,不同的产品对于属性的要求也有差别,如笔记本电脑(电池),台式电脑(机箱)。使用 EAV 模型的数据库程序,在开源及商业软件里都不多。并且多数 IDC 主机提供商都没有大规模采用这种数据库解决方案。因此,Magento 工程师通过 MySQL 作为底层数据存储,用PHP 实现了 EAV 系统。换句话说,Magento 在传统关系数据库上构建了 EAV 数据库系统。不得不说,这是 Magento 对于电子商务解决方案领袖地位的
6、体现。在实际应用中,这意味着任何使用 EAV 模型资源的模型,其属性都是分布在 MySQL 的多个表里(而非像上图中演示的普通数据库那样)。MagentoEav上图演示了查询一条 catalog_product(产品信息)EAV 记录时, Magento 所需要与数据库表进行交互的一个大概轮廓。每个产品在 catalog_product_entity 中有一条对应的记录。而整个系统中所有能够使用的属性(颜色,大小,等等,且不限于产品本身)都保存在eav_attribute 表中,注意,该表中只是记录全局使用的属性,并不会记录任何属性值。之际的属性值分散在很多表中,例如catalog_produ
7、ct_entity_attribute_varchar,catalog_product_entity_attribute_decimal,catalog_product_entity_attribute_etc。除了 EAV 系统提供的超级便利的扩展性之外,对于数据库的完整性也提供了非常具有实际意义的好处。在添加一个新的产品属性时,你只需要在 eav_attribute 中添加该属性即可。而在传统的关系数据库中,你需要 ALTER 表结构,增加字段添加新的产品属性,这样很可能会造成不必要的风险。当然,EAV 系统也有不够便利的地方,在这个系统中,不跨表的 SQL 语句几乎无法调用任何拥有的产品
8、信息。大部分操作都需要较为庞大的 JOIN 连结语句。在 Magento 中创建自定义 EAV 模型上面我们简单介绍了下 EAV 模型的概念。下文主要讲解如何在 Magento 中创建一个新的EAV 模型。可以想象对于从来没有接触过 EAV 模型的程序员来说,下文会非常有难度,并且多数程序员很少会用到 EAV 开发产品。不过,理解 EAV 模型的创建过程,会帮助你更加深入的理解 Magento 中所使用的 EAV 资源。下文中对于 EAV 模型的知识点会非常多,假如你对 Magento 的 MVC 架构还不够了解,建议你先从头阅读本系列教程。不管有多困难,一起上路吧,兄弟们!EAV 模型下的
9、Weblog 模块现在,我们开始创建 Weblog 模块的又一个模型,不过这次使用 EAV 模型资源。从头开始,创建一个新的模块,Complexworld,写好相关的配置文件,并确保能够在浏览器中访问该模块。http:/ Weblogeav 模型。记住,是模型资源区别开普通模型和 EAV 模型。所以,创建 EAV 模型的这一过程和创建普通模型没有区别。下面这段代码,和第一次创建blogpost 普通模型时差别不大。01020304050607080910Rui_Complexworld_Modelcomplexworld_resource_eav_mysql4细心的你应该注意到了上述代码与创建
10、普通模型代码的区别,在标签中,该模型资源的名字更为复杂了。先不管它,我们继续。接着,我们需要告诉 Magento 该模型资源的配置信息。和普通模型类似, EAV 资源模型的配置同样是在子节点中。0102030405060708Rui_Complexworld_Model_Resource_Eav_Mysql4eavblog_posts到目前为止,EAV 模型资源的配置与普通模型资源的配置差别还不大。节点提供了一个 EAV 资源类(继承自 Mage_Eav_Model_Entity_Abstract),内提供给Magento 我们想要创建的模型的基本表。标签指定了我们将要创建的模型,该标签内部的
11、标签指定了该模型将要使用的基本表(下文有更详细的及时)。接下来,我们同样需要子节点中指定读取和写入适配器。这点与普通模型的设置也是一样的。该节点内部包含的信息,告诉 Magento 使用哪些类 /哪种方式与数据库底层进行交互。010203040506070809101112core_writecore_write文件都在什么位置在 PHP5.3 及命名空间广泛使用之前, Magento 依然会保持与路径之间的相对关系,并保证你创建了正确命名的目录结构及类文件。在配置任何或 URI之后,你会发现,当在控制器中实例化那些还未存在的类时,会非常方便。通过配置文件,PHP 会抛出异常告诉它无法找到该文
12、件,并且会附带有该文件的相对路径。一起试下在 Index控制器中实例化尚未创建的模型。0102030405publicfunctionindexAction() $weblog2= Mage:getModel(complexworld/eavblogpost);$weblog2-load(1);var_dump($weblog2);和上面说到的一样,系统抛出以下异常,Warning: include(Magentotutorial/Complexworld/Model/Eavblogpost.php) function.include: failed to open stream: No su
13、ch file or directory in /Users/username/Sites/magento.dev/lib/Varien/Autoload.php on line 93除了告诉我们应该在什么路径创建该资源类之外,如果遇到如下异常,Warning: include(Mage/Complexworld/Model/Eavblogpost.php) function.include: failed to open stream: No such file or directory in /Users/username/Sites/magento.dev/lib/Varien/Auto
14、load.php on line 93我们就能知道配置文件出错了,因为 Magento 试图在核心文件中查找该模块,而非 local 路径中查找。了解上述内容之后,我们开始创建模型类。在以下路径创建类文件,并添加以下代码。File: app/code/local/Magentotutorial/Complexworld/Model/Eavblogpost.php:0102030405classRui_Complexworld_Model_Eavblogpost extendsMage_Core_Model_Abstract protectedfunction_construct() $this
15、-_init(complexworld/eavblogpost);再一次废话,模型本身是与模型资源独立的。无论是普通模型还是 EAV 模型都继承自同一个类。模型资源是区别它们的原因。清空 Magento 缓存,刷新页面,你会看到一个警告。Warning: include(Magentotutorial/Complexworld/Model/Resource/Eav/Mysql4/Eavblogpost.php)很明显,是时候为模型创建模型资源了。在以下路径创建模型资源文件,并添加如下代码。File: app/code/local/Magentotutorial/Complexworld/Mod
16、el/Resource/Eav/Mysql4/Eavblogpost.php:01020304050607080910classRui_Complexworld_Model_Resource_Eav_Mysql4_Eavblogpost extendsMage_Eav_Model_Entity_Abstract publicfunction_construct() $resource= Mage:getSingleton(core/resource);$resource-setType(complexworld_eavblogpost);$resource-setConnection($res
17、ource-getConnection(complexworld_read),$resource-getConnection(complexworld_write);这里,终于看到普通的模型资源和 EAV 模型资源的一些区别了。首先,这里扩展的是Mage_Eav_Model_Entity_Abstract 类。另外,该类中虽然和普通模型资源一样也使用了_construct()方法,却没有_init()方法。取而代之,我们需要自己处理 init()的一些功能。这意味着,我们需要告诉 EAV 模型资源使用哪一种链接资源,并且传递唯一标示符到对象的setType()方法中。与普通模型资源类的另外一个
18、不同点是,_construct()不是抽象方法,这主要是为了与Magento 老版本之间的兼容问题。清空 Magento 缓存,刷新页面,看下系统抛出的新异常。Invalid entity_type specified: complexworld_eavblogpost系统提示其无法找到叫 complexworld_eavblogpost 的实体类型(entity_type) 。这个值是你在 setType()方法中传递的参数。每个实体(Entity)都有一个类型。类型让 EAV 系统知道模型在使用哪些属性,并允许系统连接存储那些属性值的表。所以这里我们需要让 Magento 意识到我们添加了
19、一个新的实体类型。首先,我们看下 eav_entity_type 表。mysql select * from eav_entity_typeG* 1. row *entity_type_id: 1entity_type_code: customerentity_model: customer/customerattribute_model:entity_table: customer/entityvalue_table_prefix:entity_id_field:is_data_sharing: 1data_sharing_key: defaultdefault_attribute_set_
20、id: 1increment_model: eav/entity_increment_numericincrement_per_store: 0increment_pad_length: 8increment_pad_char: 0* 2. row *entity_type_id: 2entity_type_code: customer_addressentity_model: customer/customer_addressattribute_model:entity_table: customer/address_entityvalue_table_prefix:entity_id_fi
21、eld:is_data_sharing: 1data_sharing_key: defaultdefault_attribute_set_id: 2increment_model:increment_per_store: 0increment_pad_length: 8increment_pad_char: 0该表包含了系统中可用的所有实体类型(entity_types) ,该表中的唯一识别complexworld_eavblogpost 身份的是 entity_type_code 列。系统及应用 Systems and Applications这里有必要解释下 Magento 系统最为重要的
22、一个概念。想象下在你面前的这台电脑,Windows,Linux,Mac 等是系统-Systems ;你的浏览器(FF,IE,Opera 等)是应用-Application。这里同样省略一段,讲述了系统与应用之间的一些关系,不太能理解。创建启动资源手动创建表并插入相关数据确实没问题,不过推荐使用 Magento 启动资源(Setup Resource)。 Magento 的启动资源提供了一系列的助手方法,能够自动创建表及数据,以使系统正常运行。首先,我们将启动资源写入配置文件。010203040506070809Magentotutorial_ComplexworldMagentotutoria
23、l_Complexworld_Entity_Setupcore_setup然后,在下列路径,创建启动资源类文件,并写入如下代码。File: app/code/local/Magentotutorial/Complexworld/Entity/Setup.php:010203classMagentotutorial_Complexworld_Entity_Setup extendsMage_Eav_Model_Entity_Setup 注意这个类继承的是 Mage_Eav_Model_Entity_Setup 而不是Mage_Core_Model_Resource_Setup。最后,设置安装脚本
24、。如果你还不是太熟悉这里的命名约定,建议你复习下第六章的内容。在下列路径中创建安装脚本,并写入如下代码,File: app/code/local/Magentotutorial/Complexworld/sql/complexworld_setup/mysql4-install-0.1.0.php:01 thrownewException(“This is an exception to stop the installer from completing“);清空 Magneto 缓存,重新刷新页面,上述代码中的异常会被系统抛出,这说明启动资源已经被成功配置了。注意:我们会逐步完成安装脚本。
25、如果你认真学习了第六章的知识,你会了解到,让系统重新运行安装脚本之前,必须删除 core_resource 表中的相关模块并清空缓存。在下文中,每修改一次安装脚本,都记得要执行上述步骤。添加实体类型 Entity Type为了添加实体类型,首先需要将下列代码添加到安装脚本中,不要忘记移除上面代码中的异常,然后刷新任意页面。010203$installer= $this;$installer-addEntityType(complexworld_eavblogpost,Array(/entity_mode is the URL youd pass into a Mage:getModel() c
26、all0405060708091011121314entity_model =complexworld/eavblogpost,/blank for nowattribute_model =,/table refers to the resource URI complexworld/eavblogpost/.eavblog_poststable =complexworld/eavblogpost,/blank for now, but can also be eav/entity_increment_numericincrement_model =,/appears that this ne
27、eds to be/can be above “1“ if were using eav/entity_increment_numericincrement_per_store =0);在这段安装脚本中,我们调用了 addEntityType()方法。该方法允许我们传递实体类型及相关参数,并设置它的默认值。运行此脚本之后,在 eav_attribute_group,eav_attribute_set和 eav_entity_type 表中会多出新的记录。再次刷新 complexworld 页面,页面会显示如下错误。SQLSTATE42S02: Base table or view not fo
28、und: 1146 Table magento.eavblog_posts doesnt exist创建表现在,我们已经成功让 Magento 系统意识到新创建的实体类型。然后,我们需要添加一些数据库表用来存储所有的实体值,并继续添加配置文件,让系统认识到这些表的存在。如果你曾经观察过 Magento 核心文件中的安装脚本,你应该能够看到一些用于手动创建 EAV模型表的 SQL 代码。幸运的是,这都已经成为历史了。启动资源拥有一个createEntityTables()方法,该方法能够帮助自动创建我们需要的表,并添加相应的配置文件到系统中。在启动资源中添加如下代码。010203$install
29、er-createEntityTables($this-getTable(complexworld/eavblogpost);createEntityTables()方法接收两个参数。第一个是表名,第二个是相关配置。我们使用启动资源的 getTable()方法从配置文件中获取表名。如果你一直认真学习本系列教程,你能猜到这里相当于字符串 eavblog_posts。这里暂且忽略参数二,它一般用于一些高级配置。在成功运行上述安装脚本之后,数据库中会新增下面几张表。eavblog_postseavblog_posts_datetimeeavblog_posts_decimaleavblog_post
30、s_inteavblog_posts_texteavblog_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: 65entity_type_id: 37attribute_set_name: Defaultsort_order: 6再次重新刷新 complexworld 页面。http:/ Magento,新建的模型中需要哪些属性。对于 EA
31、V 模型来说,添加属性这一步相当于在普通数据表中添加新的字段。这一步中,我们感兴趣的方法有两个,installEntities()和 getDefaultEntites()。这两个方法的命名字面上看起来会让人困惑。毕竟,我们不是刚刚在 Magento 中配置了实体吗?现在需要再来一遍?实际上,上一小节中,我们只是添加了一个实体类型到 Magento 系统中。而这里,我们需要做的是,添加一个属于该实体类型的实体到系统中。当然,根据需要,你非常可能会添加 N 多个属于该实体类型的实体。开始吧,将这些方法添加到启动资源中。010203040506classMagentotutorial_Comple
32、xworld_Entity_Setup extendsMage_Eav_Model_Entity_Setup publicfunctiongetDefaultEntities()die(“Calling “._METHOD_);在脚本结尾,添加如下代码。01 $installer-installEntities();刷新页面,能够看到上面代码中输出的内容。这是我们希望看到的。即在安装脚本中调用installEntities()方法,会运行启动资源中的方法。Calling Magentotutorial_Complexworld_Entity_Setup:getDefaultEntities另外
33、一种可能,会看到系统抛出的异常,类似下面这种。message:protected = SQLSTATE23000: Integrity constraint violation: 1217 Cannot delete or update a parent row: a foreign key constraint fails如果出现这种情况的话,也不要意外。因为你刷新页面之后,再次运行了启动脚本,并且调用了 createEntityTables()方法。虽然 Magento 会自动生成 DROP 及 CREATE Table,不幸的是它拿具有外键关系的表没辙。解决这个情况很简单,只需要在运行启
34、动脚本之前,注视掉 createEntityTables()方法即可。重申一下,在一般情况下,安装脚本一般只会运行一次,并且 createEntityTables 也只会被调用一次。配置新添加的实体当调用 installEntites()方法时,Magento 系统会再后台运行很多操作,来正常安装实体。在那之前,必须让它知道需要创建的实体及其相关配置。通常情况下,我们会使用getDefaultEntities()方法返回这些信息。当然,也可以通过 installEntities 的参数配置进行安装,不过建议大家遵循 Magento 核心代码的一种约定。首先,添加 Eavblogpost 实体,
35、并设置一个 title 属性。0102030405060708091011121314151617181920212223242526classRui_Complexworld_Entity_Setup extendsMage_Eav_Model_Entity_Setup publicfunctiongetDefaultEntities() returnarray(complexworld_eavblogpost= array(entity_model = complexworld/eavblogpost,attribute_model = ,table = complexworld/eavb
36、logpost,attributes = array(title= array(/the EAV attribute type, NOT a mysqlvarchartype = varchar,backend = ,frontend = ,label = Title,input = text,class = ,source = ,/ store scope = 0/ global scope = 1/ website scope = 2global = 0,visible = true,required = true,user_defined = true,default = ,search
37、able = false,27282930313233343536filterable = false,comparable = false,visible_on_front = false,unique = false,),),);很复杂的一段代码,接下来,一点点分析。getDefaultEntities()返回值该方法返回键值对数组。键是实体类型的名字,也就是在$installer-addEntityType()方法中设置的参数一。值是一个描述该实体的数组。描述实体的数组描述实体的数组同样是一些列的键值对。类似如下代码,01020304entity_model = complexworld
38、/eavblogpost,attribute_model = ,table = complexworld/eavblogpost,attributes = array(这些值应该匹配$installer-addEntityType()中的参数,attributes 键值需要包含另外一个数组,用来描述添加的属性本身。描述属性本身的数组上面说到 attributes 键的值是一个数组,该数组的键是属性名,值是上述这一系列数组最终的数据键值对,用来定义该属性的相关信息。这里,我们定义一个简单的属性,不过,这里就是你定义所有属性的地方,根据程序需要,可以定义无数个属性。描述属性数组的详细讲解先来看下这
39、段代码。01020304/the EAV attribute type, NOT a mysqlvarchartype = varchar,backend = ,frontend = ,0506070809101112131415161718192021label = Title,input = text,class = ,source = ,/ store scope = 0/ global scope = 1/ website scope = 2global = 0,visible = true,required = true,user_defined = true,default = ,
40、searchable = false,filterable = false,comparable = false,visible_on_front = false,unique = false,这里,需要承认的是,我也没有搞太清楚上述键值对的意思。许多都涉及到了 Magento 后端UI 的驱动特征,比如 label 和 input。Magento 工程师将 UI 实现与后端模型结构紧紧绑定起来。后面这段不翻译了,直接上英文。Unfortunately, this is where your author has to fess up and tell you hes unsure what
41、most of these do. Many involve driving features of the Magento back-end UI, such as label and input. Magento engineers have chosen to tightly bind their UI implementation with their back-end Model structure. This allows them certain advantages, but it means there are large parts of the system that r
42、emain opaque to outsiders, particularly web developers whove been chanting the mantra of back-end/front-end separation for near on a decade.在上述代码中,需要注意的最重要的一个属性是,01 type= varchar这个键值对定义了该属性将要包含的数据类型。还记得之前通过启动资源自动创建的那几张用来存储不同数据类型的表吗?eavblog_posts_datetimeeavblog_posts_decimaleavblog_posts_inteavblog_
43、posts_texteavblog_posts_varchar注意,这些表的命名并不一定和 MySQL 中的数据类型相关,而是 EAV 属性类型,这些容易引起误会的名字(varchar,decimal),只是这些表中存储的值的类型的一个象征性叫法。好了,到此为止,所有一切都已经搞定了。最后一次刷新页面,重新运行安装脚本。在成功调用了 installEntities()方法之后,Magento 系统会, 在 eav_attribute 表中新增 tittle 一行数据 在 eav_attribute_atribute 中新增一行数据把一切整合起来很可能,这是你做程序以来写的最烂的一个 Blog
44、程序,不过,让我们继续吧。添加一些新的记录到表中,然后通过收集将其从数据库中读取出来。添加下列两个方法到 Index 控制器中。0102030405060708091011121314151617181920publicfunctionpopulateEntriesAction() for($i=0;$isetTitle(This is a test .$i);$weblog2-save();echoDone;publicfunctionshowcollectionAction() $weblog2= Mage:getModel(complexworld/eavblogpost);$entri
45、es= $weblog2-getCollection()-addAttributeToSelect(title);$entries-load();foreach($entriesas$entry) / var_dump($entry-getData();echo.$entry-getTitle().;echoDone;访问 populateEntries()方法,添加几条数据库记录。http:/magento.dev/index.php/complexworld/index/populateEntries运行成功之后,可以打开数据库,会发现 eavblog_posts 表中增加了十行记录。my
46、sql select * from eavblog_posts order by entity_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-06 08:36:41 | 1 | 9 | 31 | 0 | | 0 | 0 | 2009-12-06 08:36:41 | 2009-12-06 08:36:41 | 1 | 8 | 31 |