Automatically Install PHP’s Runkit7 via Composer

A WordPress plugin I’ve been working on recently needed to be able to accept configuration in a few different ways: users should be able to define a constant in the wp-config.php file or fill out a form within a settings screen. If the constant is defined, the setting screen should be aware of that and hide the setting, since the constant should take precedence.

This is a pretty common pattern in WordPress plugins, but it can get rather tricky to test; by design, once a constant is defined in PHP, you shouldn’t be able to change its value. PHPUnit has ways to work around this by running tests that define constants in separate processes, but this can seriously impact the performance of your test suite. Furthermore, the WordPress core test suite is pretty tightly coupled, so it doesn’t like when tests are run separately.

Enter Runkit7

An alternative way to deal with tests that require redefining constants and/or functions is an approach known as “monkey-patching”, where we’re literally re-defining behavior at runtime:

That’s pretty cool, huh? It’s also pretty frightening — the ability to redefine functionality at runtime is rarely a good idea. As a general rule, if you’re monkey-patching in a production environment, you’re probably doing it wrong.

The official PHP extension for monkey-patching has been Runkit, but the package has yet to add official support for PHP 7.0+. An unofficial fork, dubbed “Runkit7”, is working to add PHP 7+ support to Runkit and, from what I’ve seen, it appears to be working well.

Using Runkit7, we might define a constant at the beginning of our test, then clean it up at the end; this prevents the constant being defined here from impacting other tests in the suite:

Better yet, we can add a method to our test suite using PHPUnit’s @after annotation to automatically have it run after every test that have set the value. This will ensure that even if an assertion in our test method fails the constant will still be removed at the end of the test:

You’ll notice that our testConfigurationViaConstant() method also includes a @requires extension runkit annotation — this tells PHPUnit that the test method shouldn’t be executed unless Runkit is active in the test environment. If Runkit (and its functions) are unavailable, PHPUnit will simply mark the test as skipped and move on.

Generally, if I have a test suite that might need to skip tests due to Runkit being missing, I’ll indicate that with the following line in my test suite’s bootstrap file:

Of course, it would be nice if there was a way to automatically install Runkit7 in a development or testing environment, wouldn’t it?

Automatically install Runkit7 in a development or testing environment

When I found out about Runkit7, I immediately wanted a way to be able to specify it as a development dependency via Composer. While installation via PECL isn’t particularly difficult, it’s nice to be able to script the installation. The result is my runkit7-installer Composer package.

To install it into your project, simply install via Composer:

Once installed, running the vendor/bin/install-runkit.sh file will automatically attempt to install and activate Runkit7 in your local environment, making it easier than ever to use it in your projects!

For instance, Runkit7 might be used in your Travis CI builds with a travis.yml file that looks something like this:

Hopefully this helps someone else!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.