在第一篇介绍Magento ORM的文章中,我们提到过Magento拥有两类模型。普通的模型及Entity Attribute Value(EAV)模型。这里首先搞清楚它们之前的一些关系。

所有的Magento模型都继承自Mage_Core_Model_Abstract/Varien_Object类链。真正区别普通模型和EAV模型的关键是该模型使用的模型资源(Model Resource)。尽管所有的资源类都继承自Mage_Core_Model_Resource_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 open 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, this model is known as a sparse matrix.

在一个普通的数据库中,表中一般包含多列,像下表所示,

+——————+
| products�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 |
+——————+
| product_id�0�2�0�2�0�2�0�2�0�2�0�2 |
| name�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 |
| price�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 |
| etc..�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 |
+——————+

+————+—————-+——————+———+
| product_id | name�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 | price�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 | etc…�0�2 |
+————+—————-+——————+———+
| 1�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 | Widget A�0�2�0�2�0�2�0�2�0�2�0�2 | 11.34�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 | etc…�0�2 |
+————+—————-+——————+———+
| 2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 | Dongle B�0�2�0�2�0�2�0�2�0�2�0�2 | 6.34�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 �0�2�0�2 | etc…�0�2 |
+————+—————-+——————+———+

每个产品都有,name,price等等字段。

在EAV模型中,每个模型化的实体/entity(比如说产品)拥有一系列不同的属性。EAV模型几乎可以提供给电子商务一个通用的数据库解决方案。一个出售电脑(属性:CPU速度,颜色,内存)的网店与一个出售衣服(颜色,尺码,性别)的网店对于商品属性的需要肯定大不相同。即使是在电脑网店中,不同的产品对于属性的要求也有差别,如笔记本电脑(电池),台式电脑(机箱)。

使用EAV模型的数据库程序,在开源及商业软件里都不多。并且多数IDC主机提供商都没有大规模采用这种数据库解决方案。因此,Magento工程师通过MySQL作为底层数据存储,用PHP实现了EAV系统。换句话说,Magento在传统关系数据库上构建了EAV数据库系统。不得不说,这是Magento对于电子商务解决方案领袖地位的体现。

在实际应用中,这意味着任何使用EAV模型资源的模型,其属性都是分布在MySQL的多个表里(而非像上图中演示的普通数据库那样)。

Magento Eav

上图演示了查询一条catalog_product(产品信息)EAV记录时,Magento所需要与数据库表进行交互的一个大概轮廓。每个产品在catalog_product_entity中有一条对应的记录。而整个系统中所有能够使用的属性(颜色,大小,等等,且不限于产品本身)都保存在eav_attribute表中,注意,该表中只是记录全局使用的属性,并不会记录任何属性值。之际的属性值分散在很多表中,例如catalog_product_entity_attribute_varchar, catalog_product_entity_attribute_decimal, catalog_product_entity_attribute_etc。

除了EAV系统提供的超级便利的扩展性之外,对于数据库的完整性也提供了非常具有实际意义的好处。在添加一个新的产品属性时,你只需要在eav_attribute中添加该属性即可。而在传统的关系数据库中,你需要ALTER表结构,增加字段添加新的产品属性,这样很可能会造成不必要的风险。

当然,EAV系统也有不够便利的地方,在这个系统中,不跨表的SQL语句几乎无法调用任何拥有的产品信息。大部分操作都需要较为庞大的JOIN连结语句。

在Magento中创建自定义EAV模型

上面我们简单介绍了下EAV模型的概念。下文主要讲解如何在Magento中创建一个新的EAV模型。可以想象对于从来没有接触过EAV模型的程序员来说,下文会非常有难度,并且多数程序员很少会用到EAV开发产品。不过,理解EAV模型的创建过程,会帮助你更加深入的理解Magento中所使用的EAV资源。

下文中对于EAV模型的知识点会非常多,假如你对Magento的MVC架构还不够了解,建议你先从头阅读本系列教程。不管有多困难,一起上路吧,兄弟们!

EAV模型下的Weblog模块

现在,我们开始创建Weblog模块的又一个模型,不过这次使用EAV模型资源。从头开始,创建一个新的模块,Complexworld,写好相关的配置文件,并确保能够在浏览器中访问该模块。

http://example.com/complexworld

再次建议你,如果还不知道如何建立模块,从头开始看这个系列的教程。能够正常访问该地址之后,接下来创建一个Weblogeav模型。记住,是模型资源区别开普通模型和EAV模型。所以,创建EAV模型的这一过程和创建普通模型没有区别。下面这段代码,和第一次创建blogpost普通模型时差别不大。

<!-- ... -->
<globals><models><complexworld><class>Rui_Complexworld_Model</class><resourceModel>complexworld_resource_eav_mysql4</resourceModel></complexworld><!-- ... --></models>
</globals>

细心的你应该注意到了上述代码与创建普通模型代码的区别,在<resourceModel />标签中,该模型资源的名字更为复杂了。先不管它,我们继续。

接着,我们需要告诉Magento该模型资源的配置信息。和普通模型类似,EAV资源模型的配置同样是在<model />子节点中。

<complexworld_resource_eav_mysql4><class>Rui_Complexworld_Model_Resource_Eav_Mysql4</class><entities><eavblogpost><table>eavblog_posts</table></eavblogpost></entities>
</complexworld_resource_eav_mysql4>

到目前为止,EAV模型资源的配置与普通模型资源的配置差别还不大。<class />节点提供了一个EAV资源类(继承自Mage_Eav_Model_Entity_Abstract),<entities />内提供给Magento我们想要创建的模型的基本表。<eavblogpost />标签指定了我们将要创建的模型,该标签内部的<table />标签指定了该模型将要使用的基本表(下文有更详细的及时)。

接下来,我们同样需要<resources />子节点中指定读取和写入适配器。这点与普通模型的设置也是一样的。该节点内部包含的信息,告诉Magento使用哪些类/哪种方式与数据库底层进行交互。

<resources><complexworld_write><connection><use>core_write</use></connection></complexworld_write><complexworld_read><connection><use>core_write</use></connection></complexworld_read>
</resources>

文件都在什么位置

在PHP5.3及命名空间广泛使用之前,Magento依然会保持<classname />与路径之间的相对关系,并保证你创建了正确命名的目录结构及类文件。在配置任何<classname />或URI之后,你会发现,当在控制器中实例化那些还未存在的类时,会非常方便。通过配置文件,PHP会抛出异常告诉它无法找到该文件,并且会附带有该文件的相对路径。一起试下在Index控制器中实例化尚未创建的模型。


  1. public function indexAction() {

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

  3. $weblog2->load(1);

  4. var_dump($weblog2);

  5. }

和上面说到的一样,系统抛出以下异常,

Warning: include(Magentotutorial/Complexworld/Model/Eavblogpost.php) [function.include]: failed to open stream: No such file or directory�0�2 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�0�2 in /Users/username/Sites/magento.dev/lib/Varien/Autoload.php on line 93

我们就能知道配置文件出错了,因为Magento试图在核心文件中查找该模块,而非local路径中查找。

了解上述内容之后,我们开始创建模型类。在以下路径创建类文件,并添加以下代码。

File: app/code/local/Magentotutorial/Complexworld/Model/Eavblogpost.php:


  1. class Rui_Complexworld_Model_Eavblogpost extends Mage_Core_Model_Abstract {

  2. protected function _construct() {

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

  4. }

  5. }

再一次废话,模型本身是与模型资源独立的。无论是普通模型还是EAV模型都继承自同一个类。模型资源是区别它们的原因。

清空Magento缓存,刷新页面,你会看到一个警告。

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

很明显,是时候为模型创建模型资源了。在以下路径创建模型资源文件,并添加如下代码。

File: app/code/local/Magentotutorial/Complexworld/Model/Resource/Eav/Mysql4/Eavblogpost.php:


  1. class Rui_Complexworld_Model_Resource_Eav_Mysql4_Eavblogpost extends Mage_Eav_Model_Entity_Abstract {

  2. public function _construct() {

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

  4. $resource->setType('complexworld_eavblogpost');

  5. $resource->setConnection(

  6. $resource->getConnection('complexworld_read'),

  7. $resource->getConnection('complexworld_write')

  8. );

  9. }

  10. }

这里,终于看到普通的模型资源和EAV模型资源的一些区别了。首先,这里扩展的是Mage_Eav_Model_Entity_Abstract类。另外,该类中虽然和普通模型资源一样也使用了_construct()方法,却没有_init()方法。取而代之,我们需要自己处理init()的一些功能。这意味着,我们需要告诉EAV模型资源使用哪一种链接资源,并且传递唯一标示符到对象的setType()方法中。

与普通模型资源类的另外一个不同点是,_construct()不是抽象方法,这主要是为了与Magento老版本之间的兼容问题。

清空Magento缓存,刷新页面,看下系统抛出的新异常。

Invalid entity_type specified: complexworld_eavblogpost

系统提示其无法找到叫complexworld_eavblogpost的实体类型(entity_type)。这个值是你在setType()方法中传递的参数。

每个实体(Entity)都有一个类型。类型让EAV系统知道模型在使用哪些属性,并允许系统连接存储那些属性值的表。所以这里我们需要让Magento意识到我们添加了一个新的实体类型。首先,我们看下eav_entity_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: default
default_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_types),该表中的唯一识别complexworld_eavblogpost身份的是entity_type_code列。

系统及应用 Systems and Applications

这里有必要解释下Magento系统最为重要的一个概念。想象下在你面前的这台电脑,Windows,Linux,Mac等是系统-Systems;你的浏览器(FF,IE,Opera等)是应用-Application。

这里同样省略一段,讲述了系统与应用之间的一些关系,不太能理解。

创建启动资源

手动创建表并插入相关数据确实没问题,不过推荐使用Magento启动资源(Setup Resource)。Magento的启动资源提供了一系列的助手方法,能够自动创建表及数据,以使系统正常运行。

首先,我们将启动资源写入配置文件。

<complexworld_setup><setup><module>Magentotutorial_Complexworld</module><class>Magentotutorial_Complexworld_Entity_Setup</class></setup><connection><use>core_setup</use></connection>
</complexworld_setup>

然后,在下列路径,创建启动资源类文件,并写入如下代码。

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


  1. class Magentotutorial_Complexworld_Entity_Setup extends Mage_Eav_Model_Entity_Setup {

  2. }

注意这个类继承的是Mage_Eav_Model_Entity_Setup而不是Mage_Core_Model_Resource_Setup。

最后,设置安装脚本。如果你还不是太熟悉这里的命名约定,建议你复习下第六章的内容。在下列路径中创建安装脚本,并写入如下代码,

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

throw new Exception("This is an exception to stop the installer from completing");

清空Magneto缓存,重新刷新页面,上述代码中的异常会被系统抛出,这说明启动资源已经被成功配置了。

注意:我们会逐步完成安装脚本。如果你认真学习了第六章的知识,你会了解到,让系统重新运行安装脚本之前,必须删除core_resource表中的相关模块并清空缓存。在下文中,每修改一次安装脚本,都记得要执行上述步骤。

添加实体类型 Entity Type

为了添加实体类型,首先需要将下列代码添加到安装脚本中,不要忘记移除上面代码中的异常,然后刷新任意页面。


  1. $installer = $this;

  2. $installer->addEntityType('complexworld_eavblogpost',Array(

  3. //entity_mode is the URL you'd pass into a Mage::getModel() call

  4. 'entity_model' =>'complexworld/eavblogpost',

  5. //blank for now

  6. 'attribute_model' =>'',

  7. //table refers to the resource URI complexworld/eavblogpost

  8. //<complexworld_resource_eav_mysql4>...<eavblogpost><table>eavblog_posts</table>

  9. 'table' =>'complexworld/eavblogpost',

  10. //blank for now, but can also be eav/entity_increment_numeric

  11. 'increment_model' =>'',

  12. //appears that this needs to be/can be above "1" if we're using eav/entity_increment_numeric

  13. 'increment_per_store' =>'0'

  14. ));

在这段安装脚本中,我们调用了addEntityType()方法。该方法允许我们传递实体类型及相关参数,并设置它的默认值。运行此脚本之后,在eav_attribute_group,eav_attribute_set和eav_entity_type表中会多出新的记录。再次刷新complexworld页面,页面会显示如下错误。

SQLSTATE[42S02]: Base table or view not found: 1146 Table ‘magento.eavblog_posts’ doesnt exist

创建表

现在,我们已经成功让Magento系统意识到新创建的实体类型。然后,我们需要添加一些数据库表用来存储所有的实体值,并继续添加配置文件,让系统认识到这些表的存在。

如果你曾经观察过Magento核心文件中的安装脚本,你应该能够看到一些用于手动创建EAV模型表的SQL代码。幸运的是,这都已经成为历史了。启动资源拥有一个createEntityTables()方法,该方法能够帮助自动创建我们需要的表,并添加相应的配置文件到系统中。在启动资源中添加如下代码。


  1. $installer->createEntityTables(

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

  3. );

createEntityTables()方法接收两个参数。第一个是表名,第二个是相关配置。我们使用启动资源的getTable()方法从配置文件中获取表名。如果你一直认真学习本系列教程,你能猜到这里相当于字符串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 DESC LIMIT 1 \G
*************************** 1. row ***************************
attribute_set_id: 65
entity_type_id: 37
attribute_set_name: Default
sort_order: 6

再次重新刷新complexworld页面。

http://example.com/complexworld

这次不会再有任何的警告或者错误了,恭喜你,配置成功!

添加属性

差不多到最后一步了,我们需要在启动资源中告诉Magento,新建的模型中需要哪些属性。对于EAV模型来说,添加属性这一步相当于在普通数据表中添加新的字段。这一步中,我们感兴趣的方法有两个,installEntities()和getDefaultEntites()。

这两个方法的命名字面上看起来会让人困惑。毕竟,我们不是刚刚在Magento中配置了实体吗?现在需要再来一遍?

实际上,上一小节中,我们只是添加了一个实体类型到Magento系统中。而这里,我们需要做的是,添加一个属于该实体类型的实体到系统中。当然,根据需要,你非常可能会添加N多个属于该实体类型的实体。开始吧,将这些方法添加到启动资源中。


  1. class Magentotutorial_Complexworld_Entity_Setup extends Mage_Eav_Model_Entity_Setup {

  2. public function getDefaultEntities()

  3. {

  4. die("Calling ".__METHOD__);

  5. }

  6. }

在脚本结尾,添加如下代码。

$installer->installEntities();

刷新页面,能够看到上面代码中输出的内容。这是我们希望看到的。即在安装脚本中调用installEntities()方法,会运行启动资源中的方法。

Calling Magentotutorial_Complexworld_Entity_Setup::getDefaultEntities

另外一种可能,会看到系统抛出的异常,类似下面这种。

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

如果出现这种情况的话,也不要意外。因为你刷新页面之后,再次运行了启动脚本,并且调用了createEntityTables()方法。虽然Magento会自动生成DROP及CREATE Table,不幸的是它拿具有外键关系的表没辙。

解决这个情况很简单,只需要在运行启动脚本之前,注视掉createEntityTables()方法即可。重申一下,在一般情况下,安装脚本一般只会运行一次,并且createEntityTables也只会被调用一次。

配置新添加的实体

当调用installEntites()方法时,Magento系统会再后台运行很多操作,来正常安装实体。在那之前,必须让它知道需要创建的实体及其相关配置。通常情况下,我们会使用getDefaultEntities()方法返回这些信息。当然,也可以通过installEntities的参数配置进行安装,不过建议大家遵循Magento核心代码的一种约定。

首先,添加Eavblogpost实体,并设置一个title属性。


  1. class Rui_Complexworld_Entity_Setup extends Mage_Eav_Model_Entity_Setup {

  2. public function getDefaultEntities() {

  3. return array (

  4. 'complexworld_eavblogpost' => array(

  5. 'entity_model'�0�2�0�2�0�2�0�2�0�2 => 'complexworld/eavblogpost',

  6. 'attribute_model'�0�2�0�2 => '',

  7. 'table'�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => 'complexworld/eavblogpost',

  8. 'attributes'�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => array(

  9. 'title' => array(

  10. //the EAV attribute type, NOT a mysql varchar

  11. 'type'�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => 'varchar',

  12. 'backend'�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => '',

  13. 'frontend'�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => '',

  14. 'label'�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => 'Title',

  15. 'input'�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => 'text',

  16. 'class'�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => '',

  17. 'source'�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => '',

  18. // store scope == 0

  19. // global scope == 1

  20. // website scope == 2

  21. 'global'�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => 0,

  22. 'visible'�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => true,

  23. 'required'�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => true,

  24. 'user_defined'�0�2�0�2�0�2�0�2�0�2 => true,

  25. 'default'�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => '',

  26. 'searchable'�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => false,

  27. 'filterable'�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => false,

  28. 'comparable'�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => false,

  29. 'visible_on_front'�0�2 => false,

  30. 'unique'�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => false,

  31. ),

  32. ),

  33. )

  34. );

  35. }

  36. }

很复杂的一段代码,接下来,一点点分析。

getDefaultEntities()返回值

该方法返回键值对数组。键是实体类型的名字,也就是在$installer->addEntityType()方法中设置的参数一。值是一个描述该实体的数组。

描述实体的数组

描述实体的数组同样是一些列的键值对。类似如下代码,


  1. 'entity_model'�0�2�0�2�0�2�0�2�0�2 => 'complexworld/eavblogpost',

  2. 'attribute_model'�0�2�0�2 => '',

  3. 'table'�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => 'complexworld/eavblogpost',

  4. 'attributes'�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => array(

这些值应该匹配$installer->addEntityType()中的参数,attributes键值需要包含另外一个数组,用来描述添加的属性本身。

描述属性本身的数组

上面说到attributes键的值是一个数组,该数组的键是属性名,值是上述这一系列数组最终的数据键值对,用来定义该属性的相关信息。这里,我们定义一个简单的属性,不过,这里就是你定义所有属性的地方,根据程序需要,可以定义无数个属性。

描述属性数组的详细讲解

先来看下这段代码。


  1. //the EAV attribute type, NOT a mysql varchar

  2. 'type'�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => 'varchar',

  3. 'backend'�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => '',

  4. 'frontend'�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => '',

  5. 'label'�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => 'Title',

  6. 'input'�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => 'text',

  7. 'class'�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => '',

  8. 'source'�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => '',

  9. // store scope == 0

  10. // global scope == 1

  11. // website scope == 2

  12. 'global'�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => 0,

  13. 'visible'�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => true,

  14. 'required'�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => true,

  15. 'user_defined'�0�2�0�2�0�2�0�2�0�2 => true,

  16. 'default'�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => '',

  17. 'searchable'�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => false,

  18. 'filterable'�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => false,

  19. 'comparable'�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => false,

  20. 'visible_on_front'�0�2 => false,

  21. 'unique'�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 => false,

这里,需要承认的是,我也没有搞太清楚上述键值对的意思。许多都涉及到了Magento后端UI的驱动特征,比如label和input。Magento工程师将UI实现与后端模型结构紧紧绑定起来。后面这段不翻译了,直接上英文。

Unfortunately, this is where your author has to ‘fess up and tell you he’s unsure what 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 remain opaque to outsiders, particularly web developers who’ve been chanting the mantra of back-end/front-end separation for near on a decade.

在上述代码中,需要注意的最重要的一个属性是,

'type' => 'varchar'

这个键值对定义了该属性将要包含的数据类型。还记得之前通过启动资源自动创建的那几张用来存储不同数据类型的表吗?

eavblog_posts_datetime
eavblog_posts_decimal
eavblog_posts_int
eavblog_posts_text
eavblog_posts_varchar

注意,这些表的命名并不一定和MySQL中的数据类型相关,而是EAV属性类型,这些容易引起误会的名字(varchar,decimal),只是这些表中存储的值的类型的一个象征性叫法。

好了,到此为止,所有一切都已经搞定了。最后一次刷新页面,重新运行安装脚本。在成功调用了installEntities()方法之后,Magento系统会,

  • 在eav_attribute表中新增tittle一行数据
  • 在eav_attribute_atribute中新增一行数据

把一切整合起来

很可能,这是你做程序以来写的最烂的一个Blog程序,不过,让我们继续吧。添加一些新的记录到表中,然后通过收集将其从数据库中读取出来。添加下列两个方法到Index控制器中。


  1. public function populateEntriesAction() {

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

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

  4. $weblog2->setTitle('This is a test '.$i);

  5. $weblog2->save();

  6. }

  7. echo 'Done';

  8. }

  9. public function showcollectionAction() {

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

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

  12. $entries->load();

  13. foreach($entries as $entry) {

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

  15. echo '<h1>'.$entry->getTitle().'</h1>';

  16. }

  17. echo '<br>Done<br>';

  18. }

访问populateEntries()方法,添加几条数据库记录。

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

运行成功之后,可以打开数据库,会发现eavblog_posts表中增加了十行记录。

mysql> 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�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 | updated_at�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 | is_active |
+———–+—————-+——————+————–+———–+———-+———————+———————+———–+
|�0�2�0�2�0�2�0�2�0�2�0�2�0�2 10 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 31 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 | 2009-12-06 08:36:41 | 2009-12-06 08:36:41 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 1 |
|�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 9 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 31 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 | 2009-12-06 08:36:41 | 2009-12-06 08:36:41 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 1 |
|�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 8 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 31 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 | 2009-12-06 08:36:41 | 2009-12-06 08:36:41 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 1 |
|�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 7 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 31 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 | 2009-12-06 08:36:41 | 2009-12-06 08:36:41 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 1 |
|�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 6 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 31 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 | 2009-12-06 08:36:41 | 2009-12-06 08:36:41 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 1 |
|�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 5 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 31 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 | 2009-12-06 08:36:41 | 2009-12-06 08:36:41 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 1 |
|�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 4 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 31 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 | 2009-12-06 08:36:41 | 2009-12-06 08:36:41 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 1 |
|�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 3 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 31 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 | 2009-12-06 08:36:41 | 2009-12-06 08:36:41 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 1 |
|�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 2 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 31 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 | 2009-12-06 08:36:41 | 2009-12-06 08:36:41 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 1 |
|�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 1 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 31 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 | 2009-12-06 08:36:41 | 2009-12-06 08:36:41 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 1 |
+———–+—————-+——————+————–+———–+———-+———————+———————+———–+

同样的,在eavblog_posts_varchar表中,也会新增十行。

mysql> select * from eavblog_posts_varchar order by value_id DESC;
+———-+—————-+————–+———-+———–+——————+
| value_id | entity_type_id | attribute_id | store_id | entity_id | value�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 |
+———-+—————-+————–+———-+———–+——————+
|�0�2�0�2�0�2�0�2�0�2�0�2 10 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 31 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 933 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2 10 | This is a test 9 |
|�0�2�0�2�0�2�0�2�0�2�0�2�0�2 9 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 31 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 933 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 9 | This is a test 8 |
|�0�2�0�2�0�2�0�2�0�2�0�2�0�2 8 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 31 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 933 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 8 | This is a test 7 |
|�0�2�0�2�0�2�0�2�0�2�0�2�0�2 7 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 31 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 933 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 7 | This is a test 6 |
|�0�2�0�2�0�2�0�2�0�2�0�2�0�2 6 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 31 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 933 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 6 | This is a test 5 |
|�0�2�0�2�0�2�0�2�0�2�0�2�0�2 5 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 31 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 933 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 5 | This is a test 4 |
|�0�2�0�2�0�2�0�2�0�2�0�2�0�2 4 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 31 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 933 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 4 | This is a test 3 |
|�0�2�0�2�0�2�0�2�0�2�0�2�0�2 3 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 31 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 933 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 3 | This is a test 2 |
|�0�2�0�2�0�2�0�2�0�2�0�2�0�2 2 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 31 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 933 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 2 | This is a test 1 |
|�0�2�0�2�0�2�0�2�0�2�0�2�0�2 1 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 31 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 933 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2 0 |�0�2�0�2�0�2�0�2�0�2�0�2�0�2�0�2 1 | This is a test 0 |
+———-+—————-+————–+———-+———–+——————+

仔细看的话,会发现在eavblog_posts_varchar表中的记录,是通过entity_id关联到eavblog_posts表中。

最后,让我们在浏览器中通过showCollection()方法读取刚刚插入的内容。

http://magento.dev/index.php/complexworld/index/showCollection

很显然,会给出以下提示,

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

不算陌生的提示吧,我们还需要创建一个收集对象!离成功很近了!添加以下代码到下列路径中。

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


  1. class Rui_Complexworld_Model_Resource_Eav_Mysql4_Eavblogpost_Collection extends Mage_Eav_Model_Entity_Collection_Abstract

  2. {

  3. protected function _construct()

  4. {

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

  6. }

  7. }

这段代码相当简单,但是收集对象已经成功创建了,刷新页面,好了,数据调用成功了!!!

读取哪些属性?

观察细致的程序员会发现这个收集的不同。

因为EAV模型的读取通常会涉及到大型的SQL语句,你必须指定想要读取模型的哪些字段。这样才能避免无效读取。当然,如果你不在乎系统性能的损耗,也可以用星号来读取所有字段。


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

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

是时候跳出这一切了!

我认为,到这里为止,我们已经学习到了基本的Magento架构及EAV模型的知识。当然,关于EAV还有很多需要学习。下面是关于更高级别,更详细的一些学习主题,大家可以参考一下内容继续深入学习Magento系统。

  • EAV属性:属性不局限于datetime,decimal,int,text和varchar。你可以创建新类来构建不同的属性。这也是attribute_model为空的原因。
  • 收集过滤(Collection Filtering):EAV模型的收集可能会非常复杂,特别是当你在处理上面提到过的non-simple属性时。在读取数据之前,你需要对收集使用addAttributeToFilter()方法。
  • Magento EAV 层次:Magento不仅实现了EAV模型,更建立了适应电子商务店铺功能的紧密层级。

好了,是要结束的时候了,虽然还有一章,但是基本的内容都已经在这里了。来个深呼吸,想想,这一切只是编程罢了。任何事情都有它存在的理由,我们要做的,仅仅是搞明白为什么~~

Magento开发文档(七):Magento EAV模型相关推荐

  1. Magento开发文档(一):Magento入门

    开始之前,首先声明下,Magento开发者手册由Alan Storm发表在Magento官方网站上.总共分八个部分,由浅入深的介绍了Magento的MVC架构及Magento中使用的比较特殊的EAV模 ...

  2. Magento开发文档(四)Magento 布局、块 、模板

    刚入门magento的开发者容易吧布局和视图给混淆. 本文将看看Magento的Layout/Block的做法, 并告诉您如何将其融入Magento的MVC的世界观. 与许多流行的MVC系统相比,Ma ...

  3. 软件工程各类开发文档的作用

    内容提纲:(1)软件工程各类开发文档的目的与作用详解 (2)操作手册与用户手册的区别. 一.<可行性研究报告> 可行性研究报告是在制定研发项目之前,以全面.系统的分析为主要方法,经济效益为 ...

  4. 【IT基础】常见的开发文档

    Perface 随着技术的进步,小作坊式的软件开发年代已经过去.目前的软件开发能力在不断提升,用户对软件的功能和性能要求也越来越高,软件开发质量受到关注. 在软件开发过程中,各种数据和代码的管理需要经 ...

  5. 开源轻量级办公系统Sandbox介绍以及配套开发文档连载

    1.Sandbox介绍 Sandbox是一个基于django框架开发的轻量级办公平台,主要模块有:权限控制.资产(库存)管理.设备管理.客户信息管理和工单流程管理,其目的在于建立一套规范化.统一化和清 ...

  6. CRMEB开发文档及目录结构

    CRMEB 开发文档及目录结构 官网 CRMEB v2.6开源地址:http://link.crmeb.net/u/lingting 完整帮助文档:http://help.crmeb.net QQ群: ...

  7. php 后端 轻量 框架,GitHub - 22cloud/mixphp: 轻量 PHP 框架,基于 Swoole 的常驻内存型 PHP 高性能框架 (开发文档完善)...

    高性能 • 轻量级 • 命令行 MixPHP 是什么 MixPHP 秉承 "普及 PHP 常驻内存型解决方案,促进 PHP 往更后端发展" 的理念而创造,采用 Swoole 扩展作 ...

  8. 开发文档怎么编写_PoC 编写指南

    什么是 PoC PoC(全称: Proof of Concept), 中文译作概念验证.在安全界,你可以理解成为漏洞验证程序.和一些应用程序相比,PoC 是一段不完整的程序,仅仅是为了证明提出者的观点 ...

  9. Intel开发文档导读

    Intel开发文档导读 本文尝试追踪Intel开发文档的历史变迁,从最初的8086/88参考手册按时代顺序逐步演进到最新的Intel® 64 and IA-32 Architectures Softw ...

最新文章

  1. android 代码设置 键盘适应_Android自适应软键盘的Dialog以及监听软键盘弹起
  2. Integrating Spring and EHCache
  3. 闲话WPF之二六(WPF性能优化点)
  4. Java并发编程(3):线程挂起、恢复与终止的正确方法(含代码)
  5. 用纸筒做机器人_幼儿园简单手工:纸盒子回收利用做机器人(步骤图解)
  6. 【转】Sql server锁,独占锁,共享锁,更新锁,乐观锁,悲观锁
  7. 深入探究JFreeChart
  8. ekho tts 下载_Ekho TTS简介(文本转语音引擎).pptx
  9. 如何在结构体里面套结构体_Rust 学习笔记-13 Rust 结构体
  10. 过程生产excel_生产型企业,自己开发管理系统,该如何选型?
  11. java怎么这么难学6_Java难吗?
  12. 使用c语言生成斐波拉契(Fibonacci)数列的前20项并输出
  13. 2018年中国互联网企业百强榜单揭晓
  14. PHP yield简介
  15. jsp入门配置Tomcat
  16. DIY 简单又好吃的香果魔芋
  17. 520程序员的浪漫表白
  18. android手机屏幕分辨率
  19. 高仿QQ电脑管家8 界面
  20. 产品总监(经理)应该具备的能力

热门文章

  1. sap pi的操作1
  2. 四边形可以分为几类_四边形的分类
  3. PspTerminateProcess 结束冰刃进程
  4. WEEK 15 B ZJM 与生日礼物
  5. 客户端呼叫Flash Medis Server3服务端入门
  6. 罗杨美慧 20190905-1 每周例行报告
  7. mysql frm ibd 创建表_MySQL数据库实现从.frm文件和.ibd文件恢复数据表方法
  8. vivos9设置繁体语言方法分享
  9. Carson带你学Android:源码解析自定义View Draw过程
  10. 寻票软件医院挂号攻略