You have heard the advice already: Use composer. However, I frequently hear that Magento 2 modules are still developed in app/code because of reasons. Often this based upon wrong assumptions. I'll show you why.

The truth: A module always has dependencies

Why the fuzz? Basically, a module always has dependencies. Whenever you develop a Magento 2 module, it will re-use code from the core: Starting from the ComponentRegistrar in your registration.php file, to XML files being parsed by Magento, to specific features from specific modules (like Magento_Catalog or Magento_Sales).

Your module integrates with Magento itself and therefore, it has dependencies.

The bare minimum is that you have a requirement with the Magento framework. This might be version 101.0 or later. Or it might be still compatible with 100.0 as well. As soon as you realize that your module always has dependencies with specific versions of Magento packages, you should realize that those dependencies will change in the future. And if you haven't thought this over, the module that you have written will cause unexpected issues - perhaps even frustration.

Use composer to track dependencies

This is why we use composer: To track dependencies. And it is easy as hell to create a new composer file. Copy and paste from other examples, or perhaps some code-generator to fill in the blancs. And there you go. If you are unsure on what to enter as a minimum, base your composer.json file on the example below, and then use composer validate for further suggestions.

{ 
    "name": "yireo/magento2-example",
    "version": "0.0.1",
    "type": "magento2-module",
    "autoload": {
        "psr-4": {
            "Yireo\\Example\\": ""
        },
        "files": [
            "registration.php"
        ]
    }
}

Next, track the dependencies in your code and convert them to composer dependencies. I've personally written a little module for this Yireo ExtensionChecker but you can easily do this manually as well by inspecting the PHP namespaces you have used and/or specific XML files (like di.xml).

A line like this is quite common:

"require": {
    "magento/framework": "^100.1|^101.0|^102.0"
},

If you are unfamiliar with the syntax of the versioning, stop right here. You should be familiar with this. Otherwise, you are in big trouble if a Magento upgrade fails. A fundamental understanding of semantic versioning is required to make a success of handling Magento packages, including the core and including your own.

Track PHP compatibility with composer

Also realize that you might add in PHP code that has different opinions on PHP compatibility of the core. For instance, adding new PHP 7.1 features that are not supported by Magento 2.1 breaks compatibility. This needs to be declared in your composer.json file.

The Magento Marketplace rules say that a Magento extension should follow the compatibility of the Magento main version (2.2, 2.3, etc). But I disagree: It is perfectly possible that you want to enforce newer PHP versions for your own code. Especially, if we are dealing with custom modules for a custom project.

"require": {
    "magento/framework": "^100.1|^101.0|^102.0",
    "php": ">=7.1.0",
    "ext-gd": "*"
},

Also, I've made the mistake in the past to write code using the PHP YAML extension, without specifying the PHP YAML extension in my composer.json. Hosting environments tend to change. Don't let those changes break your shop. Simply document these requirements in your composer file. PHPStorm actually gives these suggestions as well.

Add composer.json to any module!

The bottomline is this: Regardless of whether you use composer to install this module, regardless of where this module is maintained, the code of your module always has dependencies and at a bare minimum, spend 5 minutes on the steps explained above.

I personally feel that any module should have a composer.json. It takes 5 minutes. And if you don't invest this time, you might simply be lazy. To track down the right versions of your dependencies is maybe a more thorough task that you might want to skip (when dealing with custom code, not re-usable modules), but simply listing the dependencies somewhere might improve managing code.

And now, because you have already added a composer.json to your module, why not use composer to install it?

My approach: Use composer paths

Now, let's move on to the main trick in this post: You can create a custom folder extensions/ in your custom Magento project and then use composer to install packages from there. So let's assume the following folder structure (part of the Magento root folder):

extensions/
extensions/Yireo_Example/
extensions/Yireo_Example/registration.php
extensions/Yireo_Example/composer.json
extensions/Yireo_Example/etc/module.xml

I can use the following command to install this extension:

composer config repositories.yireo-example path extensions/Yireo_Example

If there are multiple modules stored in the folder extensions, we can also use a wilcard:

composer config repositories.dev-extensions path extensions/\*

Composer is now able to install my extension from this folder:

composer require yireo/magento2-example:@dev

I add @dev as a version, so that if a stable version of the same module is found on Packagist, it is still preferring my own local extensions/ folder. There are more ways to do this, but that's beyond the scope of this blog.

Composer will create a symbolic link from the extensions/Yireo_Example folder to vendor/magento2-example, which allows me to have all benefits of Composer (autoloading, checking dependencies). Perhaps you then want to exclude the vendor/magento2-example folder from your PHPStorm project, but that's a detail. The main point is to show how easy it is to use composer for this.

You don't need a composer repo to use composer

While your Magento project lives in its own Git repository, you usually install Magento modules via composer, while they themselves are hosted in other composer repositories (Marketplace, GitHub, somewhere). This does not mean that you need a composer repository per module.

The method above shows that composer packages could be simply installed from a local folder. And this folder might be part of the git repository of the Magento project. Composer is not about splitting up the code in numerous repositories. Composer is about tracking dependencies.

Choosing app/code for the wrong reasons

I can still hear you say that app/code still comes in handy. Let's focus upon this.

Wrong reason: It is easy to work in app/code

It makes no difference. It is little work to add a composer.json file, even if you don't properly document your modules dependencies in there. Using composer is just as easy as using app/code. However, documenting your dependencies can be harder. But start by opening up the dependencies in such a way that it works in all (or most) cases:

"magento/framework": "^101",

This kind of version management is wrong. But if you don't have 5 minutes to think in a sane way, you can finetune this later - before or after things have been broken by some kind of undocumented dependency.

Wrong reason: Local modules and project are the same

When you document your dependencies, you might say that those dependencies are already documented in the main project. Let's say you are on Magento 2.3, which ships with the framework 102.0. Why bother documenting the specific dependency of the framework? If you upgrade the core, you'll also upgrade the framework right?

True. But you probably assume that the core and the modules deal with minor updates (from 2.3.1 to 2.3.2) in the same way. And they don't. The framework might be upgraded with a new major version 103.0 while you are still under Magento 2.3. True, it is the job of Magento to keep their own versions semantically correct. And they are doing just that. By using composer and semantic versioning for the modules below, managed by versions starting from 100.

The version 2.3 is just for marketeers. It is not for developers. We use composer.

(I actually don't know if a roll-out of 103.0 would be part of a minor core upgrade, but it serves the example.)

Wrong reason: We rely on DI compile detect issues

Not all issues are detected by running bin/magento setup:di:compile.

Right reasons for choosing app/code

So what would be the right reasons to choose app/code for modules. Well, often I still use the app/code folder to create dummy modules that are not proven to be deploy-worthy yet: Add some hacks, see if it works, maybe remove it. But once I have proven to myself that the module is deploy-worthy, I want to make sure composer checks dependencies for me. Because I'm lazy. I use composer because I don't want to mess around with dependency checks myself.

Apart from kick-starting modules, to me, using app/code is not needed. Tell me if you disagree, so we can debate :)

UPDATE (May 23rd 2019)

I'm happy to say that this post spurred some counter-reactions from highly appreciated developers in the Magento community. In short, not everybody is in favor of making composer mandatory for all packages. The debate pointed out that there are different groups of developers (and scenarios) that might have a different approach to composer.

For extension developers (of 3rd party extensions), composer should be mandatory. Next, project developers might want to opt for composer as well. However, it might cause so much additional work to manage versioning properly per package, that it might defy the purpose of composer. When packages are re-used across projects, still composer is a really good idea. But in the case of custom local packages that are only used in a single project, defining the requirements in the global composer.json is just as good as using a composer.json per module. So, in specific use-cases, using only composer packages leads to more work but not necessarily more benefit.

However, in the same debate, wrong arguments popped up: The argument of having an explosion of git repositories because using composer is wrong: The approach in this blog suggests a fine way of using a monorepo and still use composer. Another argument was that semantic versioning is used in a wrong way with Magento. The fact might be true, but it doesn't mean that composer as a whole should be abandoned at all - you can also define dependencies with a * version and still have composer check things to make sure dependencies are not removed using the composer replace trick. I keep being a firm believer in composer for module distribution. It is simply that the debate showed that not everybody needs to agree with this.

Posted on May 10, 2019

About the author

Author Jisse Reitsma

Jisse Reitsma is the founder of Yireo, extension developer, developer trainer and 3x Magento Master. His passion is for technology and open source. And he loves talking as well.

Sponsor Yireo

Looking for a training in-house?

Let's get to it!

We schrijven niet te commerciële dingen, we richten ons op de technologie (waar we dol op zijn) en we komen regelmatig met innovatieve oplossingen. Via onze nieuwsbrief kun je op de hoogte blijven van al deze coolness. Inschrijven kost maar een paar seconden.

Do not miss out on what we say

This will be the most interesting spam you have ever read

We schrijven niet te commerciële dingen, we richten ons op de technologie (waar we dol op zijn) en we komen regelmatig met innovatieve oplossingen. Via onze nieuwsbrief kun je op de hoogte blijven van al deze coolness. Inschrijven kost maar een paar seconden.