Cambrico.net

Using Composer to build your Drupal 7 projects

Crosspost from the WunderRoot blog. This is an article I wrote when I worked at WunderRoot and it's the output of an evaluation on how feasible is to use Composer as an alternative to Drush make.

What is composer

Composer is an excellent tool to manage dependencies in PHP. It allows you to define a set of packages or libraries and it will figure out the right dependencies to install and with the right versions.
There’s a trend that grows bigger every day in the Drupalverse to try to reach out and use standard, or broader tools instead of Drupal-only tools, such as Drush make.

Composer vs Drush make

Drush make is a really useful tool to build Drupal projects, it allows you to rely on drupal.org and other sites to build your project which comes with a great set of advantages:

  • We don’t need to include and maintain the contrib modules and libraries in your codebase.
  • We can switch or include extra makefiles for different environments.
  • Flexibility to build your project from sources and switch versions with ease.
  • Maintainability and accountability. We’re aware of which version is used for every component and keep track of the changes (patches on these).
  • Drush make is included in Drush already, so you most probably have it available for your project.

Composer offers a non-Drupal way to do a set of similar actions, providing a composer.json declarative file instead. It provides a more robust dependency management feature - which is a manual action in Drush make - and provides an optional autoload.php file that could become handy for those projects using more OOP structures (so using it now will help you getting ready for Drupal 8 as a side effect).

Additionally, the composer update command, by using the composer.lock file, makes sure that the dependencies are always on track on every environment, including the specific versions required, so you can opt for not removing the contents prior to deploying new changes and just rely on the updates.

Packagist

On the other hand, Drush make is tightly related with drupal.org for providing the code, whereas composer is a more generic utility and will use packagist instead.
Luckily, and thanks to the efforts of webflo, derhasi and winmillwill among others, there’s a Drupal Packagist site now, that we can rely on to make our composer.json files simpler - more on this later.
You can follow the Drupal packagist project in github, and this issue to improve the sync between drupal.org and Drupal Packagist.

How to include modules, libraries and themes in composer

By using Drupal Packagist, you can rely on most of the packages there and simply use this line in composer.json

"repositories": [
  {
    "type": "composer",
    "url": "http://drupal-packagist.webflo.io/"
  },
]

And then require the packages directly, without the need to define them.

"require": {
  "drupal/drupal": "7.34",
  "drupal/addressfield": "7.1.0-rc1",
  "drupal/ctools": "7.1.5",
  "drupal/date": "7.2.8"
}

Or depend of a git tag or hash:

"require-dev": {
  "drupal/entityreference": "7.1.x-dev#dc4196b4e97e11ff24e762d94864132e12daf7be"
}

But composer leaves you the option to completely redefine a package in case you need it, this could be that the version is not in Drupal Packagist yet, or the dependencies are not accurate.

{
  "type": "package",
  "package": {
    "name": "drupal/services",
    "version": "7.3.10",
    "type": "drupal-module",
    "dist": {
      "url": "http://ftp.drupal.org/files/projects/services-7.x-3.10.tar.gz",
      "type": "tar"
    }
  }
}

Or even checking out from git - this will make the process slower, but allows you to use custom modules in different repositories too.

{
  "type": "package",
  "package": {
    "name": "drupal/views_datasource",
    "version": "7.1.0-alpha1-dev",
    "type": "drupal-module",
    "source": {
      "url": "http://git.drupal.org/project/views_datasource.git",
      "type": "git",
      "reference": "c15e455cebe36c1a2ef1082da4b0ea7d93db2ed5"
    }
  }
},

By using the plugin custom installer, we can use custom strings to define the destination for your modules, themes, libraries… see the “drupal-module” as a package type above.

"extra": {
  "custom-installer": {
    "drupal-module": "docroot/sites/all/modules/contrib/{$name}/",
    "drupal-theme": "docroot/sites/all/themes/contrib/{$name}/",
    "drupal-library": "docroot/sites/all/libraries/{$name}/",
    "drupal-drush": "docroot/sites/all/drush/{$name}/",
    "drupal-profile": "docroot/profiles/{$name}/",
    "drupal-core": "docroot/"
  }
}

There’s also an official composer plugin for this that supports a similar structure: installers

Also, we’re able to keep/ignore certain paths when we do composer install/update, so our custom code is not affected by the package dependencies. For that, we need to include the composer preserve paths plugin.

"extra": {
  "preserve-paths": [
    "docroot/sites/all/modules/contrib",
    "docroot/sites/all/modules/custom",
    "docroot/sites/all/modules/features",
    "docroot/sites/all/themes/contrib",
    "docroot/sites/all/themes/custom",
    "docroot/sites/all/libraries",
    "docroot/sites/all/drush",
    "docroot/sites/default/settings.php",
    "docroot/sites/default/files"
  ]
} What about patches

Any given Drupal project build will inevitably result in finding bugs and missing features in the modules and libraries used. The right thing to do is to build patches and contribute them back using the issue queue of Drupal.org (or a github Pull Request if the module code lives there) or if we’re lucky, maybe someone already fixed the issue and posted a patch we can use.

For including patches in our project, we can use either the composer patches plugin from netresearch or jpstacey’s composer patcher project.

One way to do this is to declare the patches as a new repository:

{
  "type": "package",
  "package": {
    "name": "mynamespace/myproject-patches",
    "version": "1.0.0",
    "type": "patches",
    "require": {
      "netresearch/composer-patches-plugin": "~1.0"
    },
    "extra": {
      "patches": {
        "drupal/entityreference": {
          "7.1.x-dev": {
            "dc4196b4e97e11ff24e762d94864132e12daf7be": {
              "title": "Allow skipping entity access check when rendering field",
              "url": "https://www.drupal.org/files/issues/entityreference-skip-access-check-1967180-16.patch"
            }
          }
        },
        "drupal/features": {
          "7.2.2":[
            {
              "title": "Remove mtime from .info export (added by Drupal 7.33)",
              "url": "https://www.drupal.org/files/issues/2381739-features-mtime.patch"
            }
          ]
        }
      }
    },

And include it in the require or require-dev section of our file:

  "require": {
    "davidbarratt/custom-installer": "dev-master",
    "derhasi/composer-preserve-paths": "0.1.*",
    "mynamespace/myproject": "*",
    "drupal/drupal": "7.34",
    "drupal/entityreference": "7.1.x-dev#dc4196b4e97e11ff24e762d94864132e12daf7be",
    "drupal/features": "7.2.2"
  } What if I hacked the module

There are some cases where we find hacked modules (modules where the code has been directly edited by a developer, rather than extended or patched in a way that complies with Drupal coding standards — not ‘hacked’ in a security sense), most likely to happen if we take over the project from someone else, because the reader of this article would know that’s a bad practice :).
Let’s say that for a number of reasons, a module that we’re using is hacked and a number of the other modules depend on it. The normal workflow from composer would be to look for a dependency and if it’s not declared, because we’ve added this hacked module to the repo itself and we’re not pulling it from anywhere, composer will break the process due to unmet dependencies.
We can still get over this, but it is a little painful, we can use the “provide” element to declare that we’re providing that package in our repo already and then we need to update all the packages that are depending on the hacked one.

Say for this example we use entity reference, rules and entity modules:

"require": {
  "drupal/entity": "7.1.5",
  "drupal/entityreference": "7.1.1",
  "drupal/rules": "7.2.7"
}

And then we move entity to our codebase because it is hacked, we need to declare that our composer.json file provides this package:

"require": {
  "drupal/entityreference": "7.1.1",
  "drupal/rules": "7.2.7"
},
"provide": {
  "drupal/entity": "*"
},

But if we run composer install, both entityreference and rules point to a version of entity in packagist, so it will still download the dependencies, see this github issue for more details.
We need to declare the dependent packages and replace the dependencies:

{
  "type": "package",
  "package": {
    "name": "mynamespace/entityreference",
    "version": "7.1.1",
    "type": "drupal-module",
    "dist": {
      "url": "http://ftp.drupal.org/files/projects/entityreference-7.x-1.1.tar.gz",
      "type": "tar"
    },
    "require": {
      "drupal/drupal": "7.34",
      "drupal/ctools": "7.*"
    }
  }
},

The mynamespace/entityreference package doesn’t have a requirement to entity, so we’re good to go.

Example will all the above together

Here are a couple of gists with the examples used in this post:

Resources

There’s an extensive number of resources out there that can be really useful to get a good idea of what’s going on and what can be useful, here are a few:

Thanks to Christian, ciss and J-P Stacey for reviewing this post.