November 9, 2017
by Matt Raines

Dependency management for WordPress

Prater Raines launched our 19th WordPress website recently, a petition campaigning for justice for the marginalised children of Vietnamese mothers and South Korean fathers born during the Vietnam War.

Our support contract with clients always includes backing up their data and keeping their website secure and easy to modify in future by making sure it’s running the latest version of WordPress and any contributed plugins. This has been a fairly manual process for a while and it felt like a good time to streamline things a little.

Gary and I attended a great talk at the PHP UK Conference earlier this year where Chris Sherry described a modern approach to WordPress installation and management and we have often talked since about implementing this.

We started by building a composer.json which includes John P Bloch’s WordPress core repackaged for git and plugins and themes from the WordPress Packagist project. Installing core in a subdirectory (we chose “wordpress”) and keeping wp-content in the root allows us to update all the plugins, themes, and core with a simple call to composer update. No more FTP access (ugh!) or remembering to download all the files every time we’re notified of an update. And no more keeping complete copies of WordPress in our own version control software!

Our composer.json looks a bit like this. There are some other plugins and themes in the live version, including some of our own bespoke code, which I’ll be talking about in a future update. Strictly speaking you don’t have to require PHP version 7, but at Prater Raines we like to keep things sleek and modern. Anyway, you get the picture.

{
  "name": "praterraines/wordpress",
  "description": "Prater Raines multisite WordPress installation",
  "type": "project",
  "authors": [
    {"name": "Matt Raines", "email": "matt@praterraines.co.uk", "role": "Developer"},
    {"name": "Gary Fuller", "email": "gary@praterraines.co.uk", "role": "Developer"}
  ],
  "repositories": [
    {"type": "composer", "url": "https://wpackagist.org"}
  ],
  "require": {
    "php": ">=7.0",
    "johnpbloch/wordpress": "~4.8",
    "wpackagist-plugin/akismet": "^3.3.2",
    "wpackagist-plugin/wordpress-importer": "^0.6.3",
    "wpackagist-plugin/wp-mail-smtp": "^0.10.1",
    "wpackagist-plugin/wp-updates-notifier": "^1.4.4",
    "wpackagist-theme/twentyseventeen": "*"
  },
  "require-dev": {
    "wpackagist-plugin/debug-bar": "*"
  },
  "extra": {
    "wordpress-install-dir": "wordpress",
    "installer-paths": {
      "wp-content/plugins/{$name}": ["type:wordpress-plugin"],
      "wp-content/themes/{$name}": ["type:wordpress-theme"],
      "wp-content/mu-plugins/{$name}": ["type:wordpress-muplugin"]
    }
  }
}

Line 10 tells Composer where to find WordPress plugin and theme packages. Lines 27-29 configure where to install these specific kinds of package, because by default they would end up in the vendor folder.

Line 14 tells Composer which version of WordPress to install and line 25 (which isn’t strictly necessary — wordpress is the default option) where to install it.

Lines 15-19 detail the plugins and themes we need. Some of these are included in WordPress core but we’re not using the wordpress/wp-content folder so we need to specifically install them again. WordPress themes don’t have version numbers so we’ll just use * for the version in line 19.

There’s not much more to it. You need a wp-config.php and an index.php in the root of the project. The latter you can copy out of the wordpress folder and just change the final line to include the wordpress folder name.

/** Loads the WordPress Environment and Template */
require( dirname( __FILE__ ) . '/wordpress/wp-blog-header.php' );

The wp-config.php needs your database details, and some changes and convenience settings near the bottom.

// Disable editing of files and manual and automatic updates (done using composer).
define('DISALLOW_FILE_EDIT', true);
define('DISALLOW_FILE_MODS', true);
define('AUTOMATIC_UPDATER_DISABLED', true);
define('WP_AUTO_UPDATE_CORE', false);

// Move the content folder outside the WordPress install.
define('WP_CONTENT_DIR', "$_SERVER[DOCUMENT_ROOT]/wp-content");

/** Absolute path to the WordPress directory, inside a subfolder so we can update it with composer. */
if ( !defined('ABSPATH') )
    define('ABSPATH', __DIR__ . '/wordpress/');

/* That's all, stop editing! Happy blogging. */

/** Sets up WordPress vars and included files. */
require_once(ABSPATH . 'wp-settings.php');

Lines 88-89 and 91-93 reflect the unusual folder structure. Lines 82-86 prevent WordPress from notifying us about or trying to automatically install updates and explicitly prevent administrators from editing files in core — their changes would be overwritten when we update using Composer anyway.

And that’s it. We installed and set up the site, and now downloading and deploying plugin and core updates is a two command process.

More on how our own bespoke code fits into this approach and how we are further simplifying using WordPress multisite in a future update.