Composer. If you’ve worked with PHP for any period of time you’ve likely heard of or used Composer. We’ve written about it whole bunch on the blog before, and it’s a great tool for grabbing dependencies for your project (especially for us that remember PEAR 😬).
It’s not without issues however, especially with WordPress. Iain wrote a piece about dependency management and WordPress – the crux of which is that it’s difficult to bundle dependencies with WordPress plugins. This is because if you bundle a dependency with your plugin, and another plugin bundles the same dependency but a different version, you can run into a race condition. Basically, this means you don’t know what order the dependencies will be loaded in:
Knock knock Race condition Who’s there?
Ok, so how do you fix this? Avoid Composer and write everything from scratch? Well, you could do that, but you’d be missing out on all the cool stuff other folks have written, so you don’t reinvent the wheel.
Another option is a neat little tool we use here at Delicious Brains called PHP-Scoper. In this post we’ll see how you can use PHP-Scoper to namespace your dependencies and avoid the world’s worst knock-knock joke.
Getting Started
Installing PHP-Scoper is pretty easy, it’s a standard Composer global install, so you can use it on any project on your system:
composer global require humbug/php-scoper
You can also install it for your project directly with the Composer bin plugin:
composer require --dev bamarni/composer-bin-plugin
composer bin php-scoper require --dev humbug/php-scoper
Once you’ve got the package installed either globally or locally you just need to run the add-prefix
command in your working directory:
php-scoper add-prefix
Running this command in your project directory will kick off the PHP Scoper process and it will copy all of your project files (including any dependencies) into a new ‘build’ folder. In doing so, it will also update any dependency use
statements to the new local version.
For example, before running PHP Scoper you may have code that references code in your Composer vendor
folder:
namespace DeliciousBrains\WPMDB;
use DI;
After running PHP-Scoper, the use statement will reference the local version of the dependency and use the prefix you pass as a configuration parameter. In this case I used DeliciousBrains\WPMDB\Container
:
namespace DeliciousBrains\WPMDB;
use DeliciousBrains\WPMDB\Container\DI;
Example output of the build folder:
Once the build is complete, you will need to run composer dump-autoload
in the build folder to update the autoloader, but that’s about it. Now you’ve got a build of your project that includes all your prefixed dependencies. Cool, huh?
Configuration
For some projects, the default configuration may be all you need. For WP Migrate DB Pro we’ve got A LOT of classes and files, so running PHP-Scoper through all the project files takes a while and isn’t necessary. For the time being, since we’re not using Composer dependencies deep inside the codebase, we can use filters to reduce the number of files scanned.
PHP-Scoper comes with some handy configuration options for determining which files and folders it should run through. If you run php-scoper init
in your project it will create a scoper.inc.php
file with a bunch of pre-configured filters you can modify.
Internally, PHP-Scoper uses the Finder Symfony component which is super-powerful. For WP Migrate DB Pro, we’re only using a small subset of the feature set as we use a build script to package the plugin zip together. Here’s our minimal configuration:
'finders' => [
Finder::create()
->files()
->ignoreVCS( true )
->in( __DIR__ . '/build-cfg/composer-tmp/vendor/' ),
Finder::create()->append( [
__DIR__ . '/build-cfg/composer-tmp/composer.json',
] ),
]
For our use case, I didn’t want PHP-Scoper to run through all of our Composer dependencies but only one package (our new fancy dependency injection container, PHP DI). What it did was create a new composer.json
file that only includes the one package. Of course, Composer flattens all dependencies (and PHP DI includes a few other dependencies) so PHP-Scoper runs through a vendor folder with only a few packages.
Below is the basic script that runs PHP Scoper and composer dump-autoload
after it’s all done:
#!/usr/bin/env bash
php-scoper add-prefix --output-dir=builds/php-scoper-output --force
cd builds/php-scoper-output && composer dump-autoload
In our build script I then run the composer dump-autoload
and a few other find-and-replace’s with the sed
command.
<?php
shell_exec("mv $tmp_dir/php-scoper-output/vendor $tmp_dir/$plugin_name");
shell_exec("sed -i '' 's/\/\.\.\/\.\.\/vendor/\/vendor/' builds/wp-migrate-db-pro/wp-migrate-db-pro.php"); // Move vendor folder into build folder
shell_exec("sed -i '' 's/use DI/use DeliciousBrains\\\WPMDB\\\Container\\\DI/' builds/wp-migrate-db-pro/class/WPMDBDI.php"); // Manually swap namespace
shell_exec("sed -i '' 's/\/\/ @build replacement/use DeliciousBrains\\\WPMDB\\\Container\\\DI;/' builds/wp-migrate-db-pro/class/WPMDBDI_Config.php"); // Manually swap namespace
shell_exec("rm -rf $tmp_dir/php-scoper-output/");
I’m using shell_exec()
here because the plugin-build
is a PHP script that includes a bunch of predefined variables.
You’ll note in the above sed
commands I’m manually swapping a couple ‘use’ statements. This is because it was overkill to run PHP-Scoper on two files. As with any tool, there are good use cases and bad use cases. In this case, running PHP-Scoper over two files created another subfolder in the output directory so it was actually more work to use it here.
Caveats
OK so that all sounds good, but are there any drawbacks to using PHP-Scoper? One thing I did notice was that if I let the process run loose on all of our files it would take a while and really spike my Macbook’s CPU. This was due to every developer’s nemesis the node_modules
folder:
Because PHP is single-threaded, only one core of the CPU can be used at a time, so you can end up pinning your poor CPU if you’re not careful. Luckily, with the Finder configuration I mentioned above, this situation can be avoided but it’s something to watch out for.
I should also mention that there are a few other options out there that do similar things, and they generally solve the same problem as PHP-Scoper. However, The WP Offload Media team also uses PHP-Scoper to build their plugins (it seems Yoast SEO does as well), so I figured if it’s good enough for Yoast and Jonesy, it’s good enough for me!
Wrap up
That’s all there really is to using PHP-Scoper. It’s a great tool that allows you to use Composer dependencies in your plugin without worrying and run conditions and conflicts with other code.
Do you use Composer or other dependencies with your WordPress code? How do you manage conflicts? Let us know below!