├── Common ├── Cms.php ├── EavAttribute.php └── EavAttribute │ └── Context.php ├── LICENSE ├── README.md ├── Setup ├── Migration.php └── Migration │ ├── Context.php │ └── Facade │ ├── AdminConfig.php │ ├── CategoryAttribute.php │ ├── CmsBlock.php │ ├── CmsPage.php │ ├── CustomerAttribute.php │ └── ProductAttribute.php ├── composer.json ├── docs ├── header.png ├── header.svg ├── sponsors │ └── caravelx.svg ├── tldr.png └── tldr.xcf ├── etc └── module.xml └── registration.php /Common/Cms.php: -------------------------------------------------------------------------------- 1 | collectionFactory = $collectionFactory; 34 | $this->repository = $repository; 35 | $this->factory = $factory; 36 | } 37 | 38 | /** 39 | * Load cms content data by given identifier 40 | * 41 | * @param string $identifier 42 | * @param int $storeId 43 | * @return Block|Page 44 | */ 45 | public function get(string $identifier, $storeId = null) 46 | { 47 | $id = $this->findId($identifier, $storeId); 48 | 49 | return $this->repository->getById($id); 50 | } 51 | 52 | /** 53 | * Create a new content on database through corresponding cms repository 54 | * 55 | * @param string $identifier 56 | * @param array $data 57 | * @param int|null $storeId 58 | * @return Block|Page 59 | */ 60 | public function create($identifier, $data, $storeId = null) 61 | { 62 | $model = $this->factory->create() 63 | ->setIdentifier($identifier) 64 | ->addData($data) 65 | ->setStoreId($storeId); 66 | 67 | return $this->repository->save($model); 68 | } 69 | 70 | /** 71 | * Create a new content only if it not exists yet 72 | * 73 | * @param string $identifier 74 | * @param array $data 75 | * @param int|null $storeId 76 | * @return Block|Page 77 | */ 78 | public function safeCreate($identifier, $data, $storeId = null) 79 | { 80 | if ($this->exists($identifier, $storeId)) { 81 | return $this->get($identifier, $storeId); 82 | } 83 | 84 | return $this->create($identifier, $data, $storeId); 85 | } 86 | 87 | /** 88 | * Update cms content based on given identifier 89 | * 90 | * @param string $identifier 91 | * @param array $data 92 | * @param int|null $storeId 93 | * @return Block|Page 94 | */ 95 | public function update($identifier, $data, $storeId = null) 96 | { 97 | $model = $this->get($identifier, $storeId); 98 | $model->addData($data); 99 | 100 | return $this->repository->save($model); 101 | } 102 | 103 | /** 104 | * Update cms content only if it already exists 105 | * 106 | * @param string $identifier 107 | * @param array $data 108 | * @param int|null $storeId 109 | * @return Block|Page|null 110 | */ 111 | public function safeUpdate($identifier, $data, $storeId = null) 112 | { 113 | if (!$this->exists($identifier, $storeId)) { 114 | return null; 115 | } 116 | 117 | return $this->update($identifier, $data, $storeId); 118 | } 119 | 120 | /** 121 | * Delete cms content by given id 122 | * (row_id on database) 123 | * 124 | * @param string $identifier 125 | * @param int|null $storeId 126 | * @return bool 127 | */ 128 | public function delete($identifier, $storeId = null) 129 | { 130 | $id = $this->findId($identifier, $storeId); 131 | 132 | return $this->repository->deleteById($id); 133 | } 134 | 135 | /** 136 | * Check if given block exists 137 | * 138 | * @param string $identifier 139 | * @param int|null $storeId 140 | * @return bool 141 | */ 142 | public function exists($identifier, $storeId = null) 143 | { 144 | try { 145 | $this->findId($identifier, $storeId); 146 | } catch (\Throwable $th) { 147 | return false; 148 | } 149 | 150 | return true; 151 | } 152 | 153 | /** 154 | * Find a cms model by given identifier 155 | * (and optionally storeId) 156 | * 157 | * @param string $identifier 158 | * @param int $storeId 159 | * @return int 160 | */ 161 | protected function findId($identifier, $storeId = null) 162 | { 163 | $collection = $this->collectionFactory->create(); 164 | $collection->addFieldToFilter('identifier', $identifier); 165 | 166 | if ($storeId) { 167 | $collection->addStoreFilter($storeId); 168 | } 169 | 170 | if (!$collection->count()) { 171 | throw new LocalizedException( 172 | __("CMS content with identifier '$identifier' not found") 173 | ); 174 | } 175 | 176 | return $collection->getFirstItem()->getId(); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /Common/EavAttribute.php: -------------------------------------------------------------------------------- 1 | config = $context->config; 35 | $this->eavSetupFactory = $context->eavSetupFactory; 36 | $this->resourceConnection = $context->resourceConnection; 37 | } 38 | 39 | /** 40 | * Create a new attribute 41 | * 42 | * @param string $code 43 | * @param array $data 44 | * @return \Magento\Eav\Setup\EavSetup 45 | */ 46 | public function create($code, $data) 47 | { 48 | $this->getEavSetup()->addAttribute( 49 | static::ENTITY_TYPE, 50 | $code, 51 | $data 52 | ); 53 | } 54 | 55 | /** 56 | * Update an existing attribute 57 | * 58 | * @param string $code 59 | * @param array $data 60 | * @return \Magento\Eav\Setup\EavSetup 61 | */ 62 | public function update($code, $data) 63 | { 64 | $this->getEavSetup()->updateAttribute( 65 | static::ENTITY_TYPE, 66 | $code, 67 | $data 68 | ); 69 | } 70 | 71 | /** 72 | * Check if given attribute exists 73 | * 74 | * @param string $code 75 | * @return bool 76 | */ 77 | public function exists($code) 78 | { 79 | return (bool) $this->config->getAttribute(static::ENTITY_TYPE, $code)->getId(); 80 | } 81 | 82 | /** 83 | * Retrieve a fresh instance of the EavSetup 84 | * 85 | * @return \Magento\Eav\Setup\EavSetup 86 | */ 87 | protected function getEavSetup() 88 | { 89 | return $this->eavSetupFactory->create(); 90 | } 91 | 92 | /** 93 | * Database facade for quick operations 94 | */ 95 | protected function getConnection() 96 | { 97 | return $this->resourceConnection->getConnection(); 98 | } 99 | 100 | /** 101 | * Retrieve given table name on database, 102 | * with preffix and etc if applicable 103 | * 104 | * @param string $rawTableName 105 | * @return string 106 | */ 107 | protected function getTableName($rawTableName) 108 | { 109 | return $this->resourceConnection->getTableName($rawTableName); 110 | } 111 | 112 | /** 113 | * Retrieve entity type if 114 | * 115 | * @return int 116 | */ 117 | protected function getEntityTypeId() 118 | { 119 | $tableName = $this->getTableName('eav_entity_type'); 120 | $select = $this->getConnection()->select() 121 | ->from($tableName, 'entity_type_id') 122 | ->where('entity_type_code=?', static::ENTITY_TYPE); 123 | 124 | return (int) $this->getConnection()->fetchOne($select); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /Common/EavAttribute/Context.php: -------------------------------------------------------------------------------- 1 | config = $config; 34 | $this->eavSetupFactory = $eavSetupFactory; 35 | $this->resourceConnection = $resourceConnection; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Discorgento 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Discorgento Migrations](docs/header.png) 2 | 3 |

A dev-friendly approach to keep track of database changes in Magento 2

4 |

5 | GitHub Stars 6 | Total Downloads 7 | Latest Version on Packagist 8 | Join our Discord 9 |

10 | 11 |

Our Sponsors

12 |

13 | Caravel X 14 |

15 | 16 | ## Overview 💭 17 | Just changed something on the admin panel or on the database and now you need to replicate it again in staging and production? No worries, [we](https://discorgento.com/discord) got you covered. 18 | 19 | Probably you already heard about [data patches](https://developer.adobe.com/commerce/php/development/components/declarative-schema/patches/), but what if I say that it can be really, really simplified? 20 | 21 | ![image](docs/tldr.png) 22 | From 50 lines to just 15, or simply 70% less code. SEVENTY percent fewer lines. 23 | But we're just getting started. 24 | 25 | ## Install 🔧 26 | This module is compatible with both Magento 2.3 and 2.4, from PHP 7.3 to 8.3. 27 | ``` 28 | composer require discorgento/module-migrations:^2 && bin/magento setup:upgrade 29 | ``` 30 | 31 | ## Usage 🥤 32 | Quick demo on how to use it: 33 | 34 | > There's also an extended version in Brazillian Portuguese including CMS content management overview available [here](https://odysee.com/@discorgento:8/Introdu%C3%A7%C3%A3o-ao-Modulo-Migrations-Magento-discorgento-module-migrations:9). 35 | 36 | Besides simplifying the basic structure like showed before, we also provide some [facades](https://refactoring.guru/design-patterns/facade) to common tasks like handling [admin config](https://github.com/discorgento/module-migrations/wiki/Admin-Config), [product attributes](https://github.com/discorgento/module-migrations/wiki/Product-Attributes), [cms content](https://github.com/discorgento/module-migrations/wiki/Cms-Content) and [more](https://github.com/discorgento/module-migrations/wiki). As an example, you can use a snippet like this to create a whole new CMS Page, including Page Builder widgets on its content: 37 | 38 | ```php 39 | cmsPage = $cmsPage; 56 | } 57 | 58 | protected function execute() 59 | { 60 | $this->cmsPage->create('my-new-page', [ 61 | 'title' => 'Lorem Ipsum', 62 | 'content' => <<Hello World! 64 | HTML, 65 | ]); 66 | } 67 | } 68 | ``` 69 | 70 | Run a `bin/magento setup:upgrade`, navigate to the _/my-new-page path_, and that's it. And naturally as this is part of the deployment of new releases of your store, it will be automatically replicated in your integration/staging/production/whatever environments (and even your coworkers machines). 71 | 72 | > **💡 Tip:** Don't forget to check our [official wiki](https://github.com/discorgento/module-migrations/wiki) to make the most use of this powerful m2 tool! 73 | 74 | ## Notes 🗒 75 | - roadmap: create cli command to generate migrations for existant cms content (thanks [@vpjoao98](https://github.com/vpjoao98)); 76 | - issues and PRs are welcome in this repo; 77 | - we want **YOU** for [our community](https://discorgento.com/discord)! 78 | -------------------------------------------------------------------------------- /Setup/Migration.php: -------------------------------------------------------------------------------- 1 | context = $context; 23 | } 24 | 25 | /** 26 | * Your migration logic goes here 27 | */ 28 | abstract protected function execute(); 29 | 30 | /** 31 | * Undo/revert the migration (optional) 32 | * 33 | * @return void 34 | */ 35 | protected function rollback() 36 | { 37 | // optional, override to implement an undo feature in your migration 38 | } 39 | 40 | /** 41 | * @inheritDoc 42 | * 43 | * No reason to use the raw apply() method, use execute() instead 44 | */ 45 | final public function apply() 46 | { 47 | if ($appliedAlias = $this->hasAlreadyAppliedAlias()) { 48 | $this->context->logger->info(__( 49 | 'Patch data "%1" skipped because the it was already applied under the old name "%2"', 50 | [static::class, $appliedAlias] 51 | )); 52 | 53 | return; 54 | } 55 | 56 | $this->getConnection()->startSetup(); 57 | 58 | if ($areaCode = static::AREA_CODE) { 59 | try { 60 | $this->context->state->setAreaCode($areaCode); 61 | } catch (\Throwable $th) { 62 | // already set 63 | } 64 | } 65 | 66 | $this->execute(); 67 | 68 | $this->getConnection()->endSetup(); 69 | } 70 | 71 | /** 72 | * @inheritDoc 73 | * 74 | * No reason to use the raw revert() method, use rollback() instead 75 | */ 76 | final public function revert() 77 | { 78 | $this->getConnection()->startSetup(); 79 | $this->rollback(); 80 | $this->getConnection()->endSetup(); 81 | } 82 | 83 | /** 84 | * Workaround for Magento core bug 85 | * 86 | * Return false if there's no previous applied alias, 87 | * otherwise return the applied alias name 88 | * 89 | * @see https://github.com/magento/magento2/issues/31396 90 | * 91 | * @return string|false 92 | */ 93 | private function hasAlreadyAppliedAlias() 94 | { 95 | foreach ($this->getAliases() as $alias) { 96 | if ($this->context->patchHistory->isApplied($alias)) { 97 | return $alias; 98 | } 99 | } 100 | 101 | return false; 102 | } 103 | 104 | /** 105 | * Shorthand for getting the database connection 106 | */ 107 | protected function getConnection(): AdapterInterface 108 | { 109 | return $this->getModuleDataSetup()->getConnection(); 110 | } 111 | 112 | /** 113 | * Get given table name in database including prefix 114 | * 115 | * @param string $rawName 116 | * @return string 117 | */ 118 | protected function getTableName(string $rawName): string 119 | { 120 | return $this->getModuleDataSetup()->getTable($rawName); 121 | } 122 | 123 | /** 124 | * Module Setup Data getter 125 | * 126 | * @return ModuleDataSetupInterface 127 | */ 128 | protected function getModuleDataSetup() 129 | { 130 | return $this->context->moduleDataSetup; 131 | } 132 | 133 | /** 134 | * @inheritDoc 135 | */ 136 | public function getAliases() 137 | { 138 | return []; 139 | } 140 | 141 | /** 142 | * @inheritDoc 143 | */ 144 | public static function getDependencies() 145 | { 146 | return []; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /Setup/Migration/Context.php: -------------------------------------------------------------------------------- 1 | moduleDataSetup = $moduleDataSetup; 33 | $this->logger = $logger; 34 | $this->patchHistory = $patchHistory; 35 | $this->state = $state; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Setup/Migration/Facade/AdminConfig.php: -------------------------------------------------------------------------------- 1 | configWriter = $configWriter; 23 | $this->scopeConfig = $scopeConfig; 24 | } 25 | 26 | /** 27 | * Retrieve config value from given path 28 | * 29 | * @param string $path 30 | * @param string $scope 31 | * @param int $scopeId 32 | */ 33 | public function get($path, $scope = ScopeConfig::SCOPE_TYPE_DEFAULT, $scopeId = null) 34 | { 35 | return $this->scopeConfig->getValue($path, $scope, $scopeId); 36 | } 37 | 38 | /** 39 | * Set config value of given path 40 | * 41 | * @param string|array $path can also be a map of multiple config 42 | * @param string|int|array $value 43 | * @param string $scope 44 | * @param int $scopeId 45 | */ 46 | public function set($path, $value = null, $scope = ScopeConfig::SCOPE_TYPE_DEFAULT, $scopeId = 0) 47 | { 48 | if (!is_array($path)) { 49 | return $this->configWriter->save($path, $value, $scope, $scopeId); 50 | } 51 | 52 | $paths = $path; 53 | foreach ($paths as $path => $value) { 54 | $this->set( 55 | $path, 56 | $value['value'] ?? $value, 57 | $value['scope'] ?? $scope, 58 | $value['scope_id'] ?? $scopeId, 59 | ); 60 | } 61 | } 62 | 63 | /** 64 | * Exclude given config from core_config_data, thus restoting it to the default value set at its config.xml 65 | * 66 | * @param string|array $path 67 | * @param string $scope 68 | * @param int $scopeId 69 | */ 70 | public function restore($path, $scope = ScopeConfig::SCOPE_TYPE_DEFAULT, $scopeId = 0) 71 | { 72 | $paths = is_string($path) ? [$path] : $path; 73 | foreach ($paths as $path) { 74 | $this->configWriter->delete($path, $scope, $scopeId); 75 | } 76 | } 77 | 78 | /** 79 | * Reset given config(s) to it's original config.xml value 80 | * 81 | * @deprecated 2.0.3 replaced with "restore" to match the admin settings naming 82 | * @see Discorgento\Migrations\Setup\Migration\Facade\AdminConfig::restore 83 | * 84 | * @param string|array $path 85 | * @param string $scope 86 | * @param int $scopeId 87 | * @return void 88 | */ 89 | public function reset($path, $scope = ScopeConfig::SCOPE_TYPE_DEFAULT, $scopeId = 0) 90 | { 91 | $this->restore($path, $scope, $scopeId); 92 | } 93 | 94 | /** 95 | * Append given value to given path 96 | * 97 | * @param string $path 98 | * @param string|int|array $value 99 | * @param string $scope 100 | * @param int $scopeId 101 | */ 102 | public function append($path, $value, $scope = ScopeConfig::SCOPE_TYPE_DEFAULT, $scopeId = null) 103 | { 104 | $oldValue = $this->scopeConfig->getValue($path, $scope, $scopeId); 105 | $newValue = $oldValue . $value; 106 | $this->configWriter->save($path, $newValue, $scope, $scopeId ?: 0); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Setup/Migration/Facade/CategoryAttribute.php: -------------------------------------------------------------------------------- 1 | categoryRepository = $categoryRepository; 24 | } 25 | 26 | /** 27 | * Update given attribute value for multiple categories at once 28 | * 29 | * @todo Find a faster way of doing this, like with the products 30 | * 31 | * @param array $entityIds 32 | * @param array $data 33 | * @return void 34 | */ 35 | public function massUpdate($entityIds, $data) 36 | { 37 | foreach ($entityIds as $categoryId) { 38 | /** @var \Magento\Catalog\Model\Category */ 39 | $category = $this->categoryRepository->get($categoryId); 40 | $category->addData($data); 41 | 42 | $this->categoryRepository->save($category); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Setup/Migration/Facade/CmsBlock.php: -------------------------------------------------------------------------------- 1 | attributeManagement = $attributeManagement; 37 | $this->attributeSetRepository = $attributeSetRepository; 38 | $this->productAction = $productAction; 39 | } 40 | 41 | /** 42 | * Shorthand to quickly create a dropdown-type attribute 43 | * 44 | * @param string $code 45 | * @param string $label 46 | * @param array $values 47 | * @param array $config 48 | */ 49 | public function createDropdown($code, $label, $values, $config = []) 50 | { 51 | $this->create( 52 | $code, 53 | array_merge([ 54 | 'label' => $label, 55 | 'input' => 'select', 56 | 'type' => 'int', 57 | 'source_model' => \Magento\Eav\Model\Entity\Attribute\Source\Table::class, 58 | 'filterable' => 1, 59 | 'required' => false, 60 | 'option' => ['values' => $values], 61 | 'user_defined' => true, 62 | ], $config) 63 | ); 64 | } 65 | 66 | /** 67 | * Assign an attribute to some attribute set (or default if none specified) 68 | * 69 | * @param string $attributeCode 70 | * @param int|string|AttributeSet $attributeSet 71 | * @param int|string|AttributeGroup $group 72 | * @param string $after 73 | */ 74 | public function assignToAttributeSet($attributeCode, $attributeSet = null, $group = null, $after = null) 75 | { 76 | if (is_array($attributeSet)) { 77 | return $this->assignToAttributeSetLegacy($attributeCode, $attributeSet); 78 | } 79 | 80 | $attributeSetId = $this->resolveAttributeSetId($attributeSet); 81 | $attributeGroupId = $this->resolveGroupId($group, $attributeSet); 82 | $sortOrder = $after ? $this->getSortOrder($after, $attributeSet) + 1 : 999; 83 | 84 | $this->attributeManagement->assign( 85 | self::ENTITY_TYPE, 86 | $attributeSetId, 87 | $attributeGroupId, 88 | $attributeCode, 89 | $sortOrder 90 | ); 91 | } 92 | 93 | /** 94 | * Old implementation of attribute set assign 95 | * for retrocompatibility purposes only 96 | * 97 | * @param string $attributeCode 98 | * @param array $options 99 | */ 100 | private function assignToAttributeSetLegacy($attributeCode, $options = []) 101 | { 102 | $attributeSetId = (int) ($options['attribute_set_id'] ?? $this->getEavSetup() 103 | ->getDefaultAttributeSetId(self::ENTITY_TYPE)); 104 | 105 | $attributeGroupId = $options['group_id'] ?? $this->getDefaultGroupId($attributeSetId); 106 | $sortOrder = $options['sort_order'] ?? 999; 107 | 108 | $this->attributeManagement->assign( 109 | self::ENTITY_TYPE, 110 | $attributeSetId, 111 | $attributeGroupId, 112 | $attributeCode, 113 | $sortOrder 114 | ); 115 | } 116 | 117 | /** 118 | * Remove given attribute from given attribute set 119 | * 120 | * @param string $attributeCode 121 | * @param int $attributeSetId 122 | */ 123 | public function unassignFromAttributeSet($attributeCode, $attributeSetId = null) 124 | { 125 | $attributeSetId = $attributeSetId ?: $this->getEavSetup() 126 | ->getDefaultAttributeSetId(self::ENTITY_TYPE); 127 | 128 | $this->attributeManagement->unassign($attributeSetId, $attributeCode); 129 | } 130 | 131 | /** 132 | * Update given attribute value for multiple products at once 133 | * 134 | * @param array $entityIds 135 | * @param array $data 136 | * @return void 137 | */ 138 | public function massUpdate($entityIds, $data) 139 | { 140 | $this->productAction->updateAttributes( 141 | $entityIds, 142 | $data, 143 | self::SCOPE_STORE 144 | ); 145 | } 146 | 147 | /** 148 | * Return the id of given attribute set 149 | * 150 | * @param int|string|AttributeSet $attributeSet 151 | */ 152 | private function resolveAttributeSetId($attributeSet = null) : int 153 | { 154 | if ($attributeSet === null) { 155 | return $this->getDefaultAttributeSetId(); 156 | } 157 | 158 | if (is_object($attributeSet)) { 159 | return (int) $attributeSet->getId(); 160 | } 161 | 162 | if (!is_int($attributeSet)) { 163 | $select = $this->getConnection()->select() 164 | ->from($this->getTableName('eav_attribute_set'), 'attribute_set_id') 165 | ->where('attribute_set_name = ?', $attributeSet) 166 | ->where('entity_type_id = ?', $this->getEntityTypeId()); 167 | $attributeSetId = $this->getConnection()->fetchOne($select); 168 | 169 | if (empty($attributeSetId)) { 170 | throw new NoSuchEntityException(__("Attribute Set with name $attributeSet not found")); 171 | } 172 | 173 | return (int) $attributeSetId; 174 | } 175 | 176 | return $attributeSet; 177 | } 178 | 179 | /** 180 | * Resolve given attribute set group into its id 181 | * 182 | * @param int|string|AttributeGroup $group 183 | * @param int|string|AttributeSet $attributeSet 184 | */ 185 | private function resolveGroupId($group, $attributeSet = null) : int 186 | { 187 | if ($group === null) { 188 | return $this->getDefaultGroupId($attributeSet); 189 | } 190 | 191 | if (is_object($group)) { 192 | return (int) $group->getId(); 193 | } 194 | 195 | if (!is_int($group)) { 196 | $select = $this->getConnection()->select() 197 | ->from($this->getTableName('eav_attribute_group'), 'attribute_group_id') 198 | ->where('attribute_group_name = ?', $group) 199 | ->where('attribute_set_id = ?', $this->resolveAttributeSetId($attributeSet)); 200 | $groupId = $this->getConnection()->fetchOne($select); 201 | 202 | if (empty($groupId)) { 203 | throw new NoSuchEntityException(__("Attribute Group with name $attributeSet not found")); 204 | } 205 | 206 | return (int) $groupId; 207 | } 208 | 209 | return $group; 210 | } 211 | 212 | /** 213 | * Retrieve default product attribute set id 214 | */ 215 | private function getDefaultAttributeSetId() : int 216 | { 217 | return (int) $this->getEavSetup()->getDefaultAttributeSetId(self::ENTITY_TYPE); 218 | } 219 | 220 | /** 221 | * Retrieve default group id of given attribute set 222 | * 223 | * @param int|string|AttributeSet $attributeSet 224 | */ 225 | private function getDefaultGroupId($attributeSet = null) : int 226 | { 227 | $attributeSetId = $this->resolveAttributeSetId($attributeSet); 228 | $attributeSet = $this->attributeSetRepository->get($attributeSetId); 229 | 230 | return (int) $attributeSet->getDefaultGroupId(); 231 | } 232 | 233 | /** 234 | * Retrieve current sort position of given attribute 235 | * 236 | * @param string $attributeCode 237 | * @param string|int|AttributeSet $attributeSet 238 | * @return int 239 | */ 240 | private function getSortOrder($attributeCode, $attributeSet = null) 241 | { 242 | $entityTypeId = $this->getEntityTypeId(); 243 | $attributeSetId = $this->resolveAttributeSetId($attributeSet); 244 | 245 | $attributeIdSelect = $this->getConnection()->select() 246 | ->from($this->getTableName('eav_attribute'), 'attribute_id') 247 | ->where('attribute_code = ?', $attributeCode); 248 | 249 | $select = $this->getConnection()->select() 250 | ->from($this->getTableName('eav_entity_attribute'), 'sort_order') 251 | ->where('entity_type_id = ?', $entityTypeId) 252 | ->where('attribute_set_id = ?', $attributeSetId) 253 | ->where('attribute_id = ?', $attributeIdSelect); 254 | 255 | return (int) $this->getConnection()->fetchOne($select) ?: 999; 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "discorgento/module-migrations", 3 | "description": "A dev-friendly approach to quickly create migrations (patch data) on Magento 2", 4 | "type": "magento2-module", 5 | "license": "MIT", 6 | "require": { 7 | "php": ">=7.3.0 <8.4" 8 | }, 9 | "autoload": { 10 | "files": [ 11 | "registration.php" 12 | ], 13 | "psr-4": { 14 | "Discorgento\\Migrations\\": "" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /docs/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discorgento/module-migrations/aa5db59b1114206bf6da49343e38980ae1027c48/docs/header.png -------------------------------------------------------------------------------- /docs/header.svg: -------------------------------------------------------------------------------- 1 | iscorgento | migrations -------------------------------------------------------------------------------- /docs/sponsors/caravelx.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/tldr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discorgento/module-migrations/aa5db59b1114206bf6da49343e38980ae1027c48/docs/tldr.png -------------------------------------------------------------------------------- /docs/tldr.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discorgento/module-migrations/aa5db59b1114206bf6da49343e38980ae1027c48/docs/tldr.xcf -------------------------------------------------------------------------------- /etc/module.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /registration.php: -------------------------------------------------------------------------------- 1 |