Magento 2 and Composer

Magento 2 uses Composer to manage its packages and dependencies. Available from the console, it provides and standardizes the format of dependencies management. Thanks to this, we can download Magento with just one command:

$ composer create-project --repository-url=https://repo.magento.com/ magento/project-community-edition <installation directory name>

As we can see, all the necessary modules are in the vendor folder. Its content is added to .gitignore.

//file: .gitignore

...
/vendor/*
!/vendor/.htaccess
...

This way, our repository doesn’t contain the entire code. While cloning the repository, it’s enough to use one command in Composer to install the project in a new location.

$ composer install

What about app/code?

There’s nothing standing in the way of using app/code to deploy modifications. This is what module vendors sometimes suggest. Their modules are in .zip packages and the simplest solution is to unpack them to app/code.
Using Composer in such an example is not a problem. What’s the benefit?

Files in vendor

The code is located in the vendor folder – every file modification (e.g. during a debug) will be deleted upon the next deploy in which the packages are reloaded. It forces us to develop an obligatory habit of overwriting methods. Every future module update will come down to replacing a .zip package and launching Composer.

$ composer update vendor/modulename

The power of dependencies

Due to the fact that modules have inscribed dependencies, Composer will make sure to get all packages that a particular module needs during the installation. For example, here are the packages needed for the Magento_Bundle module:

//file: vendor/magento/module-bundle/composer.json

...
    "name": "magento/module-bundle",
    "description": "N/A",
    "require": {
        "php": "~5.6.5|7.0.2|7.0.4|~7.0.6",
        "magento/module-store": "100.1.*",
        "magento/module-catalog": "101.0.*",
        "magento/module-tax": "100.1.*",
        "magento/module-backend": "100.1.*",
        "magento/module-sales": "100.1.*",
        "magento/module-checkout": "100.1.*",
        "magento/module-catalog-inventory": "100.1.*",
        "magento/module-customer": "100.1.*",
        "magento/module-catalog-rule": "100.1.*",
        "magento/module-eav": "100.1.*",
        "magento/module-config": "100.1.*",
        "magento/module-gift-message": "100.1.*",
        "magento/framework": "100.1.*",
        "magento/module-quote": "100.1.*",
        "magento/module-media-storage": "100.1.*",
        "magento/module-ui": "100.1.*"
    },
...

I’d like that too!

Let’s use a module to see an example of how to prepare such a package.

Preparing a package

We need to make a module that doesn’t do anything, but is good to create a package.

Let’s add the following folders:

Magently/Package
Magently/Package/Block
Magently/Package/Helper
Magently/Package/etc

Next, we add the module.xml file with the information about our module:

//file: Magently/Package/etc/module.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Magently_Package" setup_version="1.0.0">
    </module>
</config>

Here’s where we specify the version of our module to 1.0.0.

Next, we register our package as a module by adding the registration.php file with the following content:

//file: Magently/Package/registration.php

<?php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Magently_Package',
    __DIR__
);

The last file we need is composer.json:

//file: Magently/Package/composer.json

{
    "name": "magently/package",
    "description": "Some modifications.",
    "require": {
        "php": "~5.5.0|~5.6.0|~7.0.0",
        "fooman/emailattachments-m2": "^2.0.7"
    },
    "type": "magento2-module",
    "version": "1.0.0"
}

As we can see, it’s a simple json file with the package information required by Composer. We specify the name, description, type (i.e. magento2-module, magento2-language), and the version. In require we specify what’s necessary for our module – apart from the php version, we added the fooman/emailattachments-m2 module located in repo.magento.com.

With the package prepared, we can add the Package folder to zip:

$ zip -r Package.zip Package

Then, we move the file to the location we deem appropriate. We created packages folder in the main Magento folder, and inside it a folder with the vendor name – magently.

Adding the package to the project

Let’s proceed to the most important step. In the main composer.json file (in the main catalog), we have to add our module and specify where it should be downloaded from. We add the following to require:

//file: composer.json

...
        "magently/package": "^1.0"
...

In repositories, we specify the available repositories. We need to communicate to Composer where to look for the .zip package (in the packages/magently folder).

//file: composer.json

...
    "repositories": [
        {
            "type": "composer",
            "url": "https://repo.magento.com/"
        },
       {
            "type": "artifact",
            "url": "packages/magently"
       }
    ],
...

Now we can install our package:

$ composer require magently/package

Using version ^1.0 for magently/package
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing fooman/emailattachments-m2 (2.0.7)
    Loading from cache

  - Installing magently/package (1.0.0)
    Downloading: 100%         

fooman/emailattachments-m2 suggests installing fooman/printorderpdf-m2 (Allows attaching the order confirmation pdf to the order confirmation email)
fooman/emailattachments-m2 suggests installing fooman/pdfcustomiser-m2 (Allows to easily customise the sales pdfs while also reducing file sizes )

Writing lock file
Generating autoload files

We can see in the output that our package was installed in the 1.0.0 version and that the fooman/emailattachments-m2 package was downloaded. Now, in the vendor folder we can find the magently/package folder with the content of the zip, as well as the fooman/emailattachments-m2 folder with the module we need.

After this operation, Composer updated the composer.lock file with information about new/changed packages, including the ones for magently/package.

//file: composer.lock

...
        {
            "name": "magently/package",
            "version": "1.0.0",
            "dist": {
                "type": "zip",
                "url": "packages/magently/Package.zip",
                "reference": null,
                "shasum": "077deae7443e412ed1443afce1b898229ac374df"
            },
            "require": {
                "fooman/emailattachments-m2": "^2.0.7",
                "php": "~5.5.0|~5.6.0|~7.0.0"
            },
            "type": "magento2-module",
            "description": "Some modifications."
        },
...

Updating such a package comes down to changing the version, replacing the zip pack and executing one command.

  • We change the version in Magently/Package/etc/module.xml and Magently/Package/composer.json from 1.0.0 to a newer one;
  • We create zip and replace the package in packages/magently;
  • We execute the command.
$ composer update magently/package

Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Removing magently/package (1.0.0)
  - Installing magently/package (1.0.1)
    Downloading: 100%         

Writing lock file
Generating autoload files

If you wish to find out more about managing Composer packages, we recommend visiting the home page of the project.

Do you have any questions or remarks? Let us know in the comments!