Tutorials UPDATED: 12 December 2023

WordPress Plugin Conflicts: How to Prevent Composer Dependency Hell

Konstantinos Pappas

5 min read
WordPress Plugin Conflicts: Prevent Composer Dependency Hell

Have you ever developed a WordPress plugin that ended up conflicting with another plugin due to them depending on specific, but different, versions of the same Composer package? You’re on the highway to dependency hell.

Conflicts When Distributing a WordPress Plugin

While Composer is great for managing your dependencies in a controlled environment, challenges arise when you publish your plugins to the WordPress Plugin Directory, where you don’t have control over what your users install.

Given the vastness of the WordPress ecosystem, it’s likely that your plugin is eventually going to run alongside another plugin that may also use Composer to manage its dependencies. Things are about to get ugly when both plugins specify a different version of the same package, resulting in hard to reproduce issues.

Prefixing Namespaces

Ideally, WordPress core would come up with a solution for the whole dependency management problem (there’s been a lot of discussions over the years). In the meantime, it’s up to us to handle it.

Whenever there’s a name collision on WordPress, we typically solve it by prefixing everything. We prefix class and function names, theme and plugin slugs, etc. So, we could prefix the namespaces of every package we’re loading.

However, that won’t be enough. There might be global functions, traits, and they all have to be prefixed as well. Going through this for every single package our plugin relies on, can quickly become a cumbersome endeavor.

There’s a Better Way!

There are multiple tools to automatically wrap all your Composer vendor packages in your own namespace, preventing conflicts with other plugins that may potentially load the same dependencies in different versions.

We’ve listed a few of them below:

All of them have their own pros and cons, so take a look at their README and choose the one that fits your use case the best.

A Real-World Example

We’ve recently released our Pressidium Cookie Consent plugin. It makes it easy to add a stylish customizable cookie consent banner to your WordPress website, and, conditionally load third-party scripts (analytics, targeting, etc.), based on user-selected preferences, to help you comply with EU GDPR cookie law, CCPA, and similar regulations.

Our WordPress plugin depends on a couple of Composer packages, and @williarin opened an issue letting us know about a conflict with one of those packages.

Installing and Configuring Mozart

Let’s fix it! We’ll start by installing coenjacobs/mozart as a development dependency:

$ composer require --dev coenjacobs/mozart

We’re going to update the composer.json file to configure Mozart:

"extra": {
    "mozart": {
        "dep_namespace": "Pressidium\\WP\\CookieConsent\\Dependencies\\",
        "dep_directory": "/includes/Dependencies/",
        "classmap_directory": "/includes/classes/dependencies/",
        "classmap_prefix": "Pressidium_Cookie_Consent_",
        "packages": [
            "league/container",
            "psr/log"
        ]
    }
},
"scripts": {
    "post-install-cmd": [
        "\"vendor/bin/mozart\" compose",
        "composer dump-autoload"
    ],
    "post-update-cmd": [
        "\"vendor/bin/mozart\" compose",
        "composer dump-autoload"
    ]
}

Our root namespace is Pressidium\WP\CookieConsent, so we’ve set the (sub)namespace Pressidium\WP\CookieConsent\Dependencies for the dependencies we’re about to wrap. The wrapped dependencies will be generated by Mozart, and they’ll be stored under the /includes/Dependencies directory. Similarly, we set a classmap directory and prefix.

We specify that we’d like to wrap the league/container and psr/log packages, and set the scripts to run Mozart every time we install or update our dependencies.

Let’s run composer update and let Mozart compose all dependencies as a package inside our plugin.

Updating Namespace References

Now, we have to update our codebase to reflect those changes.

That’s as simple as changing all namespace references from something like:

use Psr\Log\LogLevel;
use League\Container\ServiceProvider\AbstractServiceProvider;

to our own namespace:

use Pressidium\WP\CookieConsent\Dependencies\Psr\Log\LogLevel;
use Pressidium\WP\CookieConsent\Dependencies\League\Container\ServiceProvider\AbstractServiceProvider;

Picking a good namespace can be a challenging task, perhaps deserving its own blog post. So, here are some general guidelines:

  • When deciding on the top-level namespace name, or “vendor namespace,” choose something distinct to reduce the likelihood of name collisions. Your company name or domain name are solid choices.
  • Follow a consistent pattern of \Company\Software\Package (or \Author\Software\Package for individual developers) for your namespace structure.
  • It’s common practice to avoid underscores in your namespaces; opt for CookieConsent over Cookie_Consent.
  • Keep namespaces readable and concise. Strike a balance between being descriptive and avoiding unnecessarily long namespaces.

Updating the GitHub Actions Workflow

Finally, we’re going to update our GitHub Actions workflow to install the dependencies and wrap them (remember, composer install is going to run Mozart since we’ve set the post-install-cmd script):

- name: Setup PHP
  uses: shivammathur/setup-php@v2
  with:
    php-version: '7.4'

- name: Install dependencies and wrap them
  run: composer install

Then, remove any development dependencies without running any Composer scripts this time (we don’t want to run Mozart again):

- name: Remove development dependencies
  run: composer update --no-dev --no-scripts --optimize-autoloader

That’s it! We’ve now wrapped all Composer vendor packages inside our own namespace, and our plugin is ready to be deployed 🚀

Recap

Using Composer to manage your plugin’s dependencies works great when used in a controlled environment. However, distributing your WordPress plugin may result in hard to reproduce bugs when multiple plugins load conflicting versions of the same package.

Fortunately, there are easy-to-use tools to wrap your vendor packages in your own namespace, preventing any conflicts and breaking free from the dependency hell!

Start Your 14 Day Free Trial

Try our award winning WordPress Hosting!

OUR READERS ALSO VIEWED:

See how Pressidium can help you scale
your business with ease.