Declarative scheme and what is wrong with it in Magento 2

Hello. This publication does not pretend to be the title of truth in the first instance, but only is my personal opinion, if you share it perfectly, if not, I ask in the comments for discussion.



So, closer to the point. In version Magento 2.3 and higher, there was such a “bun” as a declarative scheme. What is this declarative scheme? If we turn to the Magento documentation, then it is written in black and white - “A declarative scheme is aimed at simplifying the installation and updating of Magento”.



Everything seems to be great, because before the developers had to write a lot of install and upgrade scripts (in M1 there was a little nightmare with them), up to version 2.3 it was necessary to keep a certain number of scripts depending on the tasks, namely:



InstallSchema - this class is launched when the module is installed to configure the database structure.

InstallData - this class is launched when the module is installed to initialize database table data.

UpgradeSchema - this class is launched when the module is updated to configure the database structure.

UpgradeData - this class is launched when the module is updated to add / remove data from the table.

Uninstall - this class is launched to remove data and tables from the database.



Agree, this is better than writing a separate script for each sneeze. But this turned out to be not very convenient, since I had to follow versioning, track and understand what you have in these scripts, and besides, they grew into huge "footcloths" of 4000+ lines. As a result, this approach turned out to be quite a failure. The number of files has decreased, but the number of lines of code has increased.



Then the declarative scheme came to the rescue. In fact, it came down to a single file - db_schema.xml . In which you store the final state of the database. That is, if you need a custom table for the module, you describe the necessary fields in this file and that’s it. Next, the magenta will create a table for you. In case you need to update a table already created earlier, you just make changes to the db_schema.xml file and that's it (the magic will happen by itself). No need to monitor versioning, no need to write database update scripts for each new version of the module, in fact no operation is necessary. Agree - it's cool.



But there is no barrel of tar without a spoon of tar. (This is not a typo, who works with magento will understand me :)).



The declarative scheme is good only when working with custom tables. If you need to add an attribute programmatically to a product or category, make an INSERT or UPDATE, or finally change something in Schema, please write patches. We will talk about them below.



For example, consider adding a custom attribute for a product.



1. Let's start by creating the following class in the module:

Setup \ Patch \ Data \ AddAlternativeNameAttribute.php



Which for a start will contain the following content



<?php namespace Foo\Bar\Setup\Patch\Data; use Magento\Eav\Setup\EavSetupFactory; use Magento\Framework\Setup\ModuleDataSetupInterface; use Magento\Framework\Setup\Patch\DataPatchInterface; class AddAlternativeNameAttribute implements DataPatchInterface { /** @var ModuleDataSetupInterface */ private $moduleDataSetup; /** @var EavSetupFactory */ private $eavSetupFactory; /** * @param ModuleDataSetupInterface $moduleDataSetup * @param EavSetupFactory $eavSetupFactory */ public function __construct( ModuleDataSetupInterface $moduleDataSetup, EavSetupFactory $eavSetupFactory ) { $this->moduleDataSetup = $moduleDataSetup; $this->eavSetupFactory = $eavSetupFactory; } }
      
      





The DataPatchInterface expects three functions to be implemented: apply , getDependencies and getAliases .



2. The apply function is the place where attribute elements are created. Now there is no need to call the startSetup and endSetup functions here, because we only create attributes. To do this, create an instance of EavSetupFactory, passing our moduleDataSetup object, and add our attribute:



  /** * {@inheritdoc} */ public function apply() { /** @var EavSetup $eavSetup */ $eavSetup = $this->eavSetupFactory->create(['setup' => $this->moduleDataSetup]); $eavSetup->addAttribute('catalog_product', 'alternative_name', [ 'type' => 'varchar', 'label' => 'Alternative Name', 'input' => 'text', 'used_in_product_listing' => true, 'user_defined' => true, ]); }
      
      





3. The getDependencies function expects an array of strings that contains the names of the dependency classes. This is a new functionality that appeared specifically for declarative schema scripts, and it tells Magento that it is necessary to execute the “patches” that we have defined here before our installation script. This is how Magento controls the execution order of patch scripts.



  /** * {@inheritdoc} */ public static function getDependencies() { return []; }
      
      





4. The last getAliases function that defines the aliases for this patch. Since we no longer specify version numbers, the name of our class may change, and if this happens, we must specify the old class name here so that it does not run a second time (patches are run only once)



  /** * {@inheritdoc} */ public function getAliases() { return []; }
      
      





The final class will look like this:



 <?php namespace Foo\Bar\Setup\Patch\Data; use Magento\Eav\Setup\EavSetup; use Magento\Eav\Setup\EavSetupFactory; use Magento\Framework\Setup\ModuleDataSetupInterface; use Magento\Framework\Setup\Patch\DataPatchInterface; class AddAlternativeNameAttribute implements DataPatchInterface { /** @var ModuleDataSetupInterface */ private $moduleDataSetup; /** @var EavSetupFactory */ private $eavSetupFactory; /** * @param ModuleDataSetupInterface $moduleDataSetup * @param EavSetupFactory $eavSetupFactory */ public function __construct( ModuleDataSetupInterface $moduleDataSetup, EavSetupFactory $eavSetupFactory ) { $this->moduleDataSetup = $moduleDataSetup; $this->eavSetupFactory = $eavSetupFactory; } /** * {@inheritdoc} */ public function apply() { /** @var EavSetup $eavSetup */ $eavSetup = $this->eavSetupFactory->create(['setup' => $this->moduleDataSetup]); $eavSetup->addAttribute('catalog_product', 'alternative_name', [ 'type' => 'varchar', 'label' => 'Alternative Name', 'input' => 'text', 'used_in_product_listing' => true, 'user_defined' => true, ]); } /** * {@inheritdoc} */ public static function getDependencies() { return []; } /** * {@inheritdoc} */ public function getAliases() { return []; } }
      
      





5. Now run bin / magento setup: upgrade so that our patch applies. For all patches that have been successfully completed, Magento inserts an entry into the patch_list database table with the patch_name field value equal to the value of our class (Foo \ Bar \ Setup \ Patch \ Data \ AddAlternativeNameAttribute).



6. Removing the value from the patch_list will cause the patch to re-run when the bin / magento setup: upgrade installation starts. This functionality will be useful when debugging patches.



Total:



+ Declarative scheme simplifies work with custom tables

+ Lack of need to monitor versioning

+ Easy data upgrade in tables and customization of table fields



- The inability to add attributes to a product category through a declarative scheme

- If the module is universal for versions 2.1, 2.2, 2.3, you will have to write both a declarative scheme and installation scripts.

- The need to write patches for working with core tables.



Perhaps in the foreseeable future, when M2 completely switches to a declarative scheme and there is no need to write patches, it will be super convenient. But whether this will happen and when it does, the question remains open.



All Articles