How to use PhpScoper in WordPress Plugins to Remedy Namespace Collisions

Have you ever wanted to use a composer package in your WordPress plugin? If so, then you may have run into namespace collision problems between your plugin and other plugins that use the same package. Fortunately, we can use PhpScoper, an open-source namespace prefixer, to prevent this issue on WordPress sites with multiple plugins that use the same package. Let’s learn how to fix namespace collisions by using PhpScoper in WordPress plugins!

PhpScoper in WordPress Plugins Cover Image

The Problem

First of all, it is important to understand what a namespace collision is, and why it is a problem so that we can properly fix it. A namespace collision occurs when two or more classes, functions, or constants share the same namespace. In an average project built with php, this is rare because we only have one Composer setup that manages dependencies for the whole project. This means that it is only possible to have one installation of each package installed at a time, which means that there will be no packages sharing the same namespace.

But, in WordPress, we can have many plugins installed on a single site. And each plugin can have its own dependencies managed by its own Composer installation. So, if two active plugins use the same package, there will be two different packages with the same namespace. In this situation, php will essentially “choose one” of the two installations of the package to use everywhere that package is used, throwing the other one out unused.

Now, imagine the two plugins rely on two different major releases of the same package that are not compatible with each other. This can leads to sneaky bugs that only present themselves when separate plugins that use the same package are active on the site. I myself faced a bug like this in a recent client project.

I had one plugin that added a custom slideshow banner to the site that relied on a package I wrote, jtgraham38/jgwordpresskit, a framework for building WordPress plugins (you can read more about it by clicking here). Then, I decided to install another plugin that my business has been developing, ContentOracle AI Chat, to add retrieval-augmented ai chat capabilities. That plugin uses jtgraham38/jgwordpresskit at a different version than the custom block plugin. The problem was, these different versions had a different structure which made them incompatible with each other. This led to crashes on the frontend of the site, but only when both were activated simultaneously. After a lot of digging, I realized that I needed a robust solution to prevent namespace collisions in all of my plugins.

The Solution: PhpScoper

It didn’t take me long to zero in on PhpScoper as the solution to my problem. PhpScoper is a tool that moves all code in a PHP project to its own unique namespace by adding a custom prefix. Let’s take a step-by-step look at how to use PhpScoper to isolate the dependencies of a WordPress plugin.

Step 1: Install PhpScoper and Utils

First, we need to install PhpScoper with Composer. We will do a global install, so that PhpScoper will not be bundled in as a dependency of our plugin. To do that, simply run:

composer global require humbug/php-scoper

In order to use PhpScoper to properly scope dependencies in a WordPress plugin, we need to be sure to exclude WordPress functions, classes, and other symbols from prefixing. Luckily, there is another package we can install that will configure this set of symbols for us. Let’s install it globally. Run

composer global require sniccowp/php-scoper-wordpress-excludes

Now that we’ve got PhpScoper and its WordPress adapter installed and ready to go, let’s move on to the configuration step.

Step 2: Configure PhpScoper

After we have everything installed, the next step is to configure PhpScoper to work with our plugin. PhpScoper is configured by creating a file called scoper.inc.php in the base directory of your plugin. I advise tracking this file in git, but adding it to your .distignore file (if you use WP CLI to bundle your plugin). Once you have created it, add the following to it:

<?php
// scoper.inc.php

function getWpExcludedSymbols(string $fileName): array
{
    //insert username below in place of 'user'
    $username = 'user';
    $filePath = '/home/'.$username.'/.config/composer/vendor/sniccowp/php-scoper-wordpress-excludes/generated/'.$fileName;

    return json_decode(
        file_get_contents($filePath),
        true,
    );
}

$wpConstants = getWpExcludedSymbols('exclude-wordpress-constants.json');
$wpClasses = getWpExcludedSymbols('exclude-wordpress-classes.json');
$wpFunctions = getWpExcludedSymbols('exclude-wordpress-functions.json');


return [
  'exclude-constants' => $wpConstants,
  'exclude-classes' => $wpClasses,
  'exclude-functions' => $wpFunctions,
  // ...
];

This configuration uses the sniccowp/php-scoper-wordpress-excludes package to prevent PhpScoper from prefixing the namespaces of WordPress core constants, functions, and classes. This is critical to ensure your plugin continues to work properly. If you need a more advanced configuration, consult the PhpScoper docs.

Note how I defined $filePath. You may need to change it based on your development environment. It should contain the path to the json files in the sniccowp/php-scoper-wordpress-excludes installation of your global Composer vendor directory, since this is where we installed the package.

Step 3: Run PhpScoper

Now that everything is set up and ready to go, we can run PhpScoper! Simply open a terminal in the root directory of your plugin, and run:

php-scoper add-prefix --prefix="your\namespace\prefix\here"

Replace the placeholder with any valid composer namespace. You will see PhpScoper run, and then a new directory, build, will appear in the root of your plugin. This will contain a complete copy of your plugin, with all the namespaces prefixed with the one you passed in. You can validate this by opening any file where you used a use ... statement. You will see your prefix prepended to the namespace of the imported asset.

Step 4: Regenerate Autoloader

Now, all your namespaces are prefixed. But, there is one more step to make your plugin work properly: regenerating the autoloader. If you don’t do this, Composer won’t know how to locate your newly prefixed packages. Luckily, it’s easy. Simply navigate to the build directory, and run

composer dump-autoload

It’s that simple!

Step 5: Create Distribution Archive

Finally, we are ready to bundle up our plugin and use it in production. I like to use WP CLI to bundle my plugins for release. The full use of WP CLI to bundle plugins is outside the scope of this article. If you are interested in learning more, see the WP CLI documentation. First, ensure your .distignore file was added to the build by PhpScoper (sometimes it skips it). If it was not included, simply copy it from your plugin’s root to the build root. Then, run

wp dist-archive build

This will dive into the build directory, grab all the files recursively except those listed in .distignore, and output them in a semantically versioned zip archive. Then, all you have to do is rename the archive from build.X.X.X.zip to your_plugin.X.X.X.zip, and your plugin is ready for release!

Of course, if you prefer not to use WP CLI, you can simply zip up and rename the build directory manually. But learning to use the CLI is well worth the effort, and I highly recommend it.

Conclusion

Using composer packages in WordPress plugins without proper namespace scoping can give rise to a whole host of nasty bugs that only occur when specific combinations of plugins appear on a site. This can make them very hard to track down and stamp out. By following the steps listed here, you can save yourself hours of headache by bringing the standard WordPress already demands you apply to other custom plugin data, such as functions, options, and custom tables to your Composer dependencies. Preventing namespace conflicts is critical to the success of any WordPress plugin that makes use of Composer packages. And hopefully, figuring out how to do that is now a little bit easier for you!

Newsletter

📧 Sign up to receive updates when I create new content!

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *