WordPress with Composer and Git

Composer is a modern package manager for PHP. Using Composer with WordPress makes it easier to keep WordPress projects in version control because your repository can include only your own files and a description of dependencies (composer.json), and not WordPress or third party plugins and themes. Submodules are an alternative, but they have their own inconveniences, and I find it easier to get the directory structure to play nice by using Composer.

The basic usage pattern goes like this. You’ll need to install composer using the package manager for your platform or from its web site, then create a composer.json as described below in a new (empty, for now) directory. Running composer update will then create another file, composer.lock, which describes the exact versions of dependencies to be installed (and generally should be checked into source contorl along with composer.json). Finally, composer install will download all needed packages.

The way the composer.json below is set up, your WordPress installation will go into a directory called webroot/ under the root directory of your project, while non-WordPress-related dependencies will by default go into a directory called vendor/.

You can then put site-specific custom plugins and themes directly into the wp-content/plugins and wp-content/themes directories and track them in source control, while ignoring all of the third-party files using the .gitignore approach below.

I do want to acknowledge an alternate approach, documented at roots.io. That approach uses a third-party installer and puts the WordPress core into a subdirectory, which requires extra fiddling. This approach only uses the official composer/installers package (with a small workaround) and keeps the normal WordPress directory structure (with .gitignore rules to keep the core out of the repo).

The base composer.json file

composer.json is a file that describes a package installable by Composer and/or a list of packages (dependencies) that Composer should install. Our project needs a composer.json that identifies WordPress as a dependency, plus any plugins, themes, or other vendor libraries to install.

WordPress does not have its own composer.json, so we will also need to add a custom package definition for it, plus some rules about install locations.

There is a very long discussion on a WordPress ticket about whether the project should actually include a composer.json file, which some view as being for libraries rather than applications. Personally I find that when developing fully customized sites, WordPress is more of a framework than an application and using Composer to manage it as a dependency works better for me than a submodule or just downloading a zip.

Here is the base composer.json for installing just WordPress. Update the version numbers as needed.

{
    "require": {
        "wordpress": "*",
    },
    "repositories": [
        {
            "type": "package",
            "package": {
                "name": "wordpress",
                "type": "wordpress-plugin",
                "version": "4.1",
                "dist": {
                    "url": "https://github.com/WordPress/WordPress/archive/4.1.zip",
                    "type": "zip"
                },
                "require": {
                    "composer/installers": "~1.0"
                }
            }
        }
    ],
    "extra" : {
        "installer-paths" : {
            "webroot": ["wordpress"],
            "webroot/wp-content/plugins/{$name}/": ["type:wordpress-plugin"],
            "webroot/wp-content/themes/{$name}/": ["type:wordpress-theme"]
        }
    }
}

The need to identify WordPress core as a wordpress-plugin is unfortunate, but stems from composer/installers only having support for a set list of types.

Installation

Run composer install the first time to generate a composer.lock file with specific installed versions, download WordPress and unpack it into webroot/.

In the future, you’ll need to run composer update (or delete composer.lock) after making changes such as adding plugins.

Adding plugins and themes from the official site

WordPress Packagist provides a full mirror of the WordPress plugin and theme collections for use with Composer.

First, add the following as an item in the "repositories" object:

{
    "type": "composer",
    "url": "http://wpackagist.org"
}

Since the installer-paths for both plugins and themes are both already set up in the base example, you then only have to add lines to the "require" section. For example:

"wpackagist-plugin/akismet": "*",
"wpackagist-theme/Responsive": "*"

Using the wpackagist-plugin/ and wpackagist-theme/ prefixes, you can pull in any plugin or theme you find on the main WordPress site.

Adding GitHub or other repositories

Now, say you want to pull in a package that is on GitHub and not in wpackagist. You’ll need to add an item to the "repositories" list for each repository, like this:

{
    "type": "vcs",
    "url": "https://github.com/USER/REPO"
}

Then add a line to "require":

"USER/REPO": "*"

Or, if you want to pull in version numbers that Composer considers too early to be in production:

"USER/REPO": "*@dev"

.gitignore rules

Because we’re pulling in so many third-party files here and then mixing them together with our own, it makes sense to use a whitelist-style .gitignore file.

This file starts out by ignoring all paths, then selectively including the ones that are actually part of the project.

*
!*/
!/.gitignore
!/composer.json
!/composer.lock
!/webroot/wp-config.php
!/webroot/favicon.ico
!/webroot/robots.txt

You’ll need to add more rules for whatever other files are going into your webroot, such as other icons, Google Webmaster Tools verification files, etc.

You may want to include themes and plugins in the repository. Typically this would apply to custom themes and plugins that are specifically for the site your are developing (reusable ones can be brought in through Composer or as Git submodules).

This can be done in .gitignore using wildcards:

!/webroot/wp-content/plugins/myplugin/**
!/webroot/wp-content/themes/mytheme/**