One of the things that Magento first version lacked was an ability to clean up module data from the database upon its removal. This is a common situation when you uninstall an extension but all the related data remains in the database. You can only get rid of it manually. It is inconvenient especially if the module has created a bunch of new tables, custom attributes, system configurations etc. In this case, an automatic removal tool of such data would be very useful.

In Magento 2 there is a great feature, which allows to create an uninstall script for your module. Let’s find out how it works.

Overview

Each module that was installed via composer can be easily uninstalled using the following command:

bin/magento module:uninstall [--backup-code] [--backup-media] [--backup-db] [-r|--remove-data] [-c|--clear-static-content] {ModuleName}

In general, the module uninstall command performs four main tasks:

  1. Removes module’s code from the codebase using a composer remove;
  2. Removes the specified module from the module list;
  3. Removes the specified module from the setup_module database table;
  4. Invokes the uninstall method in its Uninstall class.

If you check the logic in Magento\Setup\Model\UninstallCollector::collectUninstall method, which is used to initialize the uninstall classes, you will notice that the following namespace and class naming convention of uninstall classes is required for each module.

{VendorName}\{ModuleName}\Setup\Uninstall

Another important thing is that each uninstall class must implement Magento\Framework\Setup\UninstallInterface interface.

/**
* Collect Uninstall classes from modules
*
* @param array $filterModules
* @return UninstallInterface[]
*/
public function collectUninstall($filterModules = [])
{
   $uninstallList = [];
   /** @var \Magento\Setup\Module\DataSetup $setup */
   $setup = $this->dataSetupFactory->create();
   $result = $setup->getConnection()->select()->from($setup->getTable('setup_module'), ['module']);
   if (isset($filterModules) && sizeof($filterModules) > 0) {
       $result->where('module in( ? )', implode(',', $filterModules));
   }
   // go through modules
   foreach ($setup->getConnection()->fetchAll($result) as $row) {
       $uninstallClassName = str_replace('_', '\\', $row['module']) . '\Setup\Uninstall';
       if (class_exists($uninstallClassName)) {
           $uninstallClass = $this->objectManager->create($uninstallClassName);
           if (is_subclass_of($uninstallClass, 'Magento\Framework\Setup\UninstallInterface')) {
               $uninstallList[$row['module']] = $uninstallClass;
           }
       }
   }

   return $uninstallList;
}

If the uninstall class can be instantiated, it will be executed by Magento\Setup\Model\ModuleUninstaller::uninstallData method. Otherwise, if the setup uninstall class can not be found or does not match the requirements, the module will be removed without a database clean up.

Try it out

Now, we are able to create our simple uninstall script. Let’s say we have a small “Atwix_SampleSetup” module, which adds a new custom table named atwix_sample during the installation. It means that the atwix_sample table should be also removed when the module is uninstalled. This issue can be solved by the following Uninstall script:

<?php
/**
 * @author Atwix Team
 * @copyright Copyright (c) 2016 Atwix (https://www.atwix.com/)
 * @package Atwix_SampleSetup
 */

namespace Atwix\SampleSetup\Setup;

use Magento\Framework\DB\Adapter\AdapterInterface;
use Magento\Framework\Db\Select;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
use Magento\Framework\Setup\UninstallInterface as UninstallInterface;

/**
 * Class Uninstall
 */
class Uninstall implements UninstallInterface
{
    /**
     * Atwix Sample Table Name
     */
    const SAMPLE_TABLE_NAME = 'atwix_sample';

    /**
     * Invoked when remove-data flag is set during module uninstall
     *
     * @param SchemaSetupInterface $setup
     * @param ModuleContextInterface $context
     *
     * @return void
     */
    public function uninstall(SchemaSetupInterface $setup, ModuleContextInterface $context)
    {
        $installer = $setup;
        $installer->startSetup();

        /** @var AdapterInterface $connection */
        $connection = $installer->getConnection();
        $connection->dropTable(self::SAMPLE_TABLE_NAME);

        $installer->endSetup();
    }
}

As you see the implementation is pretty simple and very similar to well-known install and upgrade scripts.

How to test?

Start by installing the module via composer in order to make the uninstallation possible. Our test module is stored in Atwix_SampleSetup GIT repository. We will install it using VSC from the master branch.

Add the module’s dependency into require section.

"atwix/sample-setup": "dev-master"

Define the module’s repository within the repositories section.

"repositories": 
[
    {
        "type": "git",
        "url": "https://bitbucket.org/dmitry95/atwix_samplesetup.git"
    }
]

Perform the package installation.

composer install

Enable the module.

bin/magento module:enable Atwix_SampleSetup

Upgrade the database.

bin/magento setup:upgrade

The module has been succesfully instaled. Now we can run the uninstall script.

bin/magento module:uninstall Atwix_SampleSetup

As a result of execution a success message will be shown.

You are about to remove code and/or database tables. Are you sure?[y/N]y
Enabling maintenance mode
You are about to remove a module(s) that might have database data. Do you want to remove the data from database?[y/N]y
You are removing data without a database backup.
Removing data of Atwix_SampleSetup
Removing Atwix_SampleSetup from module registry in database
Removing Atwix_SampleSetup from module list in deployment configuration
Removing code from Magento codebase:
Cache cleared successfully.
Generated classes cleared successfully. Please run the 'setup:di:compile' command to generate classes.
Info: Some modules might require static view files to be cleared. To do this, run 'module:uninstall' with the --clear-static-content option to clear them.
Disabling maintenance mode

The module has been successfully uninstalled. That’s it.