Using Composer for WordPress Package Management

Composer is a PHP based “dependency manager”.

A “dependency” is any code or library that your site needs to work properly.
For instance, in a WordPress project, the WordPress core itself is a dependency.
Your WordPress site is dependent upon WordPress core. Without WordPress core, the site won’t do much of anything.
WordPress Plugins and themes that you need for your WP project are also be dependencies.

There are clear advantages to adding Composer to our developer toolbox.
We can use Composer to “spin up” our project, to download WordPress core and any necessary plugins, even run custom install scripts.
We can avoid committing publicly accessible plugin and theme packages into our project’s version control, keeping our repositories lighter.
We can avoid using Git submodules or SVN externals to include WP core, plugins and themes in our projects.
We can manage both WordPress and non-WordPress PHP packages for our project with the same set of tools.

Obviously, you do need Composer itself installed before you begin. Composer can easily be downloaded and run in minutes, have a look at Composer’s install instructions. I prefer the “global install” method so that I can just run the composer command directly, instead of php composer.phar.

Basic usage of Composer is fairly simple and straightforward. At its simplest, you write a “manifest” in JSON format, called composer.json. This manifest is where you will detail the specific dependencies for your project, including the versions of the dependencies.

If you wanted to write a composer.json file that simply required WordPress core for your project, it could look as simple as this :

{
    "name": "richaber/install",
    "description": "Install WordPress",
    "type": "project",
        "repositories": [
            {
                "type": "package",
                "package": {
                    "name": "wordpress",
                    "version": "3.8.1",
                    "dist": {
                        "type": "zip",
                        "url": "https://github.com/WordPress/WordPress/archive/3.8.1.zip"
                    }
                }
            }
        ],
    "require": {
        "wordpress": "3.8.1"
    }
}

Save the above as a file called composer.json, and drop it into an empty web root for testing. In your command line, cd to that web root and run composer install.

You should see output similar to this :

Loading composer repositories with package information
Installing dependencies (including require-dev)
    - Installing wordpress (3.8.1)
    Downloading: 100%
Writing lock file
Generating autoload files

In the manifest, you defined a repository where the WordPress core package resides.
When you ran the install command, Composer reached out to the dist url, grabbed the zip archive, downloaded it to your directory, and unzipped it for you.

But where the heck is WordPress in this directory?
Well, by default, Composer installed it into a sub-directory called “vendor.”

└── vendor
    ├── composer
    └── wordpress   <-- HERE'S WORDPRESS CORE

Obviously, that isn’t entirely helpful for us. But fear not, we have solutions.

So we have 2 ways we can go from here…

Install WordPress core directly into the web root, so it looks like a “normal” out-of-the-box WP install, complete with loose files jangling all about the web root.
OR
Install WordPress into its own directory, a subdirectory of the web root, sometimes called version-control friendly or VIP structure.

Since I work with teams that use SVN or Git for version control, and since I have done some WPCOM VIP work, I prefer the version-control friendly or VIP structure. You do use version control, don’t you? Of course you do, so let’s go that route.

We want to install WordPress in its own directory, named “wp.”
We could call it something else, like “core” or “wordpress,” but we’re going with “wp,” to mimic VIP style.
We could do a whole bunch of work ourselves to pull this off, everybody loves re-inventing the wheel.
Or we could lean on the existing work of others in the community.
The beauty of the open source WordPress and Composer ecosystems is, other people have probably run into the same issues we have, and someone has likely engineered a well-tested solution with advice and contributions from the community.

John P. Bloch is a well-known and respected developer at 10up, and he has been actively contributing tools for WordPress back to the community. John has a Composer package for installing WordPress into a specific directory which we can employ here, johnpbloch/wordpress-core-installer. Even better yet, John’s johnpbloch/wordpress Composer package is WordPress core with Composer support added. This can simplify our composer.json even further, by removing the block that defines the WordPress zip archive package, and as an added bonus, the johnpbloch/wordpress package includes the wordpress-core-installer package as a dependency itself.

Here is our simplified composer.json manifest, using John’s WP package, note that we have defined the wordpress-install-dir in the “extra” section:

{
    "name": "richaber/install",
    "description": "Install WordPress",
    "type": "project",
    "require": {
        "johnpbloch/wordpress": "3.8.1"
    },
    "extra": {
        "wordpress-install-dir": {
            "johnpbloch/wordpress": "wp"
        }
    }
}

Now we save that composer.json and run composer install…

$ composer install
Loading composer repositories with package information
Installing dependencies (including require-dev)
    - Installing johnpbloch/wordpress-core-installer (0.2.0)
    Downloading: 100%
    - Installing johnpbloch/wordpress (3.8.1)
    Downloading: 100%
Writing lock file
Generating autoload files

Now let’s have a look at our installation again…

├── vendor
│   ├── composer
│   └── johnpbloch
└── wp              <-- HERE'S WORDPRESS CORE

Alright, now we’re getting closer to seeing some magic.
Let’s also install a plugin and a theme!

By default, Composer searches the main Packagist repository, which has all kinds of useful packages, but not necessarily the WordPress.org plugins and themes. Fortunately for us, there is a package repo that is specifically geared towards WordPress plugins and themes, WordPress Packagist from Outlandish LLP. Putting wpackagist to work for us is as simple as adding it to our list of repositories for Composer to search, requiring the necessary plugins and/or themes, and then defining the plugin and/or theme install paths.

WordPress Packagist uses plugin and theme slugs that match those at WordPress.org. So if a plugin is found at the WordPress.org page https://wordpress.org/plugins/wordpress-importer/ then the plugin’s slug is wordpress-importer, and the wpackagist package would be wpackagist-plugin/wordpress-importer. If a theme is found at https://wordpress.org/themes/twentyfourteen then it’s slug is twentyfourteen, and it’s wpackagist package would be wpackagist-theme/twentyfourteen.

WordPress Packagist uses composer/installers to target the custom install paths for themes and plugins. We will explicitly add composer/installers as a requirement to our manifest.

We will install our plugins and themes into a sub-directory of web root, which we will call “wp-content.” We could have called it “content” or “not-core-junk” or whatever, but I chose “wp-content” so we can mimic VIP style. Note that we do not install anything in the wp/wp-content directory, we don’t want to pollute that dir in this version control friendly structure.

Let’s start with 1 plugin and 1 theme. We add wpackagist to our list of repositories. We add composer/installers, wpackagist-plugin/wordpress-importer, and wpackagist-theme/twentyfourteen to our requirements. And we define our custom installer-paths for plugins and themes in the extra section.

{
    "name": "richaber/install",
    "description": "Install WordPress",
    "type": "project",
    "repositories": [
        {
            "type": "composer",
            "url": "http://wpackagist.org"
        }
    ],
    "require": {
        "johnpbloch/wordpress": "3.8.1",
        "composer/installers": ">=1.0.12",
        "wpackagist-plugin/wordpress-importer": "*",
        "wpackagist-theme/twentyfourteen": "*"
    },
    "extra": {
        "wordpress-install-dir": {
            "johnpbloch/wordpress": "wp"
        },
        "installer-paths": {
            "wp-content/plugins/{$name}/": ["type:wordpress-plugin"],
            "wp-content/themes/{$name}/": ["type:wordpress-theme"]
        }
    }
}

Save it, and run it.

$ composer install
Loading composer repositories with package information
Installing dependencies (including require-dev)
    - Installing johnpbloch/wordpress-core-installer (0.2.0)
    Loading from cache
    - Installing composer/installers (v1.0.19)
    Downloading: 100%
    - Installing johnpbloch/wordpress (3.8.1)
    Loading from cache
    - Installing wpackagist-plugin/wordpress-importer (0.6.1)
    Downloading: 100%
    - Installing wpackagist-theme/twentyfourteen (1.3)
    Downloading: 100%
Writing lock file
Generating autoload files

Now let’s have a look at our installation again…

├── vendor
├── wp
└── wp-content
    ├── plugins
    │   └── wordpress-importer 
    └── themes
        └── twentyfourteen 

Well, this is starting to look a little more useful now, isn’t it?
We have WordPress core in a wp sub-directory of web root.
And we have plugins and themes in a wp-content sub-directory of web root.

Now, if you’ve never used the version control friendly structure before, there’s plenty of existing documentation about working with this structure. Essentially you just need to do a little work defining some constants in wp-config.php, and add an index file in the web root that kicks off WP.

We can create a standard wp-config.php file in our web root, and add a couple of constants into it that tell WordPress where to find content (wp-content) and core (wp) since they are not in the “famous 5 minute install” locations. In particular the values for WP_SITEURL, WP_CONTENT_DIR, WP_CONTENT_URL, and ABSPATH will not be “standard” and should be explicitly defined. Assuming that you’ve set up your basic wp-config.php in web root already, you can see an example of the constants to define below.

<?php
define('DB_NAME', 'database_name_here');
define('DB_USER', 'username_here');
define('DB_PASSWORD', 'password_here');
define('DB_HOST', 'localhost');
define('DB_CHARSET', 'utf8');
define('DB_COLLATE', '');
define('AUTH_KEY',         'put your unique phrase here');
define('SECURE_AUTH_KEY',  'put your unique phrase here');
define('LOGGED_IN_KEY',    'put your unique phrase here');
define('NONCE_KEY',        'put your unique phrase here');
define('AUTH_SALT',        'put your unique phrase here');
define('SECURE_AUTH_SALT', 'put your unique phrase here');
define('LOGGED_IN_SALT',   'put your unique phrase here');
define('NONCE_SALT',       'put your unique phrase here');
$table_prefix  = 'wp_';
define('WPLANG', '');
define('WP_DEBUG', true);

/** ↑ Typical wp-config stuff (comments removed for brevity) ↑ **/

/** OUR CUSTOM CONSTANTS **/
define('CORE_STRING', 'wp');
define('CONTENT_STRING', 'wp-content');

/** @var string WP_HOME The site's URL, no trailing slash @link http://goo.gl/p0sP0U */
define('WP_HOME', 'http://www.example.com');

/** @var string WP_SITEURL WordPress core URL, no trailing slash @link http://goo.gl/rYTRuQ */
define('WP_SITEURL', WP_HOME . '/' . CORE_STRING);

/** @var string CONTENT_DIR WordPress content directory, no trailing slash @link http://goo.gl/E2YVfB */
define('WP_CONTENT_DIR', dirname(__FILE__) . '/' . CONTENT_STRING);

/** @var string CONTENT_DIR WordPress content URL, no trailing slash @link http://goo.gl/E2YVfB */
define('WP_CONTENT_URL', WP_HOME . '/' . CONTENT_STRING);

/** @var string ABSPATH Absolute path to the WordPress core directory, with trailing slash */
if (!defined('ABSPATH')) {
    define('ABSPATH', dirname(__FILE__) . '/' . CORE_STRING . '/');
}

/** ↓ Last of the standard wp-config stuff ↓ **/

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

Now how about that index.php file that we need in our web root…

<?php
define( 'WP_USE_THEMES', true );
require( dirname( __FILE__ ) . '/wp/wp-blog-header.php' );

Now we have a basic WP installation ready. Add some DB magic and maybe an htaccess and you could start working with this right away.

Obviously, this can get more complex if we start running post-install-cmd and post-update-cmd scripts. We could load require-dev plugins that are specifically for development and not production, etc. But hopefully the baby-step-by-baby-step above gives you a better idea of how to begin using Composer for WordPress Package Management, without overwhelming you.