Switching to old git branches in Magento 2

With your daily work you probably jump between a lot of git branches. You check something on older branch, then you check other things on preproduction branch, finally you work on new features creating fresh copy of production branch.
In Magento 2 there is a problem which can successfully block your optimistic branch jumping. This problem looks like that:



1 exception(s):
Exception #0 (Magento\Framework\Exception\LocalizedException): Please upgrade your database: Run "bin/magento setup:upgrade" from the Magento root directory.
The following modules are outdated:
Alekseon_ProductTabs schema: current version - 1.0.1, required version - 1.0.0
Alekseon_Catalog data: current version - 1.0.8, required version - 1.0.4

Of course versions are just examples and might be different in your case, but the point is that version on the left is higher than the one on the right.
The reason is simple. Your etc/module.xml file contains different version than the one defined in database, and Magento is not able to downgrade.
Magento will suggest you to run setup:upgrade or composer:install command but none of them will work.

Let's check how it was in Magento 1

Magento 1 used similar mechanism to manage module version. You were simply defining version of module in your config.xml file and then Magento compared file version and database version to decide if module is up to date or there are scripts to be run.
Magento 1 obviously also didn't have possibility to downgrade, but it was never returning error while version in database was higher than the one in files.


Let's check how it is when you have no module in Magento 2

But how Magento 2 will behave when we will switch to the branch which doesn't contain files of module already installed in database. I was expecting similar results, but in this case Magento 2 doesn't return any error, while it should also require module to be installed in correct version defined in database.


Solution

Currently after we analyze all risks and possible solutions, we think that the best option is to let Magento 2 continue working in case database version is higher then one in the files
Of course it has some side effects, but for development environment it's something which might save some time and some problems

To make it work this way you will need to create small plugin:



<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Framework\Module\DbVersionInfo">
        <plugin disabled="false" name="DbVersionInfoPlugin" sortOrder="10" type="Alekseon\DbVersionFix\Plugin\DbVersionInfoPlugin"/>
    </type>
</config>

<?php

namespace Alekseon\DbVersionFix\Plugin;

use Magento\Framework\Module\ModuleListInterface;
use Magento\Framework\Module\ResourceInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;

class DbVersionInfoPlugin
{
    /**
     * @var ModuleListInterface
     */
    private $moduleList;
    /**
     * @var ResourceInterface
     */
    private $moduleResource;

    /**
     * DbVersionInfoPlugin constructor.
     * @param ModuleListInterface $moduleList
     * @param ResourceInterface $moduleResource
     */
    public function __construct(
        ModuleListInterface $moduleList,
        ResourceInterface $moduleResource
    ) {
        $this->moduleList = $moduleList;
        $this->moduleResource = $moduleResource;
    }

    /**
     * @param $subject
     * @param $proceed
     * @param $moduleName
     * @return bool
     */
    public function aroundIsSchemaUpToDate($subject, $proceed, $moduleName)
    {
        $result = $proceed($moduleName);
        if (!$result) {
            $dbVer = $this->moduleResource->getDbVersion($moduleName);
            $result = $this->isModuleVersionLower($moduleName, $dbVer);
        }
        return $result;
    }

    /**
     * @param $subject
     * @param $proceed
     * @param $moduleName
     * @return bool
     */
    public function aroundIsDataUpToDate($subject, $proceed, $moduleName)
    {
        $result = $proceed($moduleName);
        if (!$result) {
            $dbVer = $this->moduleResource->getDataVersion($moduleName);
            $result = $this->isModuleVersionLower($moduleName, $dbVer);
        }
        return $result;
    }

    /**
     * @param $moduleName
     * @param $version
     * @return bool
     */
    private function isModuleVersionLower($moduleName, $version)
    {
        $module = $this->moduleList->getOne($moduleName);
        $configVer = $module['setup_version'];
        return ($version !== false
            && version_compare($configVer, $version) === ModuleDataSetupInterface::VERSION_COMPARE_LOWER);
    }
}

I hope it will help for some of you