How to use setup scripts in Magento 2

By

Setup scripts are an essential tool when writing your own custom modules. But there are a lot of changes between how these are handled in Magento 1 versus Magento 2 (now known as Magento Commerce/Magento Open Source).

Magento 2 setup script changes

Let's start by taking a quick look at the key differences in how setup scripts are handled in Magento 1 versus 2 versions:

Magento 1 Magento 2

Configuration cache clear essential

Configuration cache clear optional

Scripts triggered after configuration cache clear (by default) Scripts triggered using Magento CLI tool
Data and schema scripts in separate folders Data and schema scripts in same folder
Upgrade scripts in separate files

Upgrade scripts in single files

Uses table core_resource

Uses table setup_module

No concept of recurring script

New recurring script type runs every time a setup command is triggered

No concept of uninstall script

New uninstall script type runs when module is uninstalled (composer only)

Directory structure

In Magento 1, there were separate module folders for the schema and data scripts. However, in Magento 2 this has been simplified to use a single module folder called Setup, as demonstrated in the following screenshot

screenshot of directory structure in magento 2

Additionally, you will see that there is only one schema upgrade script and only one data upgrade script – the file version numbering of Magento 1 has been abandoned. Upgrade scripts will be explained in more detail below.

Running setup scripts

Setup scripts are triggered using the Magento 2 Command Line Interface (CLI) tool. There are a few different commands to achieve this:

Command Description

magento setup:upgrade

Runs schema and data changes combined

magento setup:db-schema:upgrade

Runs schema changes only

magento setup:db-data:upgrade

Runs data changes only

Typically, you will use magento setup:upgrade to trigger the scripts as it ensures that the schema and data scripts are run in sequence, and ensures that any dependencies are generated prior to running the scripts. However, magento  setup:db-schema:upgrade and magento setup:db-data:upgrade do allow the scripts to be executed independently, and do not require the configuration cache to be cleared.

Version tracking

When a module is first installed, an entry is added to the setup_module table for that module, using the setup_version attribute value specified in etc/module.xml:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Inviqa_SetupExample" setup_version="1.0.0" />
</config>

So the above example would create the following database entry:

mysql> select * from setup_module where module = "Inviqa_SetupExample";
+---------------------+----------------+--------------+
| module              | schema_version | data_version |
+---------------------+----------------+--------------+
| Inviqa_SetupExample | 1.0.0          | 1.0.0        |
+---------------------+----------------+--------------+

There is a potential pitfall here in that when a module is installed, an entry is added to the setup_module table regardless of whether any install scripts are present. So if the install scripts are created after the module is installed, they will never be executed. If you are developing a module and hit this problem, you will need to manually delete the module entry in the setup_module table.

As their names imply, the Setup/InstallSchema.php and Setup/InstallData.php scripts are run when the module is first installed and are used to modify the database structure and content respectively.

An example of a Setup/InstallSchema.php script looks like this:

<?php

namespace Inviqa\SetupExample\Setup;

use Magento\Framework\Setup\InstallSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;

class InstallSchema implements InstallSchemaInterface
{

    public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)
    {
        $installer = $setup;

        $installer->startSetup();

        $table = $installer->getConnection()
            ->newTable($installer->getTable('inviqa_example'))
            ->addColumn(
                'entity_id',
                \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,
                null,
                ['identity' => true, 'unsigned' => true, 'nullable' => 
false, 'primary' => true],
                'Entity ID'
            )
            ->addColumn(
                'name',
                \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
                255,
                ['nullable' => false],
                'Name'
            )
            ->setComment('Inviqa Example');
        $installer->getConnection()->createTable($table);

        $installer->endSetup();
    }
}

As you can see, once the InstallSchemaInterface interface is implemented, all the setup code is placed within the install method. The example setup code uses Data Definition Language (DDL) to create a simple table called inviqa_example. DDL was added to later versions of Magento 1 to allow schema definitions and changes to be written in a cross database compatible format, and these changes have been carried through into Magento 2.

The Setup/InstallData.php script follows a similar convention but instead implements InstallDataInterface. Here’s an example:

<?php

namespace Inviqa\SetupExample\Setup;

use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;

class InstallData implements InstallDataInterface
{
    public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
    {
        $setup->getConnection()->query("INSERT INTO inviqa_example SET name = 'Test 1'");
    }
}

In this example, the setup code uses raw SQL to insert a row into the newly created inviqa_example table.

Upgrade scripts

As their names imply, the Setup/UpgradeSchema.php and Setup/UpgradeData.php scripts will update the database structure and content respectively. Updating the setup_version attribute value in etc/module.xml will prompt the upgrade scripts to be run the next time magento setup:upgrade is executed from the CLI.

An example of a Setup/UpgradeSchema.php script looks like this:

<?php

namespace Inviqa\SetupExample\Setup;

use Magento\Framework\Setup\UpgradeSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;

class UpgradeSchema implements UpgradeSchemaInterface
{
    public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $context)
    {
        $setup->startSetup();

        if (version_compare($context->getVersion(), '1.1.0', '<=')) {

            $setup->getConnection()->addColumn(
                $setup->getTable('inviqa_example'),
                'email',
                [
                    'type' => \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
                    'length' => 255,
                    'nullable' => true,
                    'comment' => 'Email'
                ]
            );

        }

        $setup->endSetup();
    }
}

Here you can see a similar pattern to the install scripts, only in this case UpgradeSchemaInterface is implemented and the setup code is placed within the upgrade method. The setup code uses DDL to add an extra column to the inviqa_example table.

The key thing to notice here though is the use of the following code to isolate the changes:

if (version_compare($context->getVersion(), '1.1.0', '<')) {
            // setup code here
        }

This will ensure that the changes are only run when the setup version is changed to 1.1.0 and then ignored on subsequent version increments. Any code outside such a construct will be executed on every upgrade, which would probably not be what is intended.

The Setup/UpgradeData.php script follows the same convention. Here’s an example:

<?php

namespace Inviqa\SetupExample\Setup;

use Magento\Framework\Setup\UpgradeDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;

class UpgradeData implements UpgradeDataInterface
{
    public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
    {
        $setup->startSetup();

        if (version_compare($context->getVersion(), '1.1.0', '<=')) {
            $setup->getConnection()->query("INSERT INTO inviqa_example SET name = 'Test 2', email = '[email protected]'");
        }

        $setup->endSetup();
    }
}

This also uses the version comparison to ensure that upgrades are tied to a specific version.

Recurring scripts

A new feature in Magento 2 is recurring scripts, which can be created in your module at Setup/Recurring.php.

Recurring scripts run every time magento setup:upgrade is run. At first glance this might not seem much different from an upgrade script without a version comparison, but the latter only executes when the version is incremented.

The only use of recurring scripts in the core code at present is in Magento\Indexer\Setup\Recurring, which validates entries in the index_state table.

The following example shows how to create a recurring script (even if the purpose is contrived and not really representative of what a recurring script would be used for).

<?php

namespace Inviqa\SetupExample\Setup;

use Magento\Framework\Setup\InstallSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;

class Recurring implements InstallSchemaInterface
{
    public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)
    {
        $setup->startSetup();

        $setup->getConnection()->query("INSERT INTO inviqa_example SET name = 'Test 3', email = '[email protected]'");

        $setup->endSetup();
    }
}

Here you can see that recurring scripts do not have their own interface type, but actually reuse InstallSchemaInterface and the recurring setup code is inside the install method.

Uninstall scripts

Another new feature in Magento 2 is ‘uninstall scripts’, which can be created in your module at Setup/Uninstall.php. They give module developers the opportunity to tidy up the database when their module is removed.

<?php

namespace Inviqa\SetupExample\Setup;

use Magento\Framework\Setup\UninstallSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;

class Recurring implements UninstallSchemaInterface
{
    public function uninstall(SchemaSetupInterface $setup, ModuleContextInterface $context)
    {
        $setup->startSetup();

        $setup->getConnection()->query("DROP table inviqa_example");

        $setup->endSetup();
    }
}

As you can see, the uninstall code removes the inviqa_example table from the database.

Uninstall scripts currently only work when a module has been installed via Composer, and is then subsequently uninstalled using the module:uninstall CLI command.

Maintainability

The departure from the complicated install/upgrade file formats of Magento 1 where module version was part of the file name has definitely simplified the implementation of upgrade scripts in Magento 2 (now known as Magento Commerce). However, it comes at the cost of maintainability, as all updates are now initiated from a single file. This could quickly lead to some very bloated upgrade files.

The onus will therefore be on the module developer to implement a maintainable structure. Fortunately, the new Dependency Injection implementation makes this reasonably easy.

However, this does not solve the curse of Magento 1 projects, which was merging project branches which had both upgraded to the same version numbers. Avoiding this will still require good developer communication.

Related reading

About the author

Phillip has more than 13 years' experience designing and building web applications, with the last five years spent exclusively on Magento projects at Inviqa. Prior to that, he worked in a variety of roles including server- and client-side development, client-/server-side integration and interface design.