
Recently, quite a lot of posts on this topic appeared on Habré, but in essence they can be called: "Look, I put the Varnish / W3 Total Cache and keep a million requests on the" Hello world "page." This article is designed more for geeks who want to know how it all works and write your own plugin for page caching.
What for?
The standard question that arises from each developer before creating a
bike of an existing functionality. Indeed, there are a lot of ready-made plug-ins and many of them are quite high-quality, but you need to understand that first of all they are designed for static blogs. What to do if you have a non-standard WordPress site?
Let's get started
What tools does WordPress provide us?
As everyone knows, this CMS makes it easy to extend its functionality with plugins, but not everyone knows that there are several types of plugins:
- ordinary plugins
are in wp-content / plugins
the administrator is free to install, activate and deactivate them; - required plugins
are in wp-content / mu-plugins
these plugins are enabled automatically and cannot be deactivated; - system plugins
are in wp-content
allow you to override kernel classes or implement their own functionality in them;
These include:
- sunrise.php
It is loaded at the very beginning of the kernel initialization. Most commonly used for domain mapping; - db.php
Allows you to override the standard class to work with the database; - object-cache.php
Allows you to redefine the standard object caching class, for example, if you want to use Memcached or Redis; - advanced-cache.php
Allows you to implement page caching, what we need!
')
advanced-cache.php
In order for this plugin to start functioning, it must be placed in the
wp-content directory, and the line added to
wp-config.php :
define('WP_CACHE', true);
If you look at the WordPress code, you can see that this script is loaded at an early stage of loading the platform.
Also, after loading the kernel, the CMS will attempt to call the wp_cache_postload () function, but about it later.
Storage
It is best to use fast storages for storing caches, since the speed at which content is retrieved from the cache directly depends on their speed. I would not advise using MySql or the file system; Memcached, Redis, or other storages that use RAM will do a better job.
I personally like Redis, as it is quite easy to use, it has good read / write speeds and as a nice bonus it saves a copy of the data to the hard disk, which will allow not to lose information when the server is restarted.
$redis = new Redis();
Of course, this is not a complete list of methods, the entire list of APIs can be studied on the
official website , but for most tasks this is sufficient.
If the site uses a pumped object cache (
object-cache.php ), then it makes sense to use its API:
wp_cache_set( $key, $value, $group, $timeout ); wp_cache_get( $key, $group ); wp_cache_delete( $key, $group );
Simplest page caching
The code is purposely simplified, many checks are removed, so as not to confuse the reader with unnecessary constructions and to focus on the logic of the caching itself. In the file
advanced-cache.php we register:
Everything, you have received the simplest working page cache, now we will consider each section in more detail.
Key creation $key = 'host:' . md5( $_SERVER['HTTP_HOST'] ) . ':uri:' . md5( $_SERVER['REQUEST_URI'] );
In this case, the key is the URL of the page. Using the
$ _SERVER global variable and hashing cannot be called a best practice, but for a simple example it will do. I advise you to add separating sections of the line as "host:" and "uri:", since it is convenient to use them in regular expressions. For example, get all the keys for a specific host:
$keys = $redis->keys( 'host:' . md5( 'site.com' ) . ':*' );
Issuance from cache
Everything is simple, if the cache has already been created, then give it to the user and complete the execution.
Save to cachePHP function
ob_start intercepts all subsequent output to the buffer and allows you to process it at the end of the script. In simple words, we get all the site content in the $ html variable.
ob_start( function( $html ) {
Next, save the data to the cache:
$data = [ 'html' => $html, 'created' => current_time('mysql'), 'execute_time' => timer_stop(), ]; wp_cache_set($key, $data, 'advanced-cache', MINUTE_IN_SECONDS * 10);
It makes sense to save not only HTML, but also other useful information: cache creation time, and so on. I highly recommend saving HTTP headers, at least
Content-Type and sending them when it is issued from the cache.
Improving
In the example above, we used the
is_admin () function to exclude admin panel caching, but this method is not very practical for two reasons:
- requests for admin-ajax.php do not fall into the cache;
- if the administrator first visits the page, then its “admin bar” and other things harmful to users will get into the cache;
The best solution for a simple site would not use the cache for logged in users (administrators) at all. Since
advanced-cache.php is executed before the kernel is fully loaded, we cannot use the
is_user_logged_in () function, but we can determine if cookie authentication is in place (as you know, WordPress does not use a session).
Complicate the task
Suppose our site gives a different content for users from different regions or countries. In this case, the cache key should be not only the URL of the page, but also the region:
$region = get_regeon_by_client_ip( $_SERVER['REMOTE_ADDR'] ); $key = 'host:' . md5( $_SERVER['HTTP_HOST'] ) . ':uri:' . md5( $_SERVER['REQUEST_URI'] ) . ':region:' . md5( $region );
By this principle, we can form different caches for different groups of users by any parameters.
wp_cache_postload ()
This function is called after the kernel is loaded and it is also convenient to use it in some cases.
From experience I will say that this option works much more stable:
function wp_cache_postload() { add_action( 'wp', function () { ob_start( function( $html ) {
At the time of the
wp_cache_postload () call, the add_action function already exists and can be used.
There are situations when you need data that is impossible to get from cookies, IP and other resources available at the initialization stage to generate a cache key. For example, you need to generate an individual cache for each user (sometimes it makes sense).
function wp_cache_postload() { $key = 'host:' . md5( $_SERVER['HTTP_HOST'] ) . ':uri:' . md5( $_SERVER['REQUEST_URI'] ) . ':user:' . get_current_user_id(); if( $data = wp_cache_get( $key, 'advanced-cache' ) ) { $html = $data['html']; die($html); } add_action( 'wp', function () { ob_start( function( $html ) {
As you can see in the example, all the logic is placed in the body of
wp_cache_postload, and all the functions of the platform are already available, including
get_current_user_id () . This option is a bit slower than the previous one, but we have endless possibilities for fine-tuning the page cache.
What should not be forgotten
- These examples are very simplified, if you use them in your projects - do not be lazy to add conditions for caching:
- GET requests only
- only if there are no errors on the page
- only if there is no set-cookie
- only if the status is 200 or 301
- The effectiveness of the cache depends on its lifetime. Increasing $ timeout, bother to think about invalidating the cache when data changes.
- WP Cron is launched later than advanced-cache.php, it may simply not work with a high cachehit.
Conclusion
There is nothing difficult in writing your own page caching. Of course, this does not make sense for a typical site, but if you have created a monster - this material should be useful.