WordPress plugin dependencies with Composer and Grunt

Using Composer and Grunt to manage WordPress plugin dependencies.

The problem

I use Composer to manage any PHP library dependencies and writing mostly WordPress plugins and themes this means I often find myself conflicted in the following scenario:

I’m developing plugin B that depends on plugin A.

The first approach is to add a message, either in the readme file or as an admin notice, stating that plugin B will require plugin A to work; the user installing plugin B will then install plugin A manually and re-activate plugin B.
A second approach might be to use some plugin solution or a library solution to the problem.
A third approach, the one I use, is to use Composer to pull in the required WordPress plugin inside the current one, an embedding of sorts. This latter approach will require each dependency plugin to declare a composer.json file or have the dependent plugin (“B”) use the Composer package option to provide a virtual one.
The limits of the latter possibility are few but those dictated by common sense: embedding BuddyPress in a plugin might not be the best course of action.

Composer update

When I download a plugin I expect it to work out of the box.
This means that distributing a plugin that will require the user to run composer update from the root folder is not the best of approaches.
I will run composer update locally and make sure all of the plugins fundamental dependencies are there in their updated and “lighter” version.
The following composer.json file is taken from the Restricted Content framework plugin

{
  "name": "lucatume/tad-restricted-content",
  "type": "wordpress-plugin",
  "description": "A WordPress content restriction framework",
  "license": "GPL 2.0",
  "authors": [
    {
      "name": "Luca Tumedei",
      "email": "luca@theaveragedev.com"
    }
  ],
  "minimum-stability": "dev",
  "repositories": [
    {
      "type": "git",
      "url": "https://github.com/lucatume/wp-browser.git"
    },
    {
      "type": "git",
      "url": "https://github.com/lucatume/tad-reschedule.git"
    },
    {
      "type": "git",
      "url": "https://github.com/WebDevStudios/CMB2.git"
    }
  ],
  "require": {
    "composer/installers": "~1.0",
    "xrstf/composer-php52": "1.*",
    "lucatume/tad-reschedule": "~1.0",
    "webdevstudios/cmb2": "~2.1"
  },
  "extra": {
    "installer-paths": {
      "vendor/{$vendor}/{$name}": [
        "lucatume/tad-reschedule",
        "webdevstudios/cmb2"
      ]
    }
  },
  "require-dev": {
    "lucatume/wp-browser": "dev-master",
    "lucatume/function-mocker": "dev-master"
  },
  "autoload": {
    "psr-0": {
      "trc_": "src/"
    },
    "files": [
      "vendor/webdevstudios/cmb2/init.php",
      "vendor/lucatume/tad-reschedule/tad-reschedule.php"
    ]
  },
  "scripts": {
    "post-install-cmd": [
      "xrstf\\Composer52\\Generator::onPostInstallCmd",
      "grunt after-composer-update --verbose"
    ],
    "post-update-cmd": [
      "xrstf\\Composer52\\Generator::onPostInstallCmd",
      "grunt after-composer-update --verbose"
    ],
    "post-autoload-dump": [
      "xrstf\\Composer52\\Generator::onPostInstallCmd",
      "grunt after-composer-update --verbose"
    ]
  }
}

I’m taking advantage of any possibility Composer can offer and I can use in a PHP 5.2 project to autoload the embedded plugins bootstrap files, init.php from Custom Meta Boxes 2 and the tad-reschedule.php file from tad-reschedule utility plugin as well as the plugin classes themselves.
In the repositories section of the file I’m specifying I will pull in the latest version directly from GitHub for both and that means I will get a lot of folders and files I do not need, first among them the .git folder, when running compser update.

Grunt to the rescue

In the last lines I’m creating PHP 5.2 compatible autoload files and running a Grunt task after that.
The plugin npm dependencies are specified in the package.json file

{
  "name": "tad-restricted-content",
  "version": "0.0.10",
  "description": "A WordPress content restriction framework.",
  "main": "tad-restricted-content.php",
  "repository": {
    "type": "git",
    "url": "git+https://lucatume@github.com/lucatume/tad-content-restriction.git"
  },
  "keywords": [
    "wordpress",
    "plugin",
    "content",
    "restriction"
  ],
  "author": "Luca Tumedeie <luca@theAverageDev.com>",
  "license": "GPL-2.0",
  "bugs": {
    "url": "https://github.com/lucatume/tad-content-restriction/issues"
  },
  "homepage": "https://github.com/lucatume/tad-content-restriction#readme",
  "devDependencies": {
    "grunt": "^0.4.5",
    "grunt-contrib-clean": "^0.6.0",
    "grunt-git": "^0.3.5"
  }
}

In the Gruntfile.js file I’m using the tasks to remove nested .git folders and other files and folders that will not be needed in a distributed version of the plugin like the tests folder, Codeception, PHPUnit and Travis configuration files.
After that the task will add the clean dependencies to the plugin repository.

module.exports = function ( grunt ) {

    var dependencies = ["vendor/lucatume/tad-reschedule/", "vendor/webdevstudios/cmb2/"],
        delete_patterns = [".git/**", "tests/**", ".gitignore", "/Gruntfile.js", "/example-functions.php", "composer.{json,lock}", "{.travis,.scrutinizer,codeception*,}.yml", "coverage.clover", "phpunit.xml.dist"],
        clean_dist_patterns = [],
        git_add_patterns = [];

    for ( i = 0; i < dependencies.length; i++ ) {
        git_add_patterns.push( dependencies[i] + '**' );
        for ( k = 0; k < delete_patterns.length; k++ ) {
            clean_dist_patterns.push( dependencies[i] + delete_patterns[k] );
        }
    }

    grunt.initConfig( {
        pkg: grunt.file.readJSON( 'package.json' ),
        clean: {
            dist: clean_dist_patterns
        },
        gitadd: {
            dist: {
                options: {
                    verbose: true,
                    force: true
                },
                files: {
                    src: git_add_patterns
                }
            }
        }
    } );

    grunt.loadNpmTasks( 'grunt-contrib-clean' );
    grunt.loadNpmTasks( 'grunt-git' );

    grunt.registerTask( 'after-composer-update', ['clean:dist', 'gitadd:dist'] );
};

The end result is that running composer update will:

  1. update the plugin dependencies, both libraries and other plugins
  2. clean a subset of those dependencies that are needed in the plugin distribution version removing files and folders that would not be used
  3. force add those dependencies to the plugin repository

It’s not covering everything I might ever need but it’s a starting point.