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:
coenjacobs/mozart
— potentially the first tool to tackle this issuebrianhenryie/strauss
— a fork of Mozarttypisttech/imposter
— similar toolhumbug/php-scoper
— does not focus exclusively on conflicts with WordPress plugins, it could work with any PHP project
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
overCookie_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!