There are many ways to manage a WordPress application. Fortunately, it is possible to use Composer with WordPress.
In this article, we will show how to use Composer with WordPress, so that you can easily maintain it, manage it and deploy it in different server environments.
That means that you will be able to install WordPress core, themes, plugins etc. as well as update and delete them when needed via Composer.
To achieve this, we will use the following tools:
- Composer - for managing packages
- WPackagist - repository for WordPress plugins and themes
- Laravel Envoy - for writing easy deployment scripts
Using Composer with WordPress
Our first goal is to download the WordPress core, the plugins and themes as versioned Composer dependencies.
So in order to use Composer with WordPress, we will first install Composer and then create a composer.json
file in the root directory of our project:
{
"repositories":[
{
"type":"composer",
"url":"https://wpackagist.org"
}
]
}
Since Composer uses Packagist by default as a package repository, we will need to tell Composer that we will need WPackagist instead.
Now we would be able to install public WordPress plugins and themes as Composer dependencies, for example like this:
{
"require": {
"wpackagist-plugin/akismet":"^4.1",
"wpackagist-theme/twentytwenty":"*"
}
}
Next, let's install the WordPress core via Composer. We will be using John P Bloch's mirror of WordPress Core to achieve that:
{
"require": {
"johnpbloch/wordpress": ">=5.4"
},
"extra": {
"installer-paths": {
"wp-content/plugins/{$name}/": [
"type:wordpress-plugin"
],
"wp-content/themes/{$name}/": [
"type:wordpress-theme"
]
},
"wordpress-install-dir": "wordpress"
},
"repositories": [
{
"type": "composer",
"url": "https://wpackagist.org"
}
]
}
In the require
section, we added the dependency. Next, in the extra
section, we told Composer where to look for themes and plugins. Lastly, we defined the WordPress installation directory to be wordpress
.
Now we can run the following command:
composer install --prefer-dist
Composer will now install WordPress within the wordpress
directory in the root of our project.
To be able to fully manage WordPress with Composer, we need to use a different directory for wp-content
instead of the default one, wordpress/wp-content
.
Let's create a new directory in the project's root, called wp-content
.
Let's go ahead and create the standard wp-config.php
file and then add the following code:
$domain = 'mydomain.test';
define('WP_SITEURL', "{$domain}/wordpress");
define('WP_HOME',"http:{$domain}");
$httpHost = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $domain;
define( 'WP_CONTENT_DIR', dirname( __FILE__ ) . '/wp-content' );
define( 'WP_CONTENT_URL', 'http://' . $httpHost . '/wp-content' );
/** Absolute path to the WordPress directory. */
if ( !defined('ABSPATH') ) {
define('ABSPATH', dirname(__FILE__) . '/wordpress');
}
/** Sets up WordPress vars and included files. */
require_once(ABSPATH . 'wp-settings.php');
Next, let's create an index.php
file within our project's root directory:
<?php
define('WP_USE_THEMES', true);
require( dirname( __FILE__ ) . '/wordpress/wp-blog-header.php' );
Since the wp-config.php
file contains sensitive data, we will not commit it to our repository by creating a .gitignore
file:
/wp-config.php
/wordpress/
/wp-content/
/vendor/
The wordpress
, wp-content
and vendor
directories also need to be ignored, so we will add them to the .gitignore
file as well.
Now we end up with a very simple project structure:
- vendor/
- wordpress/
- wp-content/
- composer.json
- composer.lock
- wp-config.php
- .gitignore
Using composer to install WordPress plugins and themes from private repositories
You may want to install plugins or themes that are hosted in private repositories on Github, Bitbucket or somewhere else, but not on WPackagist.
That is possible, but there are two things you need to do:
1) provide your credentials in an auth.json
file
2) within the composer.json
, in the repositories
section, you need to tell Composer where to look for the repository:
{
"type": "vcs",
"url": "https://bitbucket.org/your-company/your-theme.git"
}
Using Envoy to deploy WordPress with Composer
Now that we have our WordPress project set up with Composer, let's see how to deploy it.
As we mentioned previously, we will be using Laravel Envoy to write a deployment script for WordPress.
The reason why we are using Envoy is because of simplicity. You can also decide to use a different tool like Deployer.
Let's go ahead and download Envoy.
With the deployment script, we can deploy our WordPress application to different servers: development, staging and production.
Because the project structure is so lightweight, now it will be easy to write the deployment steps.
Here is what our deployment script for WordPress will look do:
- create a new release with a timestamp in a
releases
directory on your server - clone the repository
- install all the dependencies
- copy
wp-config
,.htaccess
and other files specific to the server environment (e.g production) - create a symlink of the
uploads
directory to the new release - create a symlink of the new release to the domain's document root directory on the server
- clean up old releases from the server.
First, let's go to our server and set up a directory called current
as the domain document root directory on the server.
If you are not familiar that, you can follow a tutorial on how to do it.
You can create the following directory structure on your server: ~/sites/yoursite
Now, let's install Envoy with the following command:
composer global require laravel/envoy
Next, let's write the deployment script for WP. Let's create a new file in our project root directory called Envoy.blade.php
.
In Envoy, we will use the following directives: @servers
, @setup
, @task
and @story
. It's very straightforward, here is what they mean:
-
@servers
- this is where you define all your servers with their corresponding IPs. -
@setup
- section where you can define variables or configurations. -
@task
- a single action that should be executed on the specified server. -
@story
- a sequence of tasks that need to be executed on the specified server.
The way Envoy works is, it will ssh to the specified servers and execute the defined script, by following the directives.
Inside the directives you can use PHP to define which UNIX commands need to be executed on the servers.
The first step will be to define multiple server environments:
@servers(['local' => '127.0.0.1', 'staging' => 'w.x.y.z' 'production' => ['a.b.c.d']])
Next, let's define some variables for our setup steps. These are the things we can configure, depending on our needs.
In our case, we will assume we have a private repository on Bitbucket for the project.
The deployment script will clone the master branch and set up the releases directory structure, as discussed above.
@setup
// the repository to clone
$repo = 'git@bitbucket.org:your-company/your-wp-composer-project.git';
// the branch to clone
$branch = 'master';
// set up timezones
date_default_timezone_set('Europe/Berlin');
// we want the releases to be timestamps to ensure uniqueness
$date = date('YmdHis');
// the application directory on your server
$appDir = '~/sites/yoursite';
// this is where the releases will be stored
$buildsDir = $appDir . '/releases';
// this is where the deployment will be
$deploymentDir = $buildsDir . '/' . $date;
// and this is the document root directory
$serve = $appDir . '/current';
@endsetup
Next, let's create a task to actually create the directory for the new release:
@task('dir')
echo "Preparing new deployment directory..."
cd {{ $buildsDir }}
mkdir {{ $date }}
echo "Preparing new deployment directory complete."
@endtask
As you can see, it's using Blade syntax for the UNIX commands, with the variables we defined in @setup
.
The following task will clone the repository and the specified branch:
@task('git')
echo "Cloning repository..."
cd {{ $deploymentDir }}
git clone --depth 1 -b {{ $branch }} "{{ $repo }}" {{ $deploymentDir }}
echo "Cloning repository complete."
@endtask
Of course, you will need to make sure your server can access the git repository.
The next task, will install the dependencies and copy the wp-config.php
file:
@task('install')
echo "Installing dependencies...";
composer install --prefer-dist
cp ../../wp-config.php ./wp-config.php
echo "Installing dependencies complete."
@endtask
The next task will create the symlinks to the new release and to the uploads
directory:
@task('live')
echo "Creating symlinks for the live version..."
cd {{ $deploymentDir }}
ln -nfs {{ $deploymentDir }} {{ $serve }}
ln -nfs {{ $appDir }}/uploads {{ $serve }}/wp-content/
echo "Creating symlinks completed."
@endtask
The last task will perform a cleanup and delete old releases:
@task('deployment_cleanup')
echo "Cleaning up old deployments..."
cd {{ $buildsDir }}
ls -t | tail -n +4 | xargs rm -rf
echo "Cleaned up old deployments."
@endtask
We can configure the number of old releases we want to keep on the server. In our case it is 4.
Now let's write a @story
directive where we can group the tasks we just wrote:
@story('deploy-staging', ['on' => 'staging'])
dir
git
install
live
deployment_cleanup
@endstory
@story('deploy-production', ['on' => 'production'])
dir
git
install
live
deployment_cleanup
@endstory
As you can see, we used the names of the tasks inside the @story
to define the order of execution.
Finally we would be able to run the following commands to deploy our WordPress to the server:
envoy run deploy-staging
and
envoy run deploy-production
and so on, for each environment we want.
So now we can manage our WP installation completely via Composer.
Keeping WordPress in sync with Composer
As you already know, it is possible to update plugins via Wordpress Admin panel. So if the WordPress administrator updates a plugin manually, it will make the site out of sync with the Composer file, which will beat the purpose of using Composer and it might cause the website to stop functioning properly.
So, how to avoid this situation?
It will be best to tell administrators not to update plugins on their own, especially not in production.
Once you have that settled, you can write a simple task in Envoy to update a given plugin:
@task('update-plugin')
cd {{ $deploymentDir }}
wp plugin update {{ $plugin }} --version={{ $pluginVersion }}
@endtask
@story('update')
update-plugin
@endstory
Here we are using WP-CLI to perform the update ( but you can also do it via the admin panel if you prefer )
You can execute it by typing:
envoy run update --plugin=bbpress --version=2.6.4
Here we assume we want to update the plugin bbpress to the version 2.6.4
Once this is executed, you can also update your composer.json
:
composer require wpackagist-plugin/bbpress 2.6.4
And finally, let's deploy our change to the server:
envoy run deploy-production
Other ways to use Composer with WordPress
We have showed how to manage WordPress with Composer and how to deploy it with your own customizable deployment script.
There are, of course, other ways to manage WordPress sites with Composer. One of the popular ways to do so, is with Bedrock.
Bedrock is a WordPress boilerplate project, with pre-defined directory structure.
It looks like this:
├── composer.json
├── config
│ ├── application.php
│ └── environments
│ ├── development.php
│ ├── staging.php
│ └── production.php
├── vendor
└── web
├── app
│ ├── mu-plugins
│ ├── plugins
│ ├── themes
│ └── uploads
├── wp-config.php
├── index.php
└── wp
Bedrock will help you move WordPress to a directory called wp
and set up different environments: development, staging and production.
It also does a good job setting up config values in a .env file with the help of PHP Dotenv.
The wp-config.php
reads the .env file which is not located in the web
directory and that improves the security of the website.
wp-content
is renamed to app
Once you have it set up, you can add plugins as Composer dependencies, similar to what we showed earlier.
For deployment, you can write a deployment script with either Envoy, Deployer or some other tool.
Conclusion - managing and deploying WordPress Sites with Composer
In this article, we showed two ways (manually and with Bedrock) of managing WordPress with Composer and a way how to easily deploy WordPress to multiple environments when it is used with Composer.
First we defined a composer.json
file which enabled us to install the WordPress Core, as well as the themes and the plugins as dependencies.
This made the project very lightweight and easy to deploy with a script.
While WordPress is not initially thought to be used as a Composer dependency, it is a great advantage to use it in modern development workflows as it ensures fairly easy deployments with zero downtime and quick rollbacks.
If you have any experience using WordPress with Composer (and deploying it), we would like to hear about it in the comments.
Originally published at https://time2hack.com on April 27, 2020.
Top comments (3)
Nice to see some PHP content here on dev.to. Keep up the good work Vladimir!
Thank you, David! I am glad to be here!
Nice article!
However Wordmove + wp-cli seems to be easier to setup to handle this