Setup scripts in Magento 2

Today we’ll cover how to manually enable or disable module in Magento 2 and which module setup scripts are executed in the process.

To enable custom module manually in Magento 2, two commands needs to be executed from the shell:

php bin/magento module:enable Inchoo_Custom
php bin/magento setup:upgrade

First command adds your module to the modules list in app/etc/config.php (and crashes M2 until you run second command 😛 ).

Second one really installs your module. Well, it will basically trigger setup scripts of all Magento modules that needs setup, but if you just enabled one new module nothing else will execute except that one.

If module has setup scripts, they are executed and current module version is saved in setup_module table, which is equivalent of core_resource in Magento 1. Magento regulates what needs setup by comparing module versions with what it sees in that table.

If module is installing for the first time, Install + Upgrade scripts are triggered. If module was installed before, but module version is increased, only Upgrade scripts are triggered.

To disable module, you will run:

php bin/magento module:disable Inchoo_Custom

This will flag module as disabled in config.php.

Another interesting command is:

php bin/magento module:uninstall Inchoo_Custom

This one is something new, module uninstall script is executed and whole module gets deleted afterwards. Only modules installed through Composer can be uninstalled.

To simplify, in Magento 2 you can have 6 different Setup classes in your module:

  • Setup/InstallSchema
  • Setup/UpgradeSchema
  • Setup/InstallData
  • Setup/UpgradeData
  • Setup/Recurring
  • Setup/Uninstall

There are no separate version setup files anymore, only one class per action. No more:

install-1.0.0.php
upgrade-1.0.0-1.0.1.php

upgrade-1.0.1-1.0.3.php

For the reference, all mentioned installer logic is located at /setup/src/Magento/Setup/.

Install or Upgrade schema

Schema setup scripts change database schema, they create or change needed database tables. If module is installing, SetupInstallSchema::install() is executed.

namespace InchooCustomSetup;
 
use MagentoFrameworkSetupInstallSchemaInterface;
use MagentoFrameworkSetupSchemaSetupInterface;
use MagentoFrameworkSetupModuleContextInterface;
 
class InstallSchema implements InstallSchemaInterface
{
    public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)
    {
        $setup->startSetup();
 
        $table = $setup->getConnection()->newTable(
            $setup->getTable('inchoo_custom')
        )->addColumn(
            'custom_id',
            MagentoFrameworkDBDdlTable::TYPE_INTEGER,
            null,
            ['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true],
            'Custom Id'
        )->addColumn(
            'name',
            MagentoFrameworkDBDdlTable::TYPE_TEXT,
            255,
            [],
            'Custom Name'
        )->setComment(
            'Custom Table'
        );
        $setup->getConnection()->createTable($table);
 
        $setup->endSetup();
}

If module is installing or upgrading, SetupUpgradeSchema::upgrade() is executed. Since there are no separate upgrade scripts for specific upgrade versions, your UpgradeSchema class needs to handle all possible version scenarios. As mentioned, this is quite different then in M1.

namespace InchooCustomSetup;
 
use MagentoFrameworkSetupUpgradeSchemaInterface;
use MagentoFrameworkSetupSchemaSetupInterface;
use MagentoFrameworkSetupModuleContextInterface;
 
class UpgradeSchema implements UpgradeSchemaInterface
{
    public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $context)
    {
        $setup->startSetup();
 
        //handle all possible upgrade versions
 
        if(!$context->getVersion()) {
            //no previous version found, installation, InstallSchema was just executed
            //be careful, since everything below is true for installation !
        }
 
        if (version_compare($context->getVersion(), '1.0.1') < 0) {
            //code to upgrade to 1.0.1
        }
 
        if (version_compare($context->getVersion(), '1.0.3') < 0) {
            //code to upgrade to 1.0.3
        }
 
        $setup->endSetup();
    }
}

For additional examples just check /Setup/ folder of almost any Magento module.

Install or Upgrade data

Data setup scripts contain entries module needs to insert into database. Attributes that come with Magento by default, 404 and other Cms pages, various default groups and roles, are all examples of data setup.

Data setup is executed after Schema setup, they function in a similar fashion.

Recurring (post install)

Recurring script is executed after any module setup. The idea is that Module1 can do something after Module2 and Module3 are installed, if needed.

The only example in current Magento 2 is MagentoIndexerSetupRecurring class where Magento_Indexer module checks for new defined indexers and adds them to indexer_state table.

Uninstall

Magento 2 modules can have Uninstall script !! The purpose of Uninstall script is to remove all module tables, columns, data from database, like it was never installed in the first place.

Since disabling and uninstalling are 2 different actions, I hope all M2 extensions will have uninstall script, it should be best practice to have them. This would simplify database cleanup if it’s decided that some extensions are not needed anymore.

Currently there are no Uninstall scripts in core modules to check, but the pattern is the same:

namespace InchooSetupTestSetup;
 
use MagentoFrameworkSetupUninstallInterface;
use MagentoFrameworkSetupSchemaSetupInterface;
use MagentoFrameworkSetupModuleContextInterface;
 
class Uninstall implements UninstallInterface
{
    public function uninstall(SchemaSetupInterface $setup, ModuleContextInterface $context)
    {
        $setup->startSetup();
 
        //uninstall code; Little Bobby Tables we call him ..
 
        $setup->endSetup();
    }
}

The only thing I don’t understand is why o why this works only for modules installed with Composer.

Additional

One more thing we’re usually adding through setups are EAV attributes. For them you use functionality of MagentoEavSetupEavSetup injected in your InstallData.

namespace InchooCustomSetup;
 
use MagentoEavSetupEavSetupFactory;
use MagentoFrameworkSetupInstallDataInterface;
use MagentoFrameworkSetupModuleContextInterface;
use MagentoFrameworkSetupModuleDataSetupInterface;
 
class InstallData implements InstallDataInterface
{
    /**
     * EAV setup factory
     *
     * @var EavSetupFactory
     */
    private $eavSetupFactory;
 
    /**
     * Init
     *
     * @param EavSetupFactory $eavSetupFactory
     */
    public function __construct(EavSetupFactory $eavSetupFactory)
    {
        $this->eavSetupFactory = $eavSetupFactory;
    }
 
    /**
     * {@inheritdoc}
     */
    public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
    {
        $setup->startSetup();
 
        /** @var MagentoEavSetupEavSetup $eavSetup */
        $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);
 
        $eavSetup->addAttribute(
            MagentoCatalogModelCategory::ENTITY,
            'inchoo_custom',
            [
                'type' => 'varchar',
                'input' => 'text',
                'label' => 'Inchoo Custom',
                'required' => false,
                'user_defined' => true,
                'global' => MagentoEavModelEntityAttributeScopedAttributeInterface::SCOPE_STORE,
                //...
                'group' => 'Inchoo Custom',
            ]
        );
 
        $setup->endSetup();
    }
}

Best to check Catalog and Customer InstallData for the reference.

In case you feel you need some extra help, we can offer you a detailed custom report based on our technical audit – feel free to get in touch and see what we can do for you!

So long, and thanks for all the fish.