A tidy WordPress project repository with Composer

— An article about: - Composer - PHP - Wordpress

A WordPress project usually involves creating custom theme(s) and/or plugin(s) and adding them to a WordPress install, along with maybe a few 3rd party plugins or a parent theme. Having WordPress and these 3rd party elements in the repository make it unnecessarily big.

An alternative to storing the 3rd party code your project use into its repository is to use a dependency manager. Dependency managers are tools that let you store a list of these 3rd party libraries in your repository, and will take charge of downloading them and installing them in your project. Which is what Composer does. Let’s see how we can use it to keep our repository tidy.

Composer in a few lines

First step is obviously to install Composer on your machine. The tool’s getting started guide will guide you through these steps.

Composer works with a composer.json file that lists the dependencies of your project. The most common type of dependencies are 3rd party libraries. Composer will fetch them from its default package repository Packagist (or other repositories listed in the composer.json file). Additionaly, you can specify that your project needs specific PHP extensions to work properly or features from a minimum PHP version. This helps making sure all necessary things are present for your project to work properly.

The composer install command then installs all the listed dependencies, downloading them as needed.

Downloading WordPress with Composer

With composer ready, let’s set it up to download WordPress. Unfortunately, WordPress doesn’t have any package stored on Packagist. That doesn’t mean Composer can’t work with it, though. Composer can use other repositories and is perfectly capable to download a zip file from Github, for example. The corresponding composer.json is the following.

{
  "repositories":[{
    "type": "package",
    "package": {
      "name": "wordpress",
      "version": "4.7.2",
      "dist": {
        "type": "zip",
        "url": "https://github.com/WordPress/WordPress/archive/4.7.2.zip"
      }
    }
  }],
  "require": {
    "wordpress":"4.7.2"
  }
}

Run composer install and WordPress will be downloaded… in the vendor/wordpress folder. Not quite where you’d want it…

Downloading WordPress in a handier location

A more common folder structure would be having WordPress’ files at the root of the project. This structure makes it a hassle when it comes to manage files blacklisted in .gitignore (or .hgignore, or .<whatever>ignore). Instead, we’ll aim to have it in a wordpress folder.

For this we’ll need a tool called webroot-installer. It’ll let us pull wordpress out of the vendor folder and into a folder of our choice. Composer will take care of downloading it and its configuration will be stored in the composer.json file. This requires 3 changes to the file:

  1. Listing webroot-installer as a dependency of the project
  2. Marking WordPress package to be handled by webroot-installer
  3. Configuring webroot-installer to let it know where to put WordPress
{
  "repositories":[{
    "type": "package",
    "package": {
      "name": "wordpress",
      "version": "4.7.2",
      "type": "webroot",
      "dist": {
        "type": "zip",
        "url": "https://github.com/WordPress/WordPress/archive/4.7.2.zip"
      }
    }
  }],
  "require": {
    "wordpress":"4.7.2",
    "fancyguy/webroot-installer": "1.0.0"
  },
  "extra": {
    "webroot-dir": "wordpress",
    "webroot-package": "wordpress"
  }
}

Let’s not forget to setup the .gitignore file to blacklist wordpress and vendor (aim is to keep things tidy, after all). Here we go, we now have a repository for a WordPress project clean of WordPress code. Except given WordPress architecture, the code for plugins/themes resides in the wp-content folder… Which is into the wordpress folder… Which we just kicked out of our repository… Echec.

Running wordpress with an external wp-content

Fortunately, the wp-content folder used by WordPress doesn’t need to be inside the wordpress folder. With a couple of constants, we can configure WordPress to run with the following project structure. This will allow to keep wp-content in the repository, holding our code and exclude wordpress from the repository (as well as other 3rd party plugins/themes) via the .gitignore.

 - wordpress-project
 |-- wordpress
 |-- wp-content
 |-- composer.json
 |-- composer.lock
 |-- wp-config.php
 |-- index.php

The index.php file is a copy of WordPress’ one, with an update to the path for loading WordPress. It needs to account for the wordpress folder now.

require( dirname( __FILE__ ) . '/wordpress/wp-blog-header.php' );

The rest of the setup is setting up 3 constants so WordPress knows it’s in a separate folder, where the wp-content folder actually is and which URL it has when serving it.

define( 'WP_CONTENT_DIR', dirname(__FILE__) . '/wp-content');
define('WP_CONTENT_URL', 'http://PROJECT_URL/wp-content');
define('WP_SITEURL', 'http://PROJECT_URL/wordpress');

With these set, WordPress will run with its wp-content folder sitting next to it and the repository is now tidy. Now let’s see how we can use Composer to download WordPress plugins & themes. It’d be a shame to have to do that by hand while we have a dependency manager.

Installing WordPress plugins with Composer

Like WordPress, it’s plugins and themes usually aren’t available on Packagist. Adding a new repository for each like we did for WordPress isn’t very manageable either. Fortunately, there’s a separate Composer repository, called WPackagist which provides WordPress plugins & themes.

You’ll need to add the repository to the composer.json file, and then the WPackagist website will give you the JSON bit to insert in the require section for the plugin/theme you want to download. You can search the plugins directly there, or keep looking them up on the WordPress site and just go to WPackagist to find the JSON snippet.

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

That’s it. This set up lets you have a repository with only the files you write, leaving the downloading of external code to Composer. There’s still a bit of .gitignore management to do to exclude 3rd party themes & plugins, but that’s not too terrible. Path repositories might help get your code live outside wp-content entirely, which would allow an easy blacklisting of the entire wp-content folder. Maybe I’ll give it a go someday, for now this setup works well enough for my needs.