It took me a while before i finally found the right combination, but i now have a full working setup where is manage my WordPress dependencies using Composer. Before this i used to have a local copy of my WordPress site where i run all updates, and then committed all code that was changed so i could get deployed. I knew this was dirty, but i’ve tried on multiple occasions to find a better solution but my Google fu failed me.

After asking around i’ve got a few tips from Mark and this improved my GIT repository and deployments a lot. In this blogpost i write down how i have everything configured so i can manager WordPress using Composer.

Custom theme

First up: The custom theme. I’ve placed the contents in themes/<YOUR-THEME-NAME>. I also added a composer.json in this folder with this contents:

{
  "name": "michiel-gerritsen/<YOUR-THEME-NAME>",
  "type": "wordpress-theme"
}

The contents of this file will be symlinked to the WordPress wp-content/themes folder.

Custom plugins

Basically the same as the theme. The contents of the theme are placed in plugins/<YOUR-PLUGIN-NAME>. Here is also a composer.json added with this contents (slightly different from the theme variant):

{
    "name": "michiel-gerritsen/<YOUR-PLUGIN-NAME>",
    "type": "wordpress-plugin"
}

The contents of this plugin will be symlink in the WordPress wp-content/plugins folder.

Translations

These are pretty easy as this are only Composer packages. First you need to add the wp-languages repository:

composer config repositories.wp-languages composer https://wp-languages.github.io

Then you can add the required packages:

composer require koodimonni-language/core-nl_nl:* koodimonni-language/nl_nl:*

And last but not least: Let Composer now where to install the packages:

"extra": {
    "dropin-paths": {
            "wordpress/wp-content/languages/": ["vendor:koodimonni-language"],
            "wordpress/wp-content/languages/plugins/": ["vendor:koodimonni-plugin-language"],
            "wordpress/wp-content/languages/themes/": ["vendor:koodimonni-theme-language"]
        }
}

See this url for alle available languages.

The WordPress core

Last but not least: The WordPress core. First, add the repository:

composer config repositories.wp-core composer https://wpackagist.org

Next, require the package:

composer require johnpbloch/wordpress:5.*

This will install WordPress in the wordpress folder. If you want to change this folder, add this to you composer.json:

"extras": {
  "wordpress-install-dir": "my-custom-folder"
}

Putting it all together

If you put it all together you will end up with something like this. You can then easily install your WordPress installation using Composer:

{
    "name": "controlaltdelete/my-fancy-blog",
    "require": {
        "wpackagist-plugin/wordpress-seo": "^11.0",
        "wpackagist-plugin/akismet": "^4.1",
        "wpackagist-plugin/jetpack": "^7.0",
        "johnpbloch/wordpress": "^5.2",
        "controlaltdelete/custom-theme": "*",
        "controlaltdelete/custom-plugin": "*",
        "koodimonni-language/nl_nl": "*",
        "koodimonni-language/core-nl_nl": "*"
    },
    "repositories":[
        {
            "type":"composer",
            "url":"https://wpackagist.org"
        },
        {
            "type": "path",
            "url": "./themes/custom-theme"
        },
        {
            "type": "path",
            "url": "./plugins/custom-plugin"
        }
    ],
    "extra": {
        "installer-paths": {
            "wordpress/wp-content/plugins/{$name}/": ["type:wordpress-plugin"],
            "wordpress/wp-content/themes/{$name}/": ["type:wordpress-theme"]
        },
        "dropin-paths": {
            "wordpress/wp-content/languages/": ["vendor:koodimonni-language"],
            "wordpress/wp-content/languages/plugins/": ["vendor:koodimonni-plugin-language"],
            "wordpress/wp-content/languages/themes/": ["vendor:koodimonni-theme-language"]
        }
    }
}

How does this work?

There are a few powers a play here which may not be obvious at first. Let’s go through them:

WordPress core

This is handled by the johnpbloch/wordpress package. This has a requirement on johnpbloch/wordpress-core, which holds a copy of WordPress, and johnpbloch/wordpress-core-installer, which makes sure the WordPress core files are installed outside the vendor folder.

WordPress Packagist

WordPress Packagist is a custom packagist installation which takes all plugins from the WordPress plugin & themes directory and makes them installable by Composer.

You can install any plugin or theme from using Composer by using the slug from the WordPress website:

  • https://wordpress.org/plugins/<PLUGIN-SLUG>/
  • https://nl.wordpress.org/themes/<THEME-SLUG>/

You can then require it using Composer:

  • Plugin: composer require wpackagist-plugin/<PLUGIN-SLUG>
  • Theme: composer require wpackagist-theme/<THEME-SLUG>

Custom plugins and themes

These work because of 2 things: The custom local repository, and because of the custom "type": "wordpress-plugin" in the composer.json.

Local repository
When you require your package, Composer tries to find it on Packagist first, then on wpackagist as we have defined that, and on our local path. As in most cases the package won’t be available on the first 2, it is going to get symlinked from the local variant.

Composer type
The other part is the Composer type in the composer.json of the custom plugin. Thanks to this in the main composer.json Composer knows where to install these:

"extra": {
    "installer-paths": {
        "wordpress/wp-content/plugins/{$name}/": ["type:wordpress-plugin"],
        "wordpress/wp-content/themes/{$name}/": ["type:wordpress-theme"]
    }
}

How to update anything?

Normally you would login the WordPress backend and click the update button a few times, but with this method it works a little bit different. The key to all updates is composer update. This checks your composer.json which versions you want to use and looks in composer.lock to see which versions you currently are using. Then it calculates which packages needs to be updated.

There are a few ways to handle your dependencies:

  • Fixed: You can set a specific version in your composer.json, so that you will always get the right version.
  • Wildcard: Use a wildcard (*) so that you will always get the latest version of a package. Note: This can be dangerous. This is fine for the plugins and themes in the same repository as you control them, but external packages may get updated unexpected and break your site.
  • Composer: You can now use the power of Composer. See the documentation for all options.

Bonus: Deploying this setup

Well that is easy: i don’t have anything special setup for this. I use the default from Deployer which is fine for this. The only change i had to do from my original setup was to change the base path. In a normal installation WordPress runs from the root of my project, where in this case all the code is moved to ./wordpress. So change the docroot of your nginx/apache/etc setup to current/wordpress/ and your good to go.

Bonus 2: How to work with Laravel Valet(+)?

The default Laravel Valet driver for WordPress assumes that all your files are directly in the root of your site. This installs all your files in the ./wordpress folder, so your driver isn’t working. For this case i created this driver. Save it at ~/.valet/Drivers/ComposerWordPressValetDriver.php and you should be good to go.