In this example we are going to see how redis can be used in symfony applications. I'll implement some basic features but you can extend it as you wish. You can see all commands here and documentation here.


About redis


Redis is an in-memory key-value cache and data store (also referred as NoSQL database). It can persist it's state to disk which helps recover data after restart unlike memcached. Since it stores data in memory, size of data cannot exceed the total memory space on the system. Nowadays, many prefer redis over memcached and MongoDB especially for performance reasons. It can store strings, hashes, lists, sets, sorted sets and more whereas memcached only supports strings.



What we will do



Install


$ sudo apt-get update
$ sudo apt-get install redis-server
$ sudo service redis-server status

Configuration file can be found in /etc/redis/redis.conf.


$ ps aux | grep redis
redis 31748 0.1 0.3 38576 7248 ? Ssl 13:42 0:00 /usr/bin/redis-server 127.0.0.1:6379
vagrant 31917 0.0 0.0 10472 904 pts/0 R+ 13:47 0:00 grep --color=auto redis

As you can see above the host is 127.0.0.1 and the port is 6379. Use command below to access redis command line tool. Type info to get information about the redis server and status.


$ redis-cli
127.0.0.1:6379>

Test


127.0.0.1:6379> monitor
1495812054.054517 [0 127.0.0.1:59164] "SET" "a" "1" # Persistent
1495812054.054517 [0 127.0.0.1:59164] "SETEX" "b" "10" "2" # Expire in 10 seconds

As you can above, I've already set two key-value pairs, a and b. The monitor command shows live logs of commands run against redis server.


127.0.0.1:6379> scan 0
1) "0"
2) 1) "a"
3) 2) "b"

127.0.0.1:6379> GET a
"1"
127.0.0.1:6379> TTL a
(integer) -1 # Persistent

127.0.0.1:6379> GET b
"2"
127.0.0.1:6379> TTL b # Run this command 3 seconds later
(integer) 7 # 7 seconds to expire
127.0.0.1:6379> TTL b # Run this command 20 seconds later
(integer) -2 # Expired

If you don't specify TTL while setting key-value pair, the key will never expire as TTL is set to -1 by default so technically this is a persistent operation.


Enable redis in PHP


$ sudo apt-get install php5-redis
$ sudo service apache2 restart

Symfony configuration


Add "ext-redis": "*" to composer.json and run composer update ext-redis command.


Application


Config.yml


parameters:
redis.host: 127.0.0.1
redis.port: 6379

Controllers.yml


services:
app.controller.redis:
class: AppBundle\Controller\RedisController
arguments:
- "@app.util.redis_helper"

Utils.yml


services:
app.util.redis_helper:
class: AppBundle\Util\RedisHelper
arguments:
- '%redis.host%'
- '%redis.port%'

RedisController


namespace AppBundle\Controller;

use AppBundle\Util\RedisHelper;
use RedisException;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

/**
* @Route("/redis", service="app.controller.redis")
*/
class RedisController
{
private $redisHelper;

public function __construct(RedisHelper $redisHelper)
{
$this->redisHelper = $redisHelper;
}

/**
* @param Request $request
*
* @Method({"GET"})
* @Route("/set")
*
* @return Response
*/
public function setAction(Request $request)
{
$key = $request->query->get('key');
$value = $request->query->get('value');
$ttl = $request->query->get('ttl');

$result = null;

try {
if ($key && $value) {
$this->redisHelper->set($key, $value, $ttl);
$result = ['key' => $key, 'value' => $value, 'ttl' => $ttl];
}
} catch (RedisException $e) {
$result = $e->getMessage();
}

return new Response(json_encode($result));
}

/**
* @param Request $request
*
* @Method({"GET"})
* @Route("/get")
*
* @return Response
*/
public function getAction(Request $request)
{
$key = $request->query->get('key');

$result = null;

try {
if ($key) {
$result = ['key' => $key, 'value' => $this->redisHelper->get($key)];
}
} catch (RedisException $e) {
$result = $e->getMessage();
}

return new Response(json_encode($result));
}

/**
* @param Request $request
*
* @Method({"GET"})
* @Route("/ttl")
*
* @return Response
*/
public function ttlAction(Request $request)
{
$key = $request->query->get('key');

$result = null;

try {
if ($key) {
$result = ['key' => $key, 'ttl' => $this->redisHelper->getTtl($key)];
}
} catch (RedisException $e) {
$result = $e->getMessage();
}

return new Response(json_encode($result));
}

/**
* @param Request $request
*
* @Method({"GET"})
* @Route("/persist")
*
* @return Response
*/
public function persistAction(Request $request)
{
$key = $request->query->get('key');

$result = null;

try {
if ($key) {
$result = ['key' => $key, 'persist' => $this->redisHelper->persist($key)];
}
} catch (RedisException $e) {
$result = $e->getMessage();
}

return new Response(json_encode($result));
}

/**
* @param Request $request
*
* @Method({"GET"})
* @Route("/expire")
*
* @return Response
*/
public function expireAction(Request $request)
{
$key = $request->query->get('key');
$ttl = $request->query->get('ttl');

$result = null;

try {
if ($key) {
$result = ['key' => $key, 'expire' => $this->redisHelper->expire($key, $ttl)];
}
} catch (RedisException $e) {
$result = $e->getMessage();
}

return new Response(json_encode($result));
}

/**
* @param Request $request
*
* @Method({"GET"})
* @Route("/delete")
*
* @return Response
*/
public function deleteAction(Request $request)
{
$key = $request->query->get('key');

$result = null;

try {
if ($key) {
$result = ['key' => $key, 'expire' => $this->redisHelper->delete($key)];
}
} catch (RedisException $e) {
$result = $e->getMessage();
}

return new Response(json_encode($result));
}
}

RedisHelper


Read comments in annotations!


namespace AppBundle\Util;

use Redis;

class RedisHelper
{
const MIN_TTL = 1;
const MAX_TTL = 3600;

/** @var Redis $redis */
private $redis;
private $host;
private $port;

public function __construct($host, $port)
{
$this->host = $host;
$this->port = $port;
}

/**
* Get the value related to the specified key.
*/
public function get($key)
{
$this->connect();

return $this->redis->get($key);
}

/**
* set(): Set persistent key-value pair.
* setex(): Set non-persistent key-value pair.
*/
public function set($key, $value, $ttl = null)
{
$this->connect();

if (is_null($ttl)) {
$this->redis->set($key, $value);
} else {
$this->redis->setex($key, $this->normaliseTtl($ttl), $value);
}
}

/**
* Returns 1 if the timeout was set.
* Returns 0 if key does not exist or the timeout could not be set.
*/
public function expire($key, $ttl = self::MIN_TTL)
{
$this->connect();

return $this->redis->expire($key, $this->normaliseTtl($ttl));
}

/**
* Removes the specified keys. A key is ignored if it does not exist.
* Returns the number of keys that were removed.
*/
public function delete($key)
{
$this->connect();

return $this->redis->del($key);
}

/**
* Returns -2 if the key does not exist.
* Returns -1 if the key exists but has no associated expire. Persistent.
*/
public function getTtl($key)
{
$this->connect();

return $this->redis->ttl($key);
}

/**
* Returns 1 if the timeout was removed.
* Returns 0 if key does not exist or does not have an associated timeout.
*/
public function persist($key)
{
$this->connect();

return $this->redis->persist($key);
}

/**
* The ttl is normalised to be 1 second to 1 hour.
*/
private function normaliseTtl($ttl)
{
$ttl = ceil(abs($ttl));

return ($ttl >= self::MIN_TTL && $ttl <= self::MAX_TTL) ? $ttl : self::MAX_TTL;
}

/**
* Connect only if not connected.
*/
private function connect()
{
if (!$this->redis || $this->redis->ping() != '+PONG') {
$this->redis = new Redis();
$this->redis->connect($this->host, $this->port);
}
}
}