Using APCu with PHP 7, WordPress, and W3 Total Cache

PHP with APC APC consists of 2 parts: opcode caching and memory caching. PHP 5.5 and newer have built-in opcode caching, so APC is no longer supported. A new PECL package, APCu, was created for the memory caching part. The prefix for APC memory caching functions changed from “apc” to “apcu”. For example, apc_store became apcu_store and apc_fetch became apcu_fetch.

APCu Backwards Compatibility

Some plugins, such as W3 Total Cache, are still using the apc_* functions. There’s a PECL package designed to fix this: APCu Backwards Compatibility Module (apcu_bc). All apcu_bc does is add aliases for the apc_* functions. For example, here’s the line that defines apc_store:

<span class="pl-c1">PHP_FALIAS</span>(apc_store, apcu_store, arginfo_apcu_store)
However, apcu_bc doesn’t seem to fully support PHP 7. There’s a beta version of apcu_bc that claims to support PHP 7, but I can’t build it on my server. After a bit of searching, it seems like lots of people are having the same issue.

Using a WordPress plugin instead of apcu_bc

Instead of installing an extension to create the aliases, I decided to create the aliases using PHP. I added this snippet to a plugin:

// APC polyfill.
if (function_exists('apcu_add') && !function_exists('apc_add')) {
	function apc_add() {
		return call_user_func_array('apcu_add', func_get_args());
	}

	function apc_cache_info() {
		return call_user_func_array('apcu_cache_info', func_get_args());
	}

	function apc_cas() {
		return call_user_func_array('apcu_cas', func_get_args());
	}

	function apc_clear_cache() {
		return call_user_func_array('apcu_clear_cache', func_get_args());
	}

	function apc_dec() {
		return call_user_func_array('apcu_dec', func_get_args());
	}

	function apc_delete() {
		return call_user_func_array('apcu_delete', func_get_args());
	}

	function apc_exists() {
		return call_user_func_array('apcu_exists', func_get_args());
	}

	function apc_fetch() {
		return call_user_func_array('apcu_fetch', func_get_args());
	}

	function apc_inc() {
		return call_user_func_array('apcu_inc', func_get_args());
	}

	function apc_sma_info() {
		return call_user_func_array('apcu_sma_info', func_get_args());
	}

	function apc_store() {
		return call_user_func_array('apcu_store', func_get_args());
	}

	class APCIterator extends APCUIterator {}
}
This should work for most plugins using APC. Most plugins shouldn’t be using APC functions until the plugins_loaded hook. Even if a plugin uses APC functions before the plugins_loaded hook, you can make this snippet run before the other plugins by modifying the active_plugins option. However, W3 Total Cache is different. W3 Total Cache’s object cache uses WordPress’s external object cache feature. WordPress loads the external object cache before any active plugins are loaded. This means that W3 Total Cache runs before any normal plugins.

Fixing APCu caching for W3 Total Cache

To work around this, I used a very hacky solution. I made my plugin append the above snippet to load.php. Another solution was to use PHP’s auto_prepend_file option. However, since you can prepend only one file, I prefer not to use this (also, some plugins such as Wordfence use this option). A third solution was to modify W3 Total Cache. However, if I install another plugin that uses apc_* functions, I have to apply the changes to that plugin as well. With the snippet below, the apc_* will be available to all plugins. In addition, when WordPress updates, the APC aliases will be automatically added.

<![CDATA[
// APC polyfill.
if (!defined('APC_POLYFILLED')) {
	$content = <<<'EOT'

define('APC_POLYFILLED', true);

if (function_exists('apcu_add') && !function_exists('apc_add')) {
	function apc_add() {
		return call_user_func_array('apcu_add', func_get_args());
	}

	function apc_cache_info() {
		return call_user_func_array('apcu_cache_info', func_get_args());
	}

	function apc_cas() {
		return call_user_func_array('apcu_cas', func_get_args());
	}

	function apc_clear_cache() {
		return call_user_func_array('apcu_clear_cache', func_get_args());
	}

	function apc_dec() {
		return call_user_func_array('apcu_dec', func_get_args());
	}

	function apc_delete() {
		return call_user_func_array('apcu_delete', func_get_args());
	}

	function apc_exists() {
		return call_user_func_array('apcu_exists', func_get_args());
	}

	function apc_fetch() {
		return call_user_func_array('apcu_fetch', func_get_args());
	}

	function apc_inc() {
		return call_user_func_array('apcu_inc', func_get_args());
	}

	function apc_sma_info() {
		return call_user_func_array('apcu_sma_info', func_get_args());
	}

	function apc_store() {
		return call_user_func_array('apcu_store', func_get_args());
	}

	class APCIterator extends APCUIterator {}
}

EOT;

	$file = file_get_contents(ABSPATH . WPINC . '/load.php');
	file_put_contents(ABSPATH . WPINC . '/load.php', $file . $content);
}
]]>
Let me know if you know of a better solution!

Leave a Reply

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