Safely using PHP dependencies in the WordPress ecosystem

Plugins and themes from the WordPress.org repository install as stand-alone packages; they need to contain all the code, files, and dependencies needed to function correctly. Because there is no centralized system with an overview of all the dependencies used in different WordPress plugins and themes, they all need to implement their own safety net.

Dependencies in WordPress

When building something, it is never efficient to reinvent the wheel. In programming, people embraced this concept and built a library of tools in many languages. We refer to these tools as “dependencies”. The tool that provides a library of dependencies relevant in this context is Composer, the dependency manager for PHP.

As mentioned before, WordPress does not have a centralized way of managing these dependencies, which can lead to problems. To manage dependencies effectively and allow programmers to have control over what functionality they are using, dependencies have a version number. Composer allows you to configure which version number, or range of version numbers, your code is compatible with. It also makes sure that it installs and uses a version of that dependency which matches this version constraint.

The problem

Imagine a WordPress site with two plugins; “Plugin A” and “Plugin B”. They both use a dependency “great/dependency”. “Plugin A” requires version v1.0, “Plugin B” is much newer and requires version v3.0. This will lead to problems when functions, classes or constants have been defined in both versions but have different functionality. In PHP, you can only define a class and thus load only one version. Your code expects the class from version v1.0, but because of the loading order, v3.0 could be the first active class.

The solution

There are two ways of solving this problem:

  1. Use Composer as a centralized dependency management tool to manage all dependencies in the entire WordPress installation.
  2. Make sure each plugin has a variation of the dependencies with unique names for all the classes, functions or constants in use (i.e. Yoast_Data_Search instead of Data_Search).

The first solution requires some modifications in the WordPress core code and thus is not realistic for an extensive group of current users. Some tools help you set up and configure your WordPress installation using Composer to manage all the dependencies (plugins, themes, tools you want to use for your custom code). We highly recommend looking into the currently available options and use one central place to manage dependencies. This will give you a lot of control and insight into the code you are using to run your website.

The second solution -- converting the dependencies that you use to a variation you use in your code -- is the part that I will elaborate on in this post.

The implementation in Yoast SEO is fully compatible with both solution paths and will give you the least overhead.

Why do we do this now?

At Yoast, we're shipping a small collection of own plugins as external dependencies within our plugin, for example, Translation motivation package. The number of plugins that use these packages is minimal, as these are not relevant for most plugins. Despite this, even these gave us some problems in the past because two (incompatible) versions of these packages were present in two different plugins active in the system.

WordPress increased the minimal PHP version from 5.2 to 5.6 in the WordPress 5.2 release. This opens up a world of libraries and tools that we can and want to use in our software. Tools and libraries that have been the standard solution for projects that were not limited by the PHP 5.2 restriction.

Being able to use tools written for PHP 5.6 allows us to implement functionality using standardized techniques like an ORM, database migrations, dependency injection and more. This will improve code quality, readability, and performance.

As more plugins and themes are (hopefully) will use more modern and standardized code to rely on in their code the chance of having incompatible versions of these dependencies will increase drastically.

How do we do this?

The implementation has three different environments it influences:

  1. Composer plugin release
  2. WordPress.org plugin release
  3. Development

Please note that you need PHP 5.6 for this functionality to work. As WordPress only recently increased the minimal version to PHP 5.6, it is highly recommended to wrap the code that uses these dependencies in a PHP version check.

Release as Composer package

We added the dependencies we need to our Composer configuration:

This allows Composer to install these and manage the versions that need to be installed. You don't need prefixing for this installation, because we manage the versions globally in the site.

Release on WordPress.org

To prepare the package that will be released on WordPress.org, we create an artifact of the plugin, which is deployed to the WordPress repository.

We use the terminology “artifact” to refer to the collection of files that you need to use the plugin. This collection differs from the collection of files that you need to develop the plugin.

For JavaScript, this means the minified versions of the files, for CSS this means the compiled versions instead of the source (Sass) files. For dependencies, this means the uniquely-named variation of all the classes, functions and constants within these dependencies.

Tools used:

Prefixing the code: PHP Scoper

PHP Scoper provides the ability to run code modifications. It is specifically built to “Prefix all PHP namespaces in a file/directory to isolate the code bundled in PHARs.”

PHARs are PHP executables, which also live in an ecosystem where there is no global dependency management system.

PHP Scoper helps you with the following tasks:

  • Prefix all existing namespaces with your own defined namespace.
  • Prefix all global-namespaced items with a namespace.
  • And prefix all constants.
  • Run code modifications (search/replace)
    • As some of the class names are used in strings or being built dynamically, it provides an option to add custom configuration to handle these cases. See our Idiorm configuration as an example.

Creating an artifact consists of the following steps:

Exception: PSR interfaces

We are going to prefix any classes, interfaces and constants the dependencies ship with except for PSR-interfaces. This will allow site, theme and plugin developers to easily integrate with their custom integrations based on the industry standards. We currently only use the PSR-3 Logger Interface, we might use other interfaces in the future.

Plugin development

During development, we want to be able to use the dependency as used in the WordPress.org installation as well as the Composer plugin installation.

To make it work in both situations (with and without Composer), we dynamically add PHP Aliases for classes and interfaces which start with the defined prefix. This allows us to always reference the prefixed version of the class in the code, even if the non-prefixed variation (via Composer) is loaded.

How to implement prefixing dependencies in your WordPress plugin

Requirements to prefix the PHP dependencies in your plugin or theme:

Requirements to prefix the PHP dependencies in your plugin or theme:

  1. Install Composer

    You need Composer, to manage the dependencies when developing
    Install Composer.
    Create a composer.json configuration file.
    Install the dependencies you want to use.

  2. Install a tool to prefix the dependencies

    We use PHP Scoper from Humbug to do so. To install it into your project, you can run the following command in your plugin directory:
    composer install humbug/phpscoper --dev

  3. Configure PHP Scoper

    This tells the tool which classes should be prefixed and in what way: php ./vendor/humbug/php-scoper/bin/php-scoper add-prefix --prefix={your prefix} --output-dir=./vendor_prefixed/{dependency} --config={configuration file}

  4. Create aliases for the prefixed classes and interfaces

    To make sure your code work with a Composer-managed site and a stand-alone zip. See the code we use in Yoast SEO to do this.

  5. Create an artifact

    - Use Composer to install all dependencies.
    - Run the script to prefix dependencies.
    - Remove the prefixed-dependencies from the Composer configuration. Do not commit the composer.lock or composer.json files.
    - Run composer install to only install the dependencies that need to be shipped.
    - Copy the `vendor_prefixed` and `vendor` folders to the artifact folder.
    - Create an artifact to publish on WordPress.org or install on a WordPress site manually.
    - Reset composer config to restore the configuration to the development state.

In Yoast SEO we use Grunt to automate these tasks. This way we can just run “grunt artifact” to build a plugin installer which would represent the plugin in the same way as when it is released.

Optimizing when dependencies are prefixed

As running the prefix-dependencies script takes time to run, we only want to run this when they are not present and are needed. We create a dependencies-prefixed.txt file in the vendor_prefixed folder to check against, if the dependencies are present.

Uncommon Composer configuration behaviour

Modifying the composer.json and composer.lock files to remove the dependencies that have been prefixed, is a step that carries some risks and requires cleanup. We have considered the alternative of copying the composer.json and composer.lock to the artifact folder, followed by performing the operations to prepare the vendor and vendor_prefixed folders on that location, leaving the Composer files in the development environment untouched. As the artifact does not contain any Composer configurations, these files need to be removed completely after work on the dependencies is done. This approach makes it harder to test both prefixed and non-prefixed usage of the dependencies during development.