Magento 2 EAV Model 介紹 (3) – 在 Magento 2 使用程式新增 entity type
之前的文章內,我們介紹了 Eav Model 的關係,以及如何使用程式新增 Attribute,但是在 Magento 裡面使用了許多的 Entity Type,今天要透過程式的方式來教大家如何新增 Entity Type 及其相關的資料表,讓自己的定義的 Entity Type 也可以存取 Attribute 屬性唷!
※Magento 版本:2.1 以上
1.新增資料表
要建立 Entity type,必須依照 Magento 的規則建立相對應的資料表,我們以建立 Post 的 Entity type 為例,共需要產生以下 6 張資料表
* post_entity
* post_entity_datetime
* post_entity_decimal
* post_entity_int
* post_entity_text
* post_entity_varchar
要建立資料表,我們必須先在 module 的 Setup 資料夾內建立 InstallSchema.php,裡面程式碼如下:
<?php namespace Grayson\Post\Setup; use Magento\Framework\DB\Adapter\AdapterInterface; use Magento\Framework\DB\Ddl\Table; use Magento\Framework\Setup\InstallSchemaInterface; use Magento\Framework\Setup\ModuleContextInterface; use Magento\Framework\Setup\SchemaSetupInterface; /** * Class InstallSchema * @package Grayson\Post\Setup */ class InstallSchema implements InstallSchemaInterface { const POST_ENTITY = 'post_entity'; /** * @param SchemaSetupInterface $setup * @param ModuleContextInterface $context * @throws \Zend_Db_Exception */ public function install(SchemaSetupInterface $setup, ModuleContextInterface $context) { $installer = $setup; $installer->startSetup(); } }
(1) post_entity 資料表
這張是實體 ( Entity ) 主表,所有的紀錄都會在這邊產生,程式如下:
/** * @param SchemaSetupInterface $setup * @param string $entity * @return InstallSchema * @throws \Zend_Db_Exception */ private function addEntityTable(SchemaSetupInterface $setup, string $entity): InstallSchemaInterface { $table = $setup->getConnection() ->newTable($setup->getTable($entity)) ->addColumn( 'entity_id', Table::TYPE_INTEGER, null, [ 'identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true ], 'Entity ID' )->addColumn( 'created_at', Table::TYPE_TIMESTAMP, null, [ 'nullable' => false, 'default' => Table::TIMESTAMP_INIT ], 'Creation Time' )->addColumn( 'updated_at', Table::TYPE_TIMESTAMP, null, [ 'nullable' => false, 'default' => Table::TIMESTAMP_INIT_UPDATE ], 'Update Time' ); $setup->getConnection()->createTable($table); return $this; }
(2) post_entity_datetime 資料表
/** * @param SchemaSetupInterface $setup * @param string $entity * @return InstallSchema * @throws \Zend_Db_Exception */ private function addDatetimeTable(SchemaSetupInterface $setup, string $entity): InstallSchemaInterface { $table = $setup->getConnection() ->newTable($setup->getTable($entity . '_datetime') )->addColumn( 'value_id', Table::TYPE_INTEGER, null, ['identity' => true, 'nullable' => false, 'primary' => true], 'Value ID' )->addColumn( 'attribute_id', Table::TYPE_SMALLINT, null, ['unsigned' => true, 'nullable' => false, 'default' => '0'], 'Attribute Id' )->addColumn( 'store_id', Table::TYPE_SMALLINT, null, ['unsigned' => true, 'nullable' => false, 'default' => '0'], 'Store ID' )->addColumn( 'entity_id', Table::TYPE_INTEGER, null, ['unsigned' => true, 'nullable' => false, 'default' => '0'], 'Entity Id' )->addColumn( 'value', Table::TYPE_DATETIME, null, [], 'value' )->addIndex( $setup->getIdxName($entity . '_decimal', ['entity_id', 'attribute_id', 'store_id'], AdapterInterface::INDEX_TYPE_UNIQUE), ['entity_id', 'attribute_id', 'store_id'], ['type' => AdapterInterface::INDEX_TYPE_UNIQUE] )->addIndex( $setup->getIdxName($entity . '_datetime', ['store_id']), ['store_id'] )->addIndex( $setup->getIdxName($entity . '_datetime', ['attribute_id']), ['attribute_id'] )->addForeignKey( $setup->getFkName( $entity . '_datetime', 'attribute_id', 'eav_attribute', 'attribute_id' ), 'attribute_id', $setup->getTable('eav_attribute'), 'attribute_id', Table::ACTION_CASCADE )->addForeignKey( $setup->getFkName( $entity . '_datetime', 'entity_id', $entity, 'entity_id' ), 'entity_id', $setup->getTable($entity), 'entity_id', Table::ACTION_CASCADE )->addForeignKey( $setup->getFkName( $entity . '_datetime', 'store_id', 'store', 'store_id' ), 'store_id', $setup->getTable('store'), 'store_id', Table::ACTION_CASCADE ); $setup->getConnection()->createTable($table); return $this; }
(3) post_entity_decimal 資料表
/** * @param SchemaSetupInterface $setup * @param string $entity * @return InstallSchema * @throws \Zend_Db_Exception */ private function addDecimalTable(SchemaSetupInterface $setup, string $entity): InstallSchemaInterface { $table = $setup->getConnection() ->newTable($setup->getTable($entity . '_decimal')) ->addColumn( 'value_id', Table::TYPE_INTEGER, null, ['identity' => true, 'nullable' => false, 'primary' => true], 'Value ID' )->addColumn( 'attribute_id', Table::TYPE_SMALLINT, null, ['unsigned' => true, 'nullable' => false, 'default' => '0'], 'Attribute Id' )->addColumn( 'store_id', Table::TYPE_SMALLINT, null, ['unsigned' => true, 'nullable' => false, 'default' => '0'], 'Store ID' )->addColumn( 'entity_id', Table::TYPE_INTEGER, null, ['unsigned' => true, 'nullable' => false, 'default' => '0'], 'Entity Id' )->addColumn( 'value', Table::TYPE_DECIMAL, '12,4', [], 'value' )->addIndex( $setup->getIdxName($entity . '_decimal', ['entity_id', 'attribute_id', 'store_id'], AdapterInterface::INDEX_TYPE_UNIQUE), ['entity_id', 'attribute_id', 'store_id'], ['type' => AdapterInterface::INDEX_TYPE_UNIQUE] )->addIndex( $setup->getIdxName($entity . '_decimal', ['store_id']), ['store_id'] )->addIndex( $setup->getIdxName($entity . '_decimal', ['attribute_id']), ['attribute_id'] )->addForeignKey( $setup->getFkName( $entity . '_decimal', 'attribute_id', 'eav_attribute', 'attribute_id' ), 'attribute_id', $setup->getTable('eav_attribute'), 'attribute_id', Table::ACTION_CASCADE )->addForeignKey( $setup->getFkName( $entity . '_decimal', 'entity_id', $entity . '_entity', 'entity_id' ), 'entity_id', $setup->getTable($entity), 'entity_id', Table::ACTION_CASCADE )->addForeignKey( $setup->getFkName( $entity . '_decimal', 'store_id', 'store', 'store_id' ), 'store_id', $setup->getTable('store'), 'store_id', Table::ACTION_CASCADE ); $setup->getConnection()->createTable($table); return $this; }
(4) post_entity_int 資料表
<?php /** * @param SchemaSetupInterface $setup * @param $entity * @return InstallSchema * @throws \Zend_Db_Exception */ private function addIntTable(SchemaSetupInterface $setup, $entity): InstallSchemaInterface { $table = $setup->getConnection() ->newTable($setup->getTable($entity . '_int')) ->addColumn( 'value_id', Table::TYPE_INTEGER, null, ['identity' => true, 'nullable' => false, 'primary' => true], 'Value ID' )->addColumn( 'attribute_id', Table::TYPE_SMALLINT, null, ['unsigned' => true, 'nullable' => false, 'default' => '0'], 'Attribute Id' )->addColumn( 'store_id', Table::TYPE_SMALLINT, null, ['unsigned' => true, 'nullable' => false, 'default' => '0'], 'Store ID' )->addColumn( 'entity_id', Table::TYPE_INTEGER, null, ['unsigned' => true, 'nullable' => false, 'default' => '0'], 'Entity Id' )->addColumn( 'value', Table::TYPE_INTEGER, null, [], 'value' )->addIndex( $setup->getIdxName($entity . '_int', ['entity_id', 'attribute_id', 'store_id'], AdapterInterface::INDEX_TYPE_UNIQUE), ['entity_id', 'attribute_id', 'store_id'], ['type' => AdapterInterface::INDEX_TYPE_UNIQUE] )->addIndex( $setup->getIdxName($entity . '_int', ['store_id']), ['store_id'] )->addIndex( $setup->getIdxName($entity . '_int', ['attribute_id']), ['attribute_id'] )->addForeignKey( $setup->getFkName( $entity . '_int', 'attribute_id', 'eav_attribute', 'attribute_id' ), 'attribute_id', $setup->getTable('eav_attribute'), 'attribute_id', Table::ACTION_CASCADE )->addForeignKey( $setup->getFkName( $entity . '_int', 'entity_id', $entity, 'entity_id' ), 'entity_id', $setup->getTable($entity), 'entity_id', Table::ACTION_CASCADE )->addForeignKey( $setup->getFkName( $entity . '_int', 'store_id', 'store', 'store_id' ), 'store_id', $setup->getTable('store'), 'store_id', Table::ACTION_CASCADE ); $setup->getConnection()->createTable($table); return $this; }
(5) post_entity_text 資料表
<?php /** * @param SchemaSetupInterface $setup * @param string $entity * @return InstallSchema * @throws \Zend_Db_Exception */ private function addTextTable(SchemaSetupInterface $setup, string $entity): InstallSchemaInterface { $table = $setup->getConnection() ->newTable($setup->getTable($entity . '_text')) ->addColumn( 'value_id', Table::TYPE_INTEGER, null, ['identity' => true, 'nullable' => false, 'primary' => true], 'Value ID' )->addColumn( 'attribute_id', Table::TYPE_SMALLINT, null, ['unsigned' => true, 'nullable' => false, 'default' => '0'], 'Attribute Id' )->addColumn( 'store_id', Table::TYPE_SMALLINT, null, ['unsigned' => true, 'nullable' => false, 'default' => '0'], 'Store ID' )->addColumn( 'entity_id', Table::TYPE_INTEGER, null, ['unsigned' => true, 'nullable' => false, 'default' => '0'], 'Entity Id' )->addColumn( 'value', Table::TYPE_TEXT, 255, [], 'value' )->addIndex( $setup->getIdxName($entity . '_text', ['entity_id', 'attribute_id', 'store_id'], AdapterInterface::INDEX_TYPE_UNIQUE), ['entity_id', 'attribute_id', 'store_id'], ['type' => AdapterInterface::INDEX_TYPE_UNIQUE] )->addIndex( $setup->getIdxName($entity . '_text', ['store_id']), ['store_id'] )->addIndex( $setup->getIdxName($entity . '_text', ['attribute_id']), ['attribute_id'] )->addForeignKey( $setup->getFkName( $entity . '_text', 'attribute_id', 'eav_attribute', 'attribute_id' ), 'attribute_id', $setup->getTable('eav_attribute'), 'attribute_id', Table::ACTION_CASCADE )->addForeignKey( $setup->getFkName( $entity . '_text', 'entity_id', $entity, 'entity_id' ), 'entity_id', $setup->getTable($entity), 'entity_id', Table::ACTION_CASCADE )->addForeignKey( $setup->getFkName( $entity . '_text', 'store_id', 'store', 'store_id' ), 'store_id', $setup->getTable('store'), 'store_id', Table::ACTION_CASCADE ); $setup->getConnection()->createTable($table); return $this; }
(6) post_entity_varchar 資料表
<?php /** * @param SchemaSetupInterface $setup * @param $entity * @return InstallSchema * @throws \Zend_Db_Exception */ private function addVarcharTable(SchemaSetupInterface $setup, $entity): InstallSchemaInterface { $table = $setup->getConnection() ->newTable($setup->getTable($entity . '_varchar')) ->addColumn( 'value_id', Table::TYPE_INTEGER, null, [ 'identity' => true, 'nullable' => false, 'primary' => true ], 'Value ID' )->addColumn( 'attribute_id', Table::TYPE_SMALLINT, null, [ 'unsigned' => true, 'nullable' => false, 'default' => '0' ], 'Attribute Id' )->addColumn( 'store_id', Table::TYPE_SMALLINT, null, [ 'unsigned' => true, 'nullable' => false, 'default' => '0' ], 'Store ID' )->addColumn( 'entity_id', Table::TYPE_INTEGER, null, [ 'unsigned' => true, 'nullable' => false, 'default' => '0' ], 'Entity Id' )->addColumn( 'value', Table::TYPE_TEXT, 256, [], 'value' )->addIndex( $setup->getIdxName($entity . '_varchar', ['entity_id', 'attribute_id', 'store_id'], AdapterInterface::INDEX_TYPE_UNIQUE), ['entity_id', 'attribute_id', 'store_id'], ['type' => AdapterInterface::INDEX_TYPE_UNIQUE] )->addIndex( $setup->getIdxName($entity . '_varchar', ['store_id']), ['store_id'] )->addIndex( $setup->getIdxName($entity . '_varchar', ['attribute_id']), ['attribute_id'] )->addForeignKey( $setup->getFkName( $entity . '_varchar', 'attribute_id', 'eav_attribute', 'attribute_id' ), 'attribute_id', $setup->getTable('eav_attribute'), 'attribute_id', Table::ACTION_CASCADE )->addForeignKey( $setup->getFkName( $entity . '_varchar', 'entity_id', $entity, 'entity_id' ), 'entity_id', $setup->getTable($entity), 'entity_id', Table::ACTION_CASCADE )->addForeignKey( $setup->getFkName( $entity . '_varchar', 'store_id', 'store', 'store_id' ), 'store_id', $setup->getTable('store'), 'store_id', Table::ACTION_CASCADE ); $setup->getConnection()->createTable($table); return $this; }
(7) InstallSchema
接下來我們在 InsatallSchema 內使用剛剛建立的 function,並帶入 $installer 及 self::POST_ENTITY 變數,參考底下程式碼:
<?php namespace Grayson\Post\Setup; use Magento\Framework\DB\Adapter\AdapterInterface; use Magento\Framework\DB\Ddl\Table; use Magento\Framework\Setup\InstallSchemaInterface; use Magento\Framework\Setup\ModuleContextInterface; use Magento\Framework\Setup\SchemaSetupInterface; /** * Class InstallSchema * @package Grayson\Post\Setup */ class InstallSchema implements InstallSchemaInterface { const POST_ENTITY = 'post_entity'; /** * @param SchemaSetupInterface $setup * @param ModuleContextInterface $context * @throws \Zend_Db_Exception */ public function install(SchemaSetupInterface $setup, ModuleContextInterface $context) { $installer = $setup; $installer->startSetup(); $this->addEntityTable($installer, self::POST_ENTITY); $this->addDatetimeTable($installer, self::POST_ENTITY); $this->addDecimalTable($installer, self::POST_ENTITY); $this->addIntTable($installer, self::POST_ENTITY); $this->addTextTable($installer, self::POST_ENTITY); $this->addVarcharTable($installer, self::POST_ENTITY); } //... 略
2.新增 Model、Collection、ResouceModel
MVC 是一種現代化的設計模式,Model 則是 MVC 中的 M,它負責資料庫的操作,而 Magento 的 ORM 已經幫我們封裝好 Model 的 Class ,我們使用時僅需要繼承正確的抽象類別 ( Abstract Class ) 即可,而除了 Model 之外,也幫我們封裝了 Collection 及 ResourceModel 的兩種類別,讓我們在使用 ORM 的時候更加的方便,底下我們就要在這模組中新增這幾隻程式,來完成我們 Post 的 Entity Type。
(1) Model
請注意!這邊是非常大的地雷!需要特別注意!
一般的 Model 是繼承 \Magento\Framework\Model\AbstractModel,但是因為是需要操作 Entity Type 的關係,我們繼承的 Class 需更改為 Magento\Catalog\Model\AbstractModel,可以參考下面程式的第 6 行:
<?php namespace Grayson\Post\Model; use Magento\Catalog\Model\AbstractModel; use Magento\Framework\DataObject\IdentityInterface; /** * Class Post * @package Grayson\Post\Model */ class Post extends AbstractModel implements IdentityInterface { /** * Entity code. * Can be used as part of method name for entity processing */ const ENTITY = 'grayson_post'; const CACHE_TAG = 'grayson_post'; const STORE_ID = 'store_id'; /* @var string */ protected $_eventPrefix = 'grayson_post'; /* @var string */ protected $_eventObject = 'post'; /* @var string */ protected $_cacheTag = self::CACHE_TAG; /** * @return void */ protected function _construct() { $this->_init(ResourceModel\Post::class); } /** * @return array */ public function getIdentities() { return [self::CACHE_TAG . '_' . $this->getId()]; } }
(2) ResourceModel
也不同於原本繼承 Magento\Framework\Model\ResourceModel\Db\AbstractDb,
這邊要更換為Magento\Catalog\Model\ResourceModel\AbstractResource
<?php namespace Grayson\Post\Model\ResourceModel; use Magento\Catalog\Model\ResourceModel\AbstractResource; /** * Class Post * @package Grayson\Post\Model\ResourceModel */ class Post extends AbstractResource { /** * * @return \Magento\Eav\Model\Entity\Type * @throws \Magento\Framework\Exception\LocalizedException */ public function getEntityType() { if (empty($this->_type)) { $this->setType(\Grayson\Post\Model\Post::ENTITY); } return parent::getEntityType(); } }
(3) Collection
Collection 的部分也需要由原本繼承的 Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection 改為 Magento\Catalog\Model\ResourceModel\Collection\AbstractCollection 才行:
<?php namespace Grayson\Post\Model\ResourceModel\Post; use Grayson\Post\Model\Post; use Grayson\Post\Model\ResourceModel\Post as ResourceModelPost; use Magento\Catalog\Model\ResourceModel\Collection\AbstractCollection; /** * Class Collection * @package Grayson\Post\Model\ResourceModel\Post */ class Collection extends AbstractCollection { protected function _construct() { $this->_init(Post::class, ResourceModelPost::class); } }
3.執行 Upgrade 指令
在我們都完成上面的程式檔案後,並且做最後的確認,是否有 Module 沒有複製到正確的資料。如果一切都沒有問題,直接執行
$ bin/magento setup:upgrade
即可看到安裝完成的訊息,我們可以至 eav_entity_type 資料表內檢查,是否有多出 grayson_post 這個 Entity Type 呢?如果沒有,請回頭至第一個步驟再檢查一次有沒有程式碼遺漏掉,還是沒有的話,可以參考我下面的 Github 連結,裡面有完整的模組範例,包含上述的程式碼。
參考連結:Gitbub
想看更多Magento 2 教學導覽,別忘了訂閱我們的電子報,以及追蹤我們的Facebook粉絲專頁唷!
更多Magento相關文章請看: Magento教學導覽
我要留言