Drinking the Drupal-Composer Kool-Aid?

Do you want to manage modules and dependencies the PHP way instead of the "Drupal" way? Don't know how to use composer with Drupal? Are you planning to ditch drush make approach and adopt a composer based workflow?

If you answered yes to any of the above questions, then you should read this post.

Compose what?

Composer is PHP equivalent of Python's pip, Ruby's bundler and Node's npm. It helps manage projects and dependencies for a PHP project.

Why Composer?

The average PHP developer wrote 3.5 frameworks in their lifetime. This was before the era of package management. Then came along PEAR. PEAR did the job, but had some flaws. To point out one, for instance, PEAR installs packages globally instead of installing them on a per-project basis. So, if you install Acme libary v1.2, you had to use Acme v1.2 throughout all of your projects. Thankfully, Composer has succeeded PEAR as the de facto package manager for PHP.

The Drupal context

Modules and themes in Drupal are managed by drush, specifically drush make. Both composer and drush make do the same thing, i.e. download and install specific versions of Drupal modules and themes. Composer has an extra ingredient called autoload. This automagically figures out where your dependency files are there and includes them in your code as needed. Drupal 8 core adopted composer as the dependency manager. So if composer is great, why isn't everybody using it? To bridge this gap, Drupal 8 brought in a module called composer manager. This module helps manage composer dependencies of contrib modules. This might otherwise be done by editing Drupal 8 core's root composer.json and adding our module's composer dependencies there(which amounts to hacking the core).

So, if module acme depends on package foo, it will have a composer.json in the module's root directory with foo added as a dependency. Composer manager will add foo to Drupal's vendor directory. Starting with Drupal 8.1, we no longer need Composer manager to manage module dependencies. It can be done by composer alone. Let's take a look at how we pull this off.

Make sure you have Drupal 8.1.x installed and setup. Let's download the address module using composer.

/v/w/h/drupal-8.1.1↪ composer require drupal/address "8.1.*@dev
Warning: This development build of composer is over 60 days old. It is recommended to update it by running "/usr/local/bin/composer self-update" to get the latest version.
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - The requested package drupal/address could not be found in any version, there may be a typo in the package name.

Potential causes:
 - A typo in the package name
 - The package is not available in a stable-enough version according to your minimum-stability setting
   see <https://groups.google.com/d/topic/composer-dev/_g3ASeIFlrc/discussion> for more details.

Read <https://getcomposer.org/doc/articles/troubleshooting.md> for further common problems.

Installation failed, reverting ./composer.json to its original content.

Add packagist as a repository in your composer.json for the lookup to succeed.

NOTE this is a one time thing and you needn't do it for every module.

/v/w/h/drupal-8.1.1↪ composer config repositories.drupal composer https://packagist.drupal-composer.org/

Your `composer.json` will have an entry for `repositories.drupal`.

"repositories": {
    "drupal": {
        "type": "composer",
        "url": "https://packagist.drupal-composer.org/"
    }
}

Now, another shot at it.

/v/w/h/drupal-8.1.1↪ composer require drupal/address "8.1.*@dev" 
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Nothing to install or update
Generating autoload files
> Drupal\Core\Composer\Composer::preAutoloadDump
> Drupal\Core\Composer\Composer::ensureHtaccess

NOTE that this might take a bit of time if you have a lot of composer dependencies cached.

Enable address module, either via the UI or using drush.

/v/w/h/drupal-8.1.1↪ drush en address -y                                                                                  
The following extensions will be enabled: address
Do you really want to continue? (y/n): y
address was enabled successfully.                                                                                                          [ok]
address defines the following permissions: administer address formats, administer zones

All set to rock!

So, what happened behind the scenes?

Address module had a composer.json which reads like this:

{
  "name": "drupal/address",
  "type": "drupal-module",
  "description": "Provides functionality for storing, validating and displaying international postal addresses.",
  "homepage": "http://drupal.org/project/address",
  "license": "GPL-2.0+",
  "require": {
    "commerceguys/intl": "dev-master",
    "commerceguys/addressing": "dev-master",
    "commerceguys/zone": "dev-master"
  },
  "minimum-stability": "dev"
}

The above commerceguys/* dependencies got installed in the main vendor/ directory by composer.

composer vendor directories in Drupal docroot

What are the shortcomings of using composer alone to manage modules? For one, it would be too developer focused, in the sense that site builders(common audience for Drupal) won't be able to use it because of the complexity involved. When they have to download a module and enable it(a very common operation in Drupal), they can't do it entirely through the UI and have to run composer commands in the backend.

How to update modules using composer? We shall talk about that in the next post.