I’ve described how Lafayette’s deployment strategy involved pushing rocks uphill. A key change in our thinking came when we started treating each of our WordPress multisite installations as its own software project, with its own dependencies and lifecycle. Enabling this pivot was a technology which wasn’t mature in 2013: Composer.

What is Composer?

Composer is a package manager for PHP. It fills a role similar to npm for Node.js and bundler for Ruby. It uses a JSON file to capture metadata about the project and the project’s dependencies. If you have a custom application built on Symfony and Silex, your composer.json file might look like this:


{
    "name": "outlandish/wpackagist",
    "description": "Install and manage WordPress plugins with Composer",
    "require": {
        "php": ">=5.3.2",
        "composer/composer": "1.3.*",
        "silex/silex": "~1.1",
        "twig/twig": ">=1.8,<2.0-dev",
        "symfony/console": "*",
        "symfony/filesystem":"*",
        "symfony/twig-bridge": "~2.3",
        "symfony/form": "~2.3",
        "symfony/security-csrf": "~2.3",
        "symfony/locale": "~2.3",
        "symfony/config": "~2.3",
        "symfony/translation": "~2.3",
        "pagerfanta/pagerfanta": "dev-master",
        "franmomu/silex-pagerfanta-provider": "dev-master",
        "doctrine/dbal": "2.5.*",
        "knplabs/console-service-provider": "1.0",
        "rarst/wporg-client": "dev-master",
        "guzzlehttp/guzzle-services": "1.0.*"
    },
    "bin-dir": "bin",
    "autoload": {
        "psr-4": {
            "Outlandish\\Wpackagist\\": "src/"
        }
    }
}

The real action here is in the require block, where we spell out all the different packages that compose our application. Each key/value pair is an individual package and its version constraint. Composer users semantic versioning, and supports a wide range of expressions. Here are some quick examples:

  • “*”: wildcard; highest version possible (probably don’t ever do this)
  • “2.*”: highest version within the 2.x major version
  • “~1.2”: highest version between 1.2 and 2.0

The key name is the name of the package, in the format vendor/project name. By default Composer assumes that you’re installing these packages from Packagist, but as we’ll see that’s just the beginning of the Composer ecosystem.

What is Packagist?

Packagist is a centralized Composer repository which anyone can register packages on. It’s full of packages like the ones listed in the example above. Given a specific package and version constraint, it returns the matching files. Packagist is special inasmuch as it’s available by default for every Composer project, but it’s possible to define additional repositories if you have packages which aren’t in Packagist.

What Repositories, where?

Let’s say you’ve got a private project that you can’t publish to Packagist, but you’d like to make it available to an internal Composer project. Composer has a number of options for doing this. The simplest is adding your version control repository (VCS) as a repository to your project:


{
  "name": "yourcompany/yourproject",
  "description": "Your sample project",
  "repositories": [
    {
      "type": "vcs",
      "url": "https://github.com/yourcompany/someotherproject"
    }
  ]
}

This means that Composer will scan that repository, in addition to Packagist, when searching for packages. All you need to do to make this work is to add a reasonable composer.json file to that private project.

This is fine for one or two packages, but becomes unwieldy with a dozen or more. This is where a project like Satis becomes useful. Satis transforms a repository block into a lightweight Composer repository. This way, your internal projects need to include the Satis repository only—as you add new VCS repositories to Satis they become available to your downstream projects:


{
  "name": "yourcompany/yourproject",
  "description": "Your sample project",
  "repositories": [
    {
      "type": "composer",
      "url": "https://satis.yourcompany.com"
    }
  ]
}

What’s this got to do with WordPress?

Composer’s structure lets you overlay a dependency management system on existing code without too much pain. With your own private packages adding composer.json files is straightforward. Obviously you’re not going to get composer.json files added to every package on the WordPress theme and plugin repositories.

Fortunately, this is a solved problem. The fine folks at Outlandish maintain the WordPress Packagist composer repository. WPackagist scans the WordPress theme and plugin subversion repositories, mapping projects and versions in a way that composer can understand. The themes and plugins themselves are downloaded as zip files and extracted. At a stroke, all themes and plugins from WordPress.org are available to your project along with your private projects:


{
  "name": "yourcompany/yourproject",
  "description": "Your sample project",
  "repositories": [
    {
      "type": "composer",
      "url": "https://satis.yourcompany.com"
    },
    {
      "type":"composer",
      "url":"https://wpackagist.org"
    }
  ]
}

This is far more efficient than converting repositories to git, and you arrive at the same end result: specifying a known version constraint for a given project. Next up: how you actually deploy a WordPress site with this stuff.