Categories


Archives


Recent Posts


Categories


Magento 2: Composer and Components

astorm

Frustrated by Magento? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

No Frills Magento Layout is the only Magento front end book you'll ever need. Get your copy today!

This entry is part 3 of 3 in the series Magento 2 and Composer. Earlier posts include Magento 2: Composer, Marketplace, and Satis, and Magento 2: Composer Plugins. This is the most recent post in the series.

One question I keep getting from new Magento 2 developers, (and we’re all new Magento 2 developers) is, “How should I organize my project files?”. Since Magento has heavily restructured code organization around Composer, it’s not always clear how files should be organized during development, organized for distribution, and how (if at all) to move between these two modes.

While this article won’t definitively answer those questions, we will dive into some of the formalization behind Magento 2 components, as well as how Magento 2’s Composer integration works. With this information in hand, you should be able to come up with a project structure that works for you and your team.

Magento 2 Components

Magento 1 had an informal, inconsistent idea of components. The best way to think about Magento components is

A group of source files, in various formats, with a single purpose in the system

If that’s a little vague, see our previous comments about informal and inconsistent. Speaking more concretely, the four component types in Magento are

In Magento 1, modules were pretty well defined and self contained

A folder of files with an etc/config.xml, with developers making Magento aware of the module via an app/etc/modules/Package_Namespace.xml file

Themes were a little less defined and a little less self contained

A collection of files under app/design/[area]/[package]/[name], with developers making Magento aware of the theme via a setting in core_config_data. Unless it’s the admin theme in which case you need a module. Also, modules are responsible for adding the layout XML files to themes. Also, while we’re here, modules aren’t all that self contained either because if they want to use phtml templates then the templates need to be in a theme folder

Things start to get really vague with language packs

A collection of key/value csv files located in app/locale/[language]/Packagename_Modulename.csv. Also we’re just going to drop email templates in here because reasons

And Magento 1 barely had the concept of a generic code library.

Um, yeah, maybe just drop them in lib and add that path to PHP’s autoloader? And look in the code pool folders too? And maybe just add a top level js folder for javascript libraries? Unless they go in skin?

Oh right! Skins! Magento 1 also had a (now dropped) concept of skins. Skins were best defined as

Any CSS or javascript file that doesn’t belong in a theme.

Where CSS or javascript file that doesn’t belong in a theme was defined as

Any CSS or javascript file that doesn’t belong in a skin

While, in practice, norms developed over time and development wasn’t as chaotic as I’m describing, Magento 1’s lack of formalization around components did make the system harder to work with, particularly if you were trying to redistribute Magento 1 code for reuse.

Magento 2 formalizes the idea of components, and this formalization means the core Magento system, and other external systems (i.e. Composer) can deal with these components in a sane and reasonable way.

Magento 2 Components

In Magento 2, a component is

A group of files, under a top level directory (with sub-folders allowed), with a registration.php file defining the type of component.

That’s it. There’s nothing about how the components work, interact with the system, or interact with other components. Those things aren’t the concern of the component system.

As of this writing, there are four component types in Magento 2

Let’s take a look at this in action. Consider the Magento_AdminNotification module. This is a collection of files, under a top level directory (AdminNotification), with a registration.php file. If we take a look at registration.php

#File: app/code/Magento/AdminNotification/registration.php
<?php
/**
 * Copyright © 2016 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Magento_AdminNotification',
    __DIR__
);

We can see this file registers a component via the static \Magento\Framework\Component\ComponentRegistrar::register method. This component is a module (\Magento\Framework\Component\ComponentRegistrar::MODULE), its identifier is Magento_AdminNotification, and you can find its files in the __DIR__ folder, (i.e. the same directory registration.php is in via PHP’s magic __DIR__ constant).

Next, consider the Luma theme. Again, a collection of files, under a top level directory (luma), with a registration.php file.

#File: app/design/frontend/Magento/luma/registration.php
<?php
/**
 * Copyright © 2016 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::THEME,
    'frontend/Magento/luma',
    __DIR__
);

Here the component type is a theme (\Magento\Framework\Component\ComponentRegistrar::THEME), and its name is frontend/Magento/luma.

Even though the Magento GitHub project has these files in familiar locations, (app/code, app/design, etc.), thanks to Magento 2’s new component system, these directories can be located anywhere, so long as as the module, theme, library, or language pack correctly defines its registration.php file.

How Magento Loads Components

At this point, the systems minded among you are probably wondering how Magento 2 loads and identifies components. It’s one thing to say so long as the module, theme, library, or language pack correctly defines its registration.php file, but the system still needs to load these files, and that means there are rules.

In order to get Magento to recognize your module, theme, code library, or language pack (i.e. your component), you need Magento to read your component’s registration.php file. There are two ways to get Magento to read your registration.php file

  1. Place your component in one of several predefined folders
  2. Distribute your module via Composer, and use Composer’s autoloader features

Of the two methods, the second is the preferred and recommended way of distributing Magento 2 modules. For development, the first offers a convenient way to get started on a component, or checkout/clone a version control repository to a specific location. The first also offers a non-composer way for extension developers to distribute their components.

Predefined Folders

At the time of this writing, Magento 2 will scan the following folders/files for components (the patterns below are for the glob function).

app/code/*/*/cli_commands.php
app/code/*/*/registration.php
app/design/*/*/*/registration.php
app/i18n/*/*/registration.php
lib/internal/*/*/registration.php
lib/internal/*/*/*/registration.php

This is what allows you to place modules in app/code/Packagename/Modulename, or themes in app/design/[area]/[package]/[name], etc. Magento will explicitly look for registration.php files to load at these locations. Also of interest are the app/code/*/*/cli_commands.php files — this appears to be a way for a module to register command line classes without using di.xml.

It’s not clear if these folders were added as a stop-gap measure while Magento 2 gets everyone moved over to Composer distribution, or if they’ll stick around for the long term. If you’re curious, Magento does this registration check in the following file

#File: app/etc/NonComposerComponentRegistration.php
<?php
/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */

$pathList[] = dirname(__DIR__) . '/code/*/*/cli_commands.php';
$pathList[] = dirname(__DIR__) . '/code/*/*/registration.php';
$pathList[] = dirname(__DIR__) . '/design/*/*/*/registration.php';
$pathList[] = dirname(__DIR__) . '/i18n/*/*/registration.php';
$pathList[] = dirname(dirname(__DIR__)) . '/lib/internal/*/*/registration.php';
$pathList[] = dirname(dirname(__DIR__)) . '/lib/internal/*/*/*/registration.php';
foreach ($pathList as $path) {
    // Sorting is disabled intentionally for performance improvement
    $files = glob($path, GLOB_NOSORT);
    if ($files === false) {
        throw new \RuntimeException('glob() returned error while searching in \'' . $path . '\'');
    }
    foreach ($files as $file) {
        include $file;
    }
}    

If you’re researching how a future version of Magento 2 handles scanning for components, this would be a good place to start.

Composer Distribution

The other way to have Magento notice your component is to distribute your component via Composer. We’re going to assume you have a basic familiarity with Composer, but for the purposes of this article, all you really need to know is

Composer allows you to ask for a package of PHP files, and have that package downloaded to the vendor/ folder

If you’re interested in learning more about Composer, the previous articles in this series, my Laravel, Composer, and the State of Autoloading, and the Composer manual are a good place to start.

So, assuming you have your Magento component in GitHub (or a different source repository Composer can point at), and your component has a registration.php file, the only question left is How do we get Magento to look at our registration.php file.

Rather than have Magento scan all of vendor/ for registration.php files, (an approach that could quickly get “O^N out of hand” as the number packages grows), Magento uses Composer’s file autoloader feature to load each individual component’s registration.php file.

If that didn’t make sense, an example should clear things up. Assuming you’ve installed Magento via the Composer meta-package, or installed it via the archive available via magento.com (which is based on the meta-package), take a look at the catalog module’s composer.json file.

#File: vendor/magento/module-catalog/composer.json
{
    "name": "magento/module-catalog",
    //...
    "autoload": {
        "files": [
            "registration.php"
        ],
        "psr-4": {
            "Magento\\Catalog\\": ""
        }
    }
    //...
}

The autoload section is where you configure the PHP class autoloader for a Composer package. This is covered in great detail in my Laravel, Composer, and the State of Autoloading series. The section we’re interested in today is here

#File: vendor/magento/module-catalog/composer.json
"files": [
    "registration.php"
],

The files autoloader section was originally intended as a stop gap measure for older PHP packages that had not moved to a PSR-0 (and later, PSR-4) autoloader system. Composer’s autoloader (not during install or update, but when your application is running) will automatically include any files listed in files (with the specific package as the base directory), and package developers can do whatever they need to do to setup their pre-PSR autoloaders.

Over the years, many frameworks have taken the simplicity and flexibility of the files autoloader and turned it to different purposes. Magento 2 is no exception. The above autoload configuration ensures Composer will always load the file at

#File: vendor/magento/module-catalog/registration.php
<?php
/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */

\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Magento_Catalog',
    __DIR__
);    

This, as we’ve already learned, will register the component. The same holds true for third party components (i.e. yours!) — make sure you’ve created a registration.php file with the correct registration code for your component type, and then include an identical files autoloader.

Here’s an example of each component type from Magento’s core.

Module

#File: vendor/magento/module-weee/registration.php
<?php
/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */

\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Magento_Weee',
    __DIR__
);

Theme

#File: vendor/magento/theme-frontend-luma/registration.php
<?php
/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */

\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::THEME,
    'frontend/Magento/luma',
    __DIR__
);

Library

#File: vendor/magento/framework/registration.php
<?php
/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */

\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::LIBRARY,
    'magento/framework',
    __DIR__
);

Language Pack

#File: vendor/magento/language-de_de/registration.php
<?php
/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */

\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::LANGUAGE,
    'magento_de_de',
    __DIR__
);

Invalid Assumptions

There’s one last important thing to take away from this, even if you’re not responsible for packaging your company’s Magento work. It’s no longer safe to make assumptions about where a folder is located in located in the Magento hierarchy. If you’re trying to find a specific file in Magento, it’s more important than ever to learn your way around the Magento\Framework\Module\Dir helper class.

Daily Work

So, now that we have a better understanding of what a component is, and how Magento loads components into the system, that still leaves us with our original question. Where should the code for our in progress Magento projects go? How should we store our projects in source control?

Unfortunately — there’s no clear answer, and a lot will depend on the sort of project you’re working on. Are you an extension developer? A theme developer? A system integrator/store builder or someone integrating with a Magento system? Do you want your working source repository to be the same repository Composer reads from? What tooling is your team is familiar with? While there certainly are approaches that are “better” for each scenario, from a programmer’s point of view Magento 2’s still too new to know for sure.

For what its worth, I’ve been creating symlinks to my source repositories so that NonComposerComponentRegistration.php finds my components, using a build process to create the final Composer accessible repository, and temporarily patching over any issues Magento has with symlinks.

Part of being a Magento 2 developer will be figuring this out for your own team, even if you’re just a team of one.

Originally published April 22, 2016
Series Navigation<< Magento 2: Composer Plugins