In part 1 of this series we looked at how to store and manage your WordPress site in Git. In parts 2 and 3 we looked at using Composer and Git Submodules to manage the themes and plugins in your WordPress site. In this final part of the series we’re going to look at how we can improve what we did in part 1 and store WordPress itself in a subdirectory using Composer or a Git Submodule.
WordPress as a Dependency
What are the advantages of storing WordPress in a subdirectory? Well, by treating WordPress itself as a dependency you can make the structure of your Git repo more modular and clean. By keeping WordPress out of your Git repo, you don’t have to replicate the code and updates become much easier as you don’t have to commit updates to WordPress as part of your workflow.
Iain covered some of the benefits in his post when he detailed how to install WordPress core in a subdirectory using WP-CLI or Composer. In this post we’re going to build on his Composer example and also have a look at how to do it using Git Submodules.
Restructuring WordPress
No matter which direction you choose when installing WordPress in a subdirectory there is a certain amount of restructuring that needs to be done to make it work. Mainly three things need changed:
- WordPress needs to be bootstrapped from the new location using an
index.php
file - The
wp-content
folder needs to be moved outside of the WordPress install subdirectory - Any environment/config files (e.g.
.htaccess
/wp-config.php
) also need to be moved to the new location
A lot of modern web applications also consider it best practice to move any sensitive config information above the “public” directory so that it can’t be directly accessed via a URL. We’re going to do this here too. So our directory structure should now look something like this:
/
|
|-- public/
| |-- wp-content/
| |-- wp/ <-- Our subdirectory install location for WordPress (not stored in Git repo)
| |-- .htaccess
| |-- index.php
| `-- wp-config.php
|
|-- .gitignore
|-- composer.json <-- If using Composer
`-- local-config.sample.php
1. Bootstrap index.php
This part is relatively simple. We’re going to install WordPress into the public/wp
subdirectory, so we need to tell our webserver where to find WordPress. As everything is routed through the main index.php
file (see .htaccess
) we can simply create a bootstrap index.php
file with the following contents:
<?php
// WordPress bootstrap
define( 'WP_USE_THEMES', true );
require( './wp/wp-blog-header.php' );
2. Move wp-content
We achieve this by moving the wp-content directory to public/wp-content
. WordPress won’t know that we want it to use this new wp-content location so we need to tell it to by setting the WP_CONTENT_DIR
and WP_CONTENT_URL
in our new public/wp-config.php
file (see below).
3. Environment config
You’ll notice the local-config.sample.php
file in the root directory above. While this is not technically necessary it’s a good idea to split up environment-specific config information (such as database credentials and secret keys) from the “global” config information stored in public/wp-config.php
(such as the table prefix and the wp-content location).
The idea here is that the public/wp-config.php
file will hold any “global” config information and then load the correct “environment” config file as required. So for your production site you can deploy a production-config.php
file which can be loaded by public/wp-config.php
if it exists (we don’t store it in the Git repo though for security purposes) and if production-config.php
doesn’t exist fall back to local-config.php
which assumes you are working on a local development environment.
The content of our new public/wp-config.php
file should look like:
<?php
ini_set( 'display_errors', 0 );
// ===================================================
// Load database info and local development parameters
// ===================================================
if ( file_exists( dirname( __FILE__ ) . '/../production-config.php' ) ) {
define( 'WP_LOCAL_DEV', false );
include( dirname( __FILE__ ) . '/../production-config.php' );
} else {
define( 'WP_LOCAL_DEV', true );
include( dirname( __FILE__ ) . '/../local-config.php' );
}
// ========================
// Custom Content Directory
// ========================
define( 'WP_CONTENT_DIR', dirname( __FILE__ ) . '/wp-content' );
define( 'WP_CONTENT_URL', 'http://' . $_SERVER['HTTP_HOST'] . '/wp-content' );
// ================================================
// You almost certainly do not want to change these
// ================================================
define( 'DB_CHARSET', 'utf8' );
define( 'DB_COLLATE', '' );
// ================================
// Language
// Leave blank for American English
// ================================
define( 'WPLANG', '' );
// ======================
// Hide errors by default
// ======================
define( 'WP_DEBUG_DISPLAY', false );
define( 'WP_DEBUG', false );
// =========================
// Disable automatic updates
// =========================
define( 'AUTOMATIC_UPDATER_DISABLED', false );
// =======================
// Load WordPress Settings
// =======================
$table_prefix = 'wp_';
if ( ! defined( 'ABSPATH' ) ) {
define( 'ABSPATH', dirname( __FILE__ ) . '/wp/' );
}
require_once( ABSPATH . 'wp-settings.php' );
We need to rename our local-config.sample.php
to local-config.php
to use it. It should look something like this:
<?php
define( 'DB_NAME', 'wordpress' );
define( 'DB_USER', 'exampleuser' );
define( 'DB_PASSWORD', 'examplepassword' );
define( 'DB_HOST', 'localhost' );
ini_set( 'display_errors', E_ALL );
define( 'WP_DEBUG_DISPLAY', true );
define( 'WP_DEBUG', true );
define('AUTH_KEY', ',7jxG`)ZYM/m1OB6G/&z)gL=oW={,B1-&xcGuySKE.vQh_-fI)j$y^222Hs8cR&W');
define('SECURE_AUTH_KEY', '=3/+qt|eD2>(|nx@-p|vp&8T*n6;ZKZ1[91m`a^-PbV+wzbiK ,gyNe&iTpHI(+1');
define('LOGGED_IN_KEY', 'Wu5@R=lv&j!.~IMD_D;%gzG>NSYfNG-K B@6wd]cp2|jYCgHV;>dSe[. u|f@2V]');
define('NONCE_KEY', '=d#!`FCws;6W-z%j%:Jh8@-~U|k[PoA8Lb+.r4yf_fi*1bJXMQm2bu{j@ObTNlSe');
define('AUTH_SALT', '|~mjb|}FFR~b=jcF--;6.`KEO|wP>f&|+2s-#4]6QzY7o4#^y2&9mabHT..DR<O-');
define('SECURE_AUTH_SALT', '+YmVlzrBIw!kMkq(j3p&5+IU17>+ea[E9ZNdH-*k)(cCc74N^7CVd|ol(*^i]do!');
define('LOGGED_IN_SALT', 'rG9_QAj+~/q2UA*5Fk(Q](/NY&IG}[Z8&uf+Q{;YB/uA0Q.iFU:UW *OCN;|FUi1');
define('NONCE_SALT', 'uBW!%ut#F]]5Etl3MwAi|;9 82#qY9(x:])4BU*y{4BrSHk^hT&E6>m<`)zwsaIs');
One last thing we want to do is make sure we’re ignoring the correct files and folders in our .gitignore
file so that only the required files and folders are stored in our Git repo. The /vendor
folder is where composer will store its dependencies so we want to ignore it too.
/public/wp
/vendor
production-config.php
local-config.php
Now that we have our structure in place we can go ahead and install WordPress.
Install WordPress using Composer
As we looked at in part 2 Composer is a great way to manage packages in your PHP application, and the same applies if you want to use WordPress as a dependency. As in part 2 we’re going to use WP Packagist for our themes and plugins (although I’ve excluded them for this example) but this time we’re also going to add the johnpbloch/wordpress package which is a Composer compatible duplicate of the WordPress repo. Our composer.json
file should look like:
{
"repositories": [
{
"type": "composer",
"url": "http://wpackagist.org"
}
],
"require": {
"php": ">=5.4",
"composer/installers": "1.*",
"johnpbloch/wordpress": "4.3.*"
},
"extra": {
"wordpress-install-dir": "public/wp",
"installer-paths": {
"public/wp-content/mu-plugins/{$name}/": ["type:wordpress-muplugin"],
"public/wp-content/plugins/{$name}/": ["type:wordpress-plugin"],
"public/wp-content/themes/{$name}/": ["type:wordpress-theme"]
}
}
}
There are three things to note here:
- We are installing version
4.3.*
of WordPress. This means Composer will install the latest version of WordPress 4.3.x but not version 4.4 and above (once it’s released). This is to prevent Composer installing breaking changes by mistake. If you want WordPress to always update to the latest version when usingcomposer update
change this to*
. - Inside the “extra” settings we’re telling Composer to install WordPress in our new subdirectory location at
public/wp
via “wordpress-install-dir”. - We’re also using the “installer-paths” directive to tell Composer that the location of our
wp-content
directory has changed topublic/wp-content
and this is where it should install our themes and plugins.
It might seem like we’ve done a lot of work but this is where we begin to see the advantage of a setup like this. Now we just need to run composer install
to install WordPress or composer update
to update WordPress. Deployment strategies are outside the scope of this article but hopefully you can see how powerful and easy this kind setup makes deployments.
Install WordPress using Git Submodules
If Composer isn’t your thing we can achieve the same outcome using Git Submodules. Instead of using a composer.json
file we can add WordPress as a Git Submodule (just like we did for themes and plugins in part 3) to install WordPress in a subdirectory.
To add WordPress as a submodule run the following command from the root of the project:
git submodule add https://github.com/WordPress/WordPress public/wp
For stability reasons it is recommended that we install a stable version of WordPress. We can do this by checking out a specific tag:
cd public/wp
git checkout tags/4.3.1
cd ../..
Remember we need to commit any changes we make to submodules:
git commit -am "Install WordPress 4.3.1 as a submodule"
Updating WordPress is a similar workflow:
cd public/wp
git fetch -a
git checkout tags/4.3.2
cd ../..
git commit -am "Update WordPress to 4.3.2"
And that’s how we are able to install and manage WordPress as a Git Submodule.
It’s worth mentioning at this point that some of the concepts that I’ve used in both of the above methods are based on Mark Jaquith’s Skeleton repo which is definitely worth checking out if you want to look into these kind of setups a bit further.
I hope you’ve enjoyed this series on managing your WordPress site with Git and Composer and are able to take some, or all, of this workflow and implement it in your own WordPress sites.
Remember that what we have covered in this series is not necessarily the “right” way to manage WordPress in Git, but just one way you can use Git and WordPress together. Take the bits that work for you and create your own workflow and ignore the rest, or improve this workflow. It’s up to you.
Got any tips or suggestions I haven’t covered in this series? Let me know in the comments.