Web Docroot in WordPress & Composer

Composer is a dependency manager for PHP that has been widely adopted by the PHP community at large, but is used less frequently with WordPress. WordPress is largely procedural in nature, and Composer has great tools for object oriented code, like autoloading. That's nice to have but Composer is primarily a dependency manager, so today I'm going to show you the basics needed to manage a WordPress site with Composer.

TL;DR: Check out the example repo.

When working with version control, I recommend keeping only custom code—not dependencies—in your repository. Ever had merge conflicts from dependencies? Or a pull request with over a hundred files due to plugin updates? Excluding dependencies—such as Composer's vendor directory—from your working repository will solve these problems.

Since Pantheon requires a fully built site—dependencies included—this will need to be a separate repository from your Pantheon (or production) repository. This is referred to as a two repository model.

Getting Started: Set up a Nested Docroot

The first trick is to set up a nested docroot. This is a subdirectory within your project folder where the site will live. Here I'll refer to it as the web directory because that is what’s used on Pantheon (see pantheon.yml for details). This makes installing items with Composer a lot easier.

We’ll start by making a fresh repository. I'll use GitHub but you can host the repository anywhere you like.

Setting Up composer.json

Next, create a composer.json file. You'll need to include some required information, such as name and description. You can also add optional information, such as author.

Now we need to define our dependencies. I'll use Pantheon's version of WordPress core. This is a great example because we can see how to pull code in from another GitHub repository, even if it doesn't have a composer.json of it's own.

To do this, we'll need to define the Pantheon WordPress repository in the repositories array in composer.json. This tells Composer to look outside of the officially published items in the packagist repository, allowing us to use dependencies hosted virtually anywhere.

Let's see what our composer.json looks like after this step, then we'll go line by line to see what's happening.

First, we are defining some details about the package, such as name and version. In this case, using WordPress 4.7.2. dist tells Composer where to find the distribution version. I've chosen to use the GitHub .zip archive for the 4.7.2 tag as a new tag is created for each WordPress core release. Next we define source. The source parameter tells Composer where to fall back to if a stable release (distribution) is not found. Here it is simply the Git URL.

Next is type, which is set to wordpress-core. This is important to make use of the WordPress core installer we are requiring in the next line. The johnpbloch/wordpress-core-installer project "is a custom Composer installer for WordPress core".

This is useful as we can define a custom installation directory for WordPress core, rather than the default vendor directory used by Composer. In the next section, we tell the WordPress core installer to install WordPress core in the web/wp directory.

Finally, we tell Composer that pantheon-systems/wordpress at version 4.7.2 is required for our project.

Now, if we run composer update, Composer will download WordPress core from the Pantheon repository and place it in the web/wp directory.

Keep Dependencies Out of The Repository

Remember when I said "When working with version control I recommend keeping only custom code, not dependencies, in your repository" earlier? Let's take care of that by ignoring the entire web directory by adding web/ to .gitignore.

Add .gitignore, composer.json and composer.lock to Git and commit them. At this point your repository should look like this and anyone else should be able to clone it and run composer install to download WordPress core. Tip: composer install can be run on a continuous integration server or your web server directly as well, just make sure you've run composer update locally first to generate a composer.lock file. To do this on Pantheon, check out the Terminus Composer Plugin.

You might wonder why WordPress is in a subdirectory and not in the web root. WordPress allows us to give WordPress core it's own directory, which we’ll take advantage of. In addition to making it easier to install WordPress core via Composer, we have the added benefit of keeping our web docroot clutter free.

Following method II outlined in the codex article above we need to create a web/index.php file. It should look like this:

Next, we'll need to relocate the wp-content directory outside of the WordPress core directory. This will allow us to install themes and plugins with Composer into web/wp-content. Go ahead and create the web/wp-content directory now.

We need to use WP_HOME, WP_SITEURL, WP_CONTENT_DIR and WP_CONTENT_URL to let WordPress know the new directory paths and URLs for core and wp-content. This can be done in wp-config.php like so:

Important: WordPress will look for wp-config.php in the core directory first—web/wp/wp-config.php in our case—then fall back to web/wp-config.php. Since we want the latter to be used, web/wp/wp-config.php must be removed after installing WordPress core with Composer. I've put the commands needed in bin/build.sh of the example repo.

Now we're ready to install plugins and themes with Composer. WPackagist is perfect for this. It is a Composer repository that mirrors WordPress.org, giving us access to all the plugins and themes from the official WordPress repository in Composer.

We need to tell Composer about this repository by defining it in composer.json, adding it to the repositories section. We'll also tell Composer to install WordPress plugins and themes in wp-content instead of the default vendor directory.

Then we simply add WordPress plugins and themes into the require section of our composer.json in the format of "wpackagist-theme/twentyseventeen": "^1.1".

After adding the twentyseventeen theme and the WP-CFM plugin, our updated composer.json looks like this:

Now that we can add WordPress plugins and themes from the repo, the last step is to add custom code. For this example I'll create a child theme for twentyseventeen in web/wp-content/themes/twentyseventeen-child.

The last thing we need to do is exclude items installed with Composer from our Git repository. Git allows us to use negations in .gitignore.

"An optional prefix "!" which negates the pattern; any matching file excluded by a previous pattern will become included again."

Taking advantage of this we can ignore everything and then specifically include our custom code. This ensures we don't accidentally commit anything we aren't working on directly. It looks like this:

Installing WordPress Dependencies with Composer

Hopefully you can see the power of installing WordPress dependencies for your project with Composer, and keeping your repository clean with only custom code. This has the added benefit of less merge conflicts, tidier pull requests, and more.

Check out the complete example repo here.

You may also like: 

Topics Development, WordPress