Magento 中有两种持久化模型,简单类型和 **Entity**–**attribute**–**value** (**EAV**) 类型。术语 entity (后面统一译做实体) 可以表示其中的任意一种模型,我们可以将 entity 看成一种持久化模型。 <br />Magento_Newsletter 模块中的 Subscriber 实体是简单模型的一个例子,我们可以观察到它包含以下内容: > - 一个继承自 `Magento\Framework\Model\AbstractModel` 的模型类 `Magento\Newsletter\Model\Subscriber` > - 一个继承自 `Magento\Framework\Model\ResourceModel\Db\AbstractDb` 的资源类 `Magento\Newsletter\Model\ResourceModel\Subscriber` > - 一个继承自 `Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection` 的 collection 类 `Magento\Newsletter\Model\ResourceModel\Subscriber\Collection` <br />Magento_Customer 模块的 Customer 实体是 EAV 模型的一个例子,它由以下几部分组成。 > - 一个继承自 `Magento\Framework\Model\AbstractModel` 类的模型类 `Magento\Customer\Model\Customer` > - 一个继承自 `Magento\Eav\Model\Entity\VersionControl\AbstractEntity` 类的 resource model 类 `Magento\Customer\Model\ResourceModel\Customer` > - 一个继承自 `Magento\Eav\Model\Entity\Collection\VersionControl\AbstractCollection` 类的 collection 类 `Magento\Customer\Model\ResourceModel\Customer\Collection` <br />EAV 模型和普通模型的不同之处本质上在于 resource model 和 collection 类的不同,resource model 用来连接数据库,或者称作持久化器,如果你愿意的话。<br /> <br />当一个订阅者(subscriber)被保存时,对应的数据被水平地保存在数据库中,从订阅者的模型中获取的数据直接输出到单张 `newsletter_subscriber` 表中。<br /> <br />当保存顾客(customer)信息时,数据被水平地保存在数据库中,从顾客模型中获取的数据被输出到一下几张表中: > - customer_entity > - customer_entity_datetime > - customer_entity_decimal > - customer_entity_int > - customer_entity_text > - customer_entity_varchar <br />决定个体属性存储在什么地方是由包含在 `eav_attribute.backend_type` 的列决定的,`SELECT DISTINCT backend_type FROM eav_attribute;` 查询语句获取到的数据展示如下: > - 存储在 `<entityName>_entity` 表中的 `static` 属性值 > - 存储在 `<entityName>_entity_varchar` 表中的 `varchar` 属性值 > - 存储在 `<entityName>_entity_int` 表中的 `int` 属性值 > - 存储在 `<entityName>_entity_text` 表中的 `text` 属性值 > - 存储在 `<entityName>_entity_datetime` 表中的 `datetime` 属性值 > - 存储在 `<entityName>_entity_decimal` 表中的 `decimal` 属性值 紧挨着 `eav_attribute` 表,其他相关信息散落在其他 `eav_*` 表中,最重要的就是 `eav_attribute_*` 表,主要有: > - eav_attribute > - eav_attribute_group > - eav_attribute_label > - eav_attribute_option > - eav_attribute_option_swatch > - eav_attribute_option_value > - eav_attribute_set <br />`SELECT entity_type_code, entity_model FROM eav_entity_type;` 查询表明下面的几个 Magento 实体采用的是 EAV 模型: > - `customer`: `Magento\Customer\Model\ResourceModel\Customer` > - `customer_address`: `Magento\Customer\Model\ResourceModel\Address` > - `catalog_category`: `Magento\Catalog\Model\ResourceModel\Category` > - `catalog_product`: `Magento\Catalog\Model\ResourceModel\Product` > - `order`: `Magento\Sales\Model\ResourceModel\Order` > - `invoice`: `Magento\Sales\Model\ResourceModel\Order\Invoice` > - `creditmemo`: `Magento\Sales\Model\ResourceModel\Order\Creditmemo` > - `shipment`: `Magento\Sales\Model\ResourceModel\Order\Shipment` 但是,以上的几个模型并没有全部使用 EAV 模型,SELECT DISTINCT entity_type_id FROM eav_attribute; 查询出来的只有下面几个: > - customer > - customer_address > - catalog_category > - catalog_product <br /> <br />EAV 模型本身就更加复杂,它的主要使用场景是动态创建属性,最理想的是通过管理接口创建属性,就像产品属性的创建一样。但是在绝大多数时候,简单模型就够用了。<br /> ### 创建一个简单模型 <br />和 EAV 模型不同,创建一个简单模型十分直接。下面就一起来创建一个 Log 实体的 model、resource model 和 collection。<br /> <br />我们首先创建 <MAGELICIOUS_DIR>/Core/Model/Log.php 并填充以下内容: ```php class Log extends \Magento\Framework\Model\AbstractModel { protected $_eventPrefix = 'magelicious_core_log'; protected $_eventObject = 'log'; protected function _construct() { $this->_init(\Magelicious\Core\Model\ResourceModel\Log::class); } } ``` $_eventPrefix 和 $_eventObject 的使用并不是必须的,但是推荐使用。这些值会在 Magento\Framework\Model\AbstractModel 的事件派发中被使用,而且也给我们的模块添加了可扩展性。尽管 Magento 使用 <ModuleName>_<ModelName> 惯例来给 $_eventPrefix 赋值,但是使用 <VendorName>_<ModuleName>_<ModelName> 会更加安全。 $_eventObject 的名称按照惯例是模型本身的名称。<br /> <br />我们接下来创建 <MAGELICIOUS_DIR>/Core/Model/ResourceModel/Log.php 文件并填充以下内容: ```php class Log extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb { protected function _construct() { $this->_init('magelicious_core_log', 'entity_id'); } } ``` _init 方法有 2 个参数:magelicious_core_log 赋值给 $mainTable ,entity_id 赋值给 $idFieldName 参数 ,$idFieldName 的值是对应表的主键名称。现在 magelicious_core_log 表还不存在,我们将在后面创建它。<br /> <br />接下来我们创建 <MAGELICIOUS_DIR>/Core/Model/ResourceModel/Log/Collection.php 并填充以下内容: ```php class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection { protected function _construct() { $this->_init( \Magelicious\Core\Model\Log::class, \Magelicious\Core\Model\ResourceModel\Log::class ); } } ``` _init 方法有 2 个参数:$model 和 $resourceModel 的字符串表示,Magento 用 <FULLY_QUALIFIED_CLASS_NAME>::class 这种语法来表示,这种方式相比直接传递类名更好。 <br /> <br /> ### 需要记住的方法 EAV 模型和简单模型都继承自 Magento\Framework\Model\AbstractModel 类,Magento\Framework\Model\AbstractModel 又继承自 Magento\Framework\DataObject ,DataObject 又几个简洁的值得记住的方法。<br /> <br />下面的一组方法和数据转换相关: > - toArray: 将对象转换为数组,数组中的键为请求的键 > - toXml: 将对象转换为 > - toJson: 将对象转换为 JSON > - toString: 将对象数据按照预定义格式转换为字符串 > - serialize: 将对象按照定义好的键和值转换为字符串 <br />下面的这些方法都是通过魔术方法 __call 实现的,主要有以下几种语法: > - get<AttributeName>, 例如 $object->getPackagingOption() > - set<AttributeName>, 例如 $object->setPackagingOption('plastic_bag') > - uns<AttributeName>, 例如 $object->unsPackagingOption() > - has<AttributeName>, 例如 $object->hasPackagingOption() <br />为了尝试上面的几个方法,我们手动创建 magelicious_core_log 表,SQL 如下: ```sql CREATE TABLE `magelicious_core_log` ( `entity_id` int(10) unsigned NOT NULL AUTO_INCREMENT, `severity_level` varchar(24) NOT NULL, `note` text NOT NULL, `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`entity_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ``` 因为继承自 DataObject ,所以即使 Magelicious\Core\Model\Log 模型是空的,也可以利用下面的方法来保存数据: ```php $log->setCreatedAt(new \DateTime()); $log->setSeverityLevel('info'); $log->setNote('Just Some Note'); $log->save(); ``` 上面的例子能正常运行,但是模型类远远不止这些。在创建模型的过程中手动创建数据表并不可行。Magento 中采用了 setup 脚本的机制来创建数据表。<br />