WordPress + Angular + Redis = Double Happiness

Using WordPress as a backend / REST API Provider for sendhappiness.org was fun and I am really happy that I chose WordPress. The whole webapp and mobile app came out pretty quickly.

In this article I am going to write about the process of using WordPress as a backend and completely decoupling it from frontend by using Redis.

My WordPress setup looks like this http://snypd.com/setting-up-wordpress-environments-like-a-boss/
Please give it a look, it will hardly take 4-5 mins.

As you can see in the above mentioned link I like to keep config files separately. There is a wordpress.php file in the config folder whose job is to load the environment and boot up the WordPress. I need something which can load the environment but only boots the wp when needed, so I copied the code of wordpress.php file, pasted into a new file api.php and commented out the require_once ABSPATH . 'wp-settings.php'; which is responsible for loading wp.

api.php

<?php  
/**
 * The base configurations of the WordPress.
 *
 * This file has the following configurations: MySQL settings, Table Prefix,
 * Secret Keys, WordPress Language, and ABSPATH. You can find more information
 * by visiting {@link http://codex.wordpress.org/Editing_wp-config.php Editing
 * wp-config.php} Codex page. You can get the MySQL settings from your web host.
 *
 * This file is used by the wp-config.php creation script during the
 * installation. You don't have to use the web site, you can just copy this file
 * to "wp-config.php" and fill in the values.
 *
 * @package WordPress
 */

define('APP_ROOT', dirname(__DIR__));  
define('APP_ENV', getenv('APPLICATION_ENV'));  
if (file_exists(APP_ROOT . '/config/env/' . APP_ENV . '.php')) {  
    require APP_ROOT . '/config/env/' . APP_ENV . '.php';
} else {
    require APP_ROOT . '/config/env/server.php';
}

/**
 * WordPress Localized Language, defaults to English.
 *
 * Change this to localize WordPress. A corresponding MO file for the chosen
 * language must be installed to wp-content/languages. For example, install
 * de_DE.mo to wp-content/languages and set WPLANG to 'de_DE' to enable German
 * language support.
 */
define('WPLANG', '');

/* That's all, stop editing! Happy blogging. */

/** Absolute path to the WordPress directory. */
if (!defined('ABSPATH')) {  
    define('ABSPATH', APP_ROOT . '/htdocs/wordpress/');
}
// require_once ABSPATH . 'wp-settings.php';

Lots of people have asked me why I have not used the WP API plugin [ http://wp-api.org/ ] and the reason is that I wanted to learn something new and also if I use wp api plugin then I will loose the capability of loading WordPress whenever I need.

Here is the tiny function which I use to load WordPress if I need to run a WP related function

function i_need_wp() {  
        global $table_prefix;
        require_once ABSPATH . 'wp-settings.php';
    }

I am not generating APIs by creating a plugin but I created a folder in the root directory and there I am including all the resources. The API URL looks like this http://api.sendhappiness.org/v2/products, v2 is a folder in the root directory and the index.php file looks like this

/v2/index.php

<?php  
require '../../config/api.php';  
define('SH_API_DIR', dirname(__FILE__) . '/');  
require_once SH_API_DIR . 'class-sh-api.php';  

The class-sh-api.php file basically include all the routes and setups Slim framework [ http://www.slimframework.com/ ].

class-sh-api.php

//----Some Code-----
// Start Slimframework
$app = new \Slim\Slim(array(
            'mode' => 'production',
            'templates.path' => SH_API_DIR . 'templates',
        ));

//Initiate redis
$app->redis = new Redis();
        $app->redis->connect("HOST");
//----Some Code-----

// Register Routes
// This function will include route files
$this -> register_routes();

Let me show you route file for products.

route-products.php

$app = \Slim\Slim::getInstance();
$app->get('/products', function () use ($app) {
// The lrange is a redis list function which will get the all the ids stored in the product
    $product_ids = $app->redis->lrange("product_ids:simple", 0, -1);
    foreach ((array) $product_ids as $product_id) {
        $_data[] = unserialize($app->redis->hGet("product:$product_id", "data"));
    }
    $result = array('products' => $_data);
    return json_response($result);
});

This is pretty simple piece of code. In this I am handling all the get request on /products endpoint and returning json data from redis

$app -> get

It will handle the get request on products endpoint

$product_ids = $app->redis->lrange("product_ids:simple", 0, -1);

lRange is a redis list function, it will return data stored in list name product_ids:simple

    foreach ((array) $product_ids as $product_id) {
        $_data[] = unserialize($app->redis->hGet("product:$product_id", "data"));
    }

After getting the ids in an array, iterate through them and get the complete data of the product stored in a redis hash.

I used the same method for getting all the data for the webapp which made it pretty fast and responsive.

Now how to sync redis with WordPress? For this we are going to code a plugin and use WordPress's awesome function add_action :)

So, the code looks some thing like this

add_action('save_post', array($this, 'redis_mumbo_jumbo'), 10, 3);  
function redis_mumbo_jumbo($post_id, $post, $update) {  
        if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
            return;
        }
        $this->redis = new Redis();
        $this->redis->connect("HOST");
        $post_type = $post->post_type;
        switch ($post_type) {
            case 'product':
                # code...
                $this->redis_add_product($post, $update);
                break;
            case 'post':
                $this->redis_add_post($post, $update);
                break;
            case 'shop_order':
                $this->redis_add_order($post, $update);
                break;
        }

    }

And that is how you get an insanely fast WordPress backend. :)

comments powered by Disqus