Skip to content

Module structure

ProcessEight edited this page Jan 27, 2021 · 2 revisions

Module structure

A module must have, at the very least:

  • module.xml
  • registration.php

A module may also have an optional composer.json file.

The role of registration.php

How does Magento 2 find and load registration.php?

Upon running bin/magento module:enable Vendor_Module:

#11 registration.php:5, require_once()
#10 NonComposerComponentRegistration.php:29, Magento\NonComposerComponentRegistration\{closure:/var/www/html/m2/research/m23-example-modules/html/app/etc/NonComposerComponentRegistration.php:29-29}()
#9 NonComposerComponentRegistration.php:29, array_map()
#8 NonComposerComponentRegistration.php:29, Magento\NonComposerComponentRegistration\{closure:/var/www/html/m2/research/m23-example-modules/html/app/etc/NonComposerComponentRegistration.php:18-31}()
#7 NonComposerComponentRegistration.php:33, require()
#6 autoload_real.php:73, composerRequirefe24ed7cfcf7e56742d967c8a9b0a516()
#5 autoload_real.php:63, ComposerAutoloaderInitfe24ed7cfcf7e56742d967c8a9b0a516::getLoader()
#4 autoload.php:7, include()
#3 autoload.php:30, require_once()
#2 bootstrap.php:33, require()
#1 magento:14, {main}()

Starting at the bottom with stack frame:

  1. #1, bin/magento is the entry point.
  2. #2 initialises the Magento 2 environment.
  3. #3 registers an autoloader and validates that composer install has been run.
  4. These next three lines all call logic inside composer (and outside of Magento). They are all about loading autoloaders.
  5. This line includes a composer autoloader.
  6. This line includes a composer autoloader.
  7. Back in the Magento core, NonComposerComponentRegistration is a special file which contains a single function (it's not a method because it's not in a class).
  8. This method, called main, globs a list of components which cannot be autoloaded. These components are listed in the file it requires (html/app/etc/registration_globlist.php).
  9. The list of glob()bed files is iterated over with array_map.
  10. For each file in the glob()bed list, Magento 2 includes it with require_once.
  11. This is how the registration.php file is read by Magento 2.

What does registration.php do?

Each registration.php file calls \Magento\Framework\Component\ComponentRegistrar::register:

    /**
     * Sets the location of a component.
     *
     * @param string $type component type
     * @param string $componentName Fully-qualified component name
     * @param string $path Absolute file path to the component
     * @throws \LogicException
     * @return void
     */
    public static function register($type, $componentName, $path)
    {
        self::validateType($type);
        if (isset(self::$paths[$type][$componentName])) {
            throw new \LogicException(
                ucfirst($type) . ' \'' . $componentName . '\' from \'' . $path . '\' '
                . 'has been already defined in \'' . self::$paths[$type][$componentName] . '\'.'
            );
        } else {
            self::$paths[$type][$componentName] = str_replace('\\', '/', $path);
        }
    }

The purpose of this method is to check that the component is one of several pre-determined types defined at the top of the class:

    /**#@- */
    private static $paths = [
        self::MODULE => [],
        self::LIBRARY => [],
        self::LANGUAGE => [],
        self::THEME => [],
        self::SETUP => []
    ];

That property eventually contains a list of all the components (modules, themes, etc) registered by Magento 2.

This is used whenever a module wants to find the paths to installed component types.

The module:enable command

The command is defined here: \Magento\Setup\Console\Command\ModuleEnableCommand, but all the logic is in the parent abstract class: \Magento\Setup\Console\Command\AbstractModuleManageCommand.

On running the command, the execute method is executed and checks to see if there are any modules registered whose 'Status' has changed.

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        // Flag set in \Magento\Setup\Console\Command\ModuleEnableCommand or \Magento\Setup\Console\Command\ModuleDisableCommand
        $isEnable = $this->isEnable();
        // Check if we are enabling/disabling one module or all of them
        if ($input->getOption(self::INPUT_KEY_ALL)) {
            /** @var \Magento\Framework\Module\FullModuleList $fullModulesList */
            $fullModulesList = $this->objectManager->get(\Magento\Framework\Module\FullModuleList::class);
            $modules = $fullModulesList->getNames();
        } else {
            $modules = $input->getArgument(self::INPUT_KEY_MODULES);
        }
        $messages = $this->validate($modules);
        if (!empty($messages)) {
            $output->writeln(implode(PHP_EOL, $messages));
            return Cli::RETURN_FAILURE;
        }
        try {
            // Check which modules have changed status
            $modulesToChange = $this->getStatus()->getModulesToChange($isEnable, $modules);
        } catch (\LogicException $e) {
            $output->writeln('<error>' . $e->getMessage() . '</error>');
            return Cli::RETURN_FAILURE;
        }
        if (!empty($modulesToChange)) {
            $force = $input->getOption(self::INPUT_KEY_FORCE);
            if (!$force) {
                // Check to see if this module(s) have any dependencies on other modules
                $constraints = $this->getStatus()->checkConstraints($isEnable, $modulesToChange);
                if ($constraints) {
                    $output->writeln(
                        "<error>Unable to change status of modules because of the following constraints:</error>"
                    );
                    $output->writeln('<error>' . implode("</error>\n<error>", $constraints) . '</error>');
                    // we must have an exit code higher than zero to indicate something was wrong
                    return Cli::RETURN_FAILURE;
                }
            }
            // Enable the modules
            $this->setIsEnabled($isEnable, $modulesToChange, $output);
            // Clean cache and generated files and folders
            $this->cleanup($input, $output);
            // Regenerate generated files and folders
            $this->getGeneratedFiles()->requestRegeneration();
            if ($force) {
                $output->writeln(
                    '<error>Alert: You used the --force option.'
                    . ' As a result, modules might not function properly.</error>'
                );
            }
        } else {
            $output->writeln('<info>No modules were changed.</info>');
        }
        return Cli::RETURN_SUCCESS;
    }

\Magento\Framework\Module\Status::checkConstraints checks to see if the module depends on any other modules. \Magento\Framework\Module\Status::setIsEnabled enables the changed modules. \Magento\Framework\App\Cache is cleaned. \Magento\Framework\App\Filesystem\DirectoryList::GENERATED_CODE and \Magento\Framework\App\Filesystem\DirectoryList::GENERATED_METADATA folders are cleared. Regeneration of generated files is requested by creating the flag .regenerate in var/