This article reflects v3.4 and has not yet been revised

Improving Performance with Cache

Phalcon provides the Phalcon\Cache class allowing faster access to frequently used or already processed data. Phalcon\Cache is written in C, achieving higher performance and reducing the overhead when getting items from the backends. This class uses an internal structure of frontend and backend components. Front-end components act as input sources or interfaces, while backend components offer storage options to the class.

When to implement cache?

Although this component is very fast, implementing it in cases that are not needed could lead to a loss of performance rather than gain. We recommend you check this cases before using a cache:

  • Usted está haciendo cálculos complejos que siempre devuelven el mismo resultado (cambian con poca frecuencia)
  • Utiliza un montón de ayudantes y la salida generada es casi siempre la misma
  • Se accede a datos de la base de datos constantemente y rara vez cambian de estos datos

Nota Incluso después de implementar el caché, debe comprobar la proporción de aciertos de su caché durante un período de tiempo. Esto puede hacerse fácilmente, especialmente en el caso de Memcache o Apc, con las herramientas pertinentes que proporcionan los backends.

Caching Behavior

The caching process is divided into 2 parts:

  • Frontend: esta parte es responsable de comprobar si una clave ha expirado y realizar transformaciones adicionales a los datos antes de guardarlos y después recuperarlos desde el backend
  • Backend: esta parte es responsable de comunicar, escribir y leer los datos requeridos por la interfaz.

Factory

Instantiating frontend or backend adapters can be achieved by two ways:

Traditional way

<?php

use Phalcon\Cache\Backend\File as BackFile;
use Phalcon\Cache\Frontend\Data as FrontData;

// Crea una salida frontend. Cachear archivos por 2 días
$frontCache = new FrontData(
    [
        'lifetime' => 172800,
    ]
);

// Crear el componente que realiza el cache desde la salida 'Output' a un archivo 'File' de backend
// Establece el directorio del archivo de cache - es importante mantener la '/' al final 
// del valor 'cacheDir'
$cache = new BackFile(
    $frontCache,
    [
        'cacheDir' => '../app/cache/',
    ]
);

or using the Factory object as follows:

<?php

use Phalcon\Cache\Frontend\Factory as FFactory;
use Phalcon\Cache\Backend\Factory as BFactory;

 $options = [
     'lifetime' => 172800,
     'adapter'  => 'data',
 ];
 $frontendCache = FFactory::load($options);


$options = [
    'cacheDir' => '../app/cache/',
    'prefix'   => 'app-data',
    'frontend' => $frontendCache,
    'adapter'  => 'file',
];

$backendCache = BFactory::load($options);

Caching Output Fragments

An output fragment is a piece of HTML or text that is cached as is and returned as is. The output is automatically captured from the ob_* functions or the PHP output so that it can be saved in the cache. The following example demonstrates such usage. It receives the output generated by PHP and stores it into a file. The contents of the file are refreshed every 172,800 seconds (2 days).

The implementation of this caching mechanism allows us to gain performance by not executing the helper Phalcon\Tag::linkTo() call whenever this piece of code is called.

<?php

use Phalcon\Tag;
use Phalcon\Cache\Backend\File as BackFile;
use Phalcon\Cache\Frontend\Output as FrontOutput;

// Crear una salida frontend. Cachear estos archivos por 2 días
$frontCache = new FrontOutput(
    [
        'lifetime' => 172800,
    ]
);

// Crear el componenta que almacena la salida 'Output' en un archivo 'File' en el backend
// Establecer el directorio de cacheo - es importante mantener la barra '/' al final 
// del valor cacheDir
$cache = new BackFile(
    $frontCache,
    [
        'cacheDir' => '../app/cache/',
    ]
);

// Obtener/establecer el archivo de cache en ../app/cache/my-cache.html
$content = $cache->start('my-cache.html');

// Si $content es idéntico a null entonces el contenido debe ser generado para el cache
if ($content === null) {
    // Imprimir fecha y hora
    echo date('r');

    // Generar un link a la acción de registro
    echo Tag::linkTo(
        [
            'user/signup',
            'Registrarme',
            'class' => 'signup-button',
        ]
    );

    // Almacenar la salida en el archivo de cache
    $cache->save();
} else {
    // Imprimir la salida cacheada
    echo $content;
}

Nota En el ejemplo anterior, nuestro código sigue siendo el mismo, haciéndose eco de salida para el usuario como lo ha venido haciendo antes. Nuestro componente de caché captura transparentemente esa salida y almacena en el archivo de caché (cuando se genera la caché) o envía hacia el usuario previamente compilado de una llamada anterior, evitando así operaciones costosas.

Caching Arbitrary Data

Caching just data is equally important for your application. Caching can reduce database load by reusing commonly used (but not updated) data, thus speeding up your application.

File Backend Example

One of the caching adapters is File. The only key area for this adapter is the location of where the cache files will be stored. This is controlled by the cacheDir option which must have a backslash at the end of it.

<?php

use Phalcon\Cache\Backend\File as BackFile;
use Phalcon\Cache\Frontend\Data as FrontData;

// Los archivos del cache se almacenan por 2 días usando el frontend Data
$frontCache = new FrontData(
    [
        'lifetime' => 172800,
    ]
);

// Crear el componente de cache para 'Data' con destino en 'File' backend
// Establecer el directorio de cacheo - Es importante mantener la barra `/` al final
// del valor cacheDir
$cache = new BackFile(
    $frontCache,
    [
        'cacheDir' => '../app/cache/',
    ]
);

$cacheKey = 'robots_order_id.cache';

// Intentar obtener los registros almacenados
$robots = $cache->get($cacheKey);

if ($robots === null) {
    // $robots es nulo debido a que el cache expiro o no existía
    // Hacer una llamada a la base de datos para completar la variable
    $robots = Robots::find(
        [
            'order' => 'id',
        ]
    );

    // Almacenarlo en el cache
    $cache->save($cacheKey, $robots);
}

// Usar los $robots :)
foreach ($robots as $robot) {
   echo $robot->name, '\n';
}

Memcached Backend Example

The above example changes slightly (especially in terms of configuration) when we are using a Memcached backend.

<?php

use Phalcon\Cache\Frontend\Data as FrontData;
use Phalcon\Cache\Backend\Libmemcached as BackMemCached;

// Cachear datos por una hora
$frontCache = new FrontData(
    [
        'lifetime' => 3600,
    ]
);

// Crear el componente que almacenará 'Data' al backend 'Memcached'
// Configuración de conexión de Memcached
$cache = new BackMemCached(
    $frontCache,
    [
        'servers' => [
            [
                'host'   => '127.0.0.1',
                'port'   => '11211',
                'weight' => '1',
            ]
        ]
    ]
);

$cacheKey = 'robots_order_id.cache';

// Intentar obtener registros almacenados
$robots = $cache->get($cacheKey);

if ($robots === null) {
    // $robots es nulo porque el cache expiro o no existía
    // Hacer la llamada a base de datos para generar la variable
    $robots = Robots::find(
        [
            'order' => 'id',
        ]
    );

    // Almacenar en base de datos
    $cache->save($cacheKey, $robots);
}

// Usar $robots :)
foreach ($robots as $robot) {
   echo $robot->name, '\n';
}

Nota Llamar a save() regresará un booleano, indicando éxito (true) o fracaso (false). Dependiendo del servidor backend que utilices, necesitaras buscar en los registros pertinentes para identificar fallas.

Querying the cache

The elements added to the cache are uniquely identified by a key. In the case of the File backend, the key is the actual filename. To retrieve data from the cache, we just have to call it using the unique key. If the key does not exist, the get method will return null.

<?php

// Recuperar productos con la clave 'myProducts'
$products = $cache->get('myProducts');

If you want to know which keys are stored in the cache you could call the queryKeys method:

<?php

// Consultar todas las claves utilizadas en el cache
$keys = $cache->queryKeys();

foreach ($keys as $key) {
    $data = $cache->get($key);

    echo 'Clave=', $key, ' Datos=', $data;
}

// Consultar todas las claves en el cache que comienzan con 'my-prefix'
$keys = $cache->queryKeys('my-prefix');

Deleting data from the cache

There are times where you will need to forcibly invalidate a cache entry (due to an update in the cached data). The only requirement is to know the key that the data have been stored with.

<?php

// Borrar un item con una clave específica
$cache->delete('someKey');

$keys = $cache->queryKeys();

// Borrar todos los items del cache
foreach ($keys as $key) {
    $cache->delete($key);
}

Checking cache existence

It is possible to check if a cache already exists with a given key:

<?php

if ($cache->exists('someKey')) {
    echo $cache->get('someKey');
} else {
    echo '¡El cache no existe!';
}

Lifetime

A lifetime is a time in seconds that a cache could live without expire. By default, all the created caches use the lifetime set in the frontend creation. You can set a specific lifetime in the creation or retrieving of the data from the cache:

Setting the lifetime when retrieving:

<?php

$cacheKey = 'my.cache';

// Establecer el cache cuando se obtienen resultados
$robots = $cache->get($cacheKey, 3600);

if ($robots === null) {
    $robots = 'some robots';

    // Almacenarlo en cache
    $cache->save($cacheKey, $robots);
}

Setting the lifetime when saving:

<?php

$cacheKey = 'my.cache';

$robots = $cache->get($cacheKey);

if ($robots === null) {
    $robots = 'some robots';

    // Establecer el cache cuando se guardan datos
    $cache->save($cacheKey, $robots, 3600);
}

Multi-Level Cache

This feature of the cache component, allows the developer to implement a multi-level cache. This new feature is very useful because you can save the same data in several cache locations with different lifetimes, reading first from the one with the faster adapter and ending with the slowest one until the data expires:

<?php

use Phalcon\Cache\Multiple;
use Phalcon\Cache\Backend\Apc as ApcCache;
use Phalcon\Cache\Backend\File as FileCache;
use Phalcon\Cache\Frontend\Data as DataFrontend;
use Phalcon\Cache\Backend\Memcache as MemcacheCache;

$ultraFastFrontend = new DataFrontend(
    [
        'lifetime' => 3600,
    ]
);

$fastFrontend = new DataFrontend(
    [
        'lifetime' => 86400,
    ]
);

$slowFrontend = new DataFrontend(
    [
        'lifetime' => 604800,
    ]
);

// Los backends se registran del más rápido al más lento
$cache = new Multiple(
    [
        new ApcCache(
            $ultraFastFrontend,
            [
                'prefix' => 'cache',
            ]
        ),
        new MemcacheCache(
            $fastFrontend,
            [
                'prefix' => 'cache',
                'host'   => 'localhost',
                'port'   => '11211',
            ]
        ),
        new FileCache(
            $slowFrontend,
            [
                'prefix'   => 'cache',
                'cacheDir' => '../app/cache/',
            ]
        ),
    ]
);

// save(), guarda en cada backend
$cache->save('my-key', $data);

Frontend Adapters

The available frontend adapters that are used as interfaces or input sources to the cache are:

Adaptador Descripción
Phalcon\Cache\Frontend\Output Lee los datos de entrada desde la salida estándar de PHP.
Phalcon\Cache\Frontend\Data It’s used to cache any kind of PHP data (big arrays, objects, text, etc). Data is serialized before stored in the backend.
Phalcon\Cache\Frontend\Base64 It’s used to cache binary data. The data is serialized using base64_encode before be stored in the backend.
Phalcon\Cache\Frontend\Json Data is encoded in JSON before be stored in the backend. Decoded after be retrieved. This frontend is useful to share data with other languages or frameworks.
Phalcon\Cache\Frontend\Igbinary It’s used to cache any kind of PHP data (big arrays, objects, text, etc). Data is serialized using Igbinary before be stored in the backend.
Phalcon\Cache\Frontend\None Se usa para almacenar en caché cualquier tipo de datos PHP sin serializarlos.

Implementing your own Frontend adapters

The Phalcon\Cache\FrontendInterface interface must be implemented in order to create your own frontend adapters or extend the existing ones.

Backend Adapters

The backend adapters available to store cache data are:

Adaptador Descripción Info Extensiones Requeridas
Phalcon\Cache\Backend\Apc Almacena los datos a la Caché Alternativa de PHP (APC). APC APC
Phalcon\Cache\Backend\Apcu Almacena los datos en la APCu (APC sin almacenamiento en caché de opcode) APCu APCu
Phalcon\Cache\Backend\File Almacena los datos en archivos planos locales.    
Phalcon\Cache\Backend\Libmemcached Almacena los datos en un servidor memcached. Memcached Memcached
Phalcon\Cache\Backend\Memcache Almacena los datos en un servidor memcached. Memcache Memcache
Phalcon\Cache\Backend\Memory Stores data in memory    
Phalcon\Cache\Backend\Mongo Stores data to Mongo Database. MongoDB Mongo
Phalcon\Cache\Backend\Redis Stores data in Redis. Redis Redis
Phalcon\Cache\Backend\Xcache Stores data in XCache. XCache XCache
NOTE In PHP 7 to use phalcon apc based adapter classes you needed to install apcu and apcu_bc package from pecl. Now in Phalcon 4.0.0 you can switch your <em>\Apc classes to </em>\Apcu and remove apcu_bc. Keep in mind that in Phalcon 4 we will most likely remove all *\Apc classes. {.alert.alert-warning}

Factory

There are many backend adapters (see Backend Adapters). El que desee utilizar dependerá de las necesidades de su aplicación. The following example loads the Backend Cache Adapter class using adapter option, if frontend will be provided as array it will call Frontend Cache Factory

<?php

use Phalcon\Cache\Backend\Factory;
use Phalcon\Cache\Frontend\Data;

$options = [
    'prefix'   => 'app-data',
    'frontend' => new Data(),
    'adapter'  => 'apc',
];
$backendCache = Factory::load($options);

Implementing your own Backend adapters

The Phalcon\Cache\BackendInterface interface must be implemented in order to create your own backend adapters or extend the existing ones.

File Backend Options

This backend will store cached content into files in the local server. The available options for this backend are:

Opción Descripción
prefix Un prefijo se antepone automáticamente en las claves de caché.
cacheDir Un directorio con permisos para escribir en la que se colocarán los archivos cacheados.

Libmemcached Backend Options

This backend will store cached content on a memcached server. Per default persistent memcached connection pools are used. The available options for this backend are:

General options

Opción Descripción
statsKey Utilizado para seguimiento de claves almacenado en memoria caché.
prefix Un prefijo se antepone automáticamente en las claves de caché.
persistent_id Para crear una instancia que persiste entre las solicitudes, utilice persistent_id para especificar un ID único para la instancia.

Servers options

Opción Descripción
host El servidor memcached.
port El puerto memcached.
weight El parámetro de carga tiene el efecto hash consistente utilizado para determinar desde qué servidor leer/escribir claves.

Client options

Used for setting Memcached options. See Memcached::setOptions for more.

Ejemplo

<?php
use Phalcon\Cache\Backend\Libmemcached;
use Phalcon\Cache\Frontend\Data as FrontData;

// Cachear los datos por 2 días
$frontCache = new FrontData(
    [
        'lifetime' => 172800,
    ]
);

// Creamos las opciones de conexión para memcached
$cache = new Libmemcached(
    $frontCache,
    [
        'servers' => [
            [
                'host'   => '127.0.0.1',
                'port'   => 11211,
                'weight' => 1,
            ],
        ],
        'client' => [
            \Memcached::OPT_HASH       => \Memcached::HASH_MD5,
            \Memcached::OPT_PREFIX_KEY => 'prefix.',
        ],
        'persistent_id' => 'my_app_cache',
    ]
);

Memcache Backend Options

This backend will store cached content on a memcached server. The available options for this backend are:

Opción Descripción
prefix Un prefijo se antepone automáticamente en las claves de caché.
host El servidor memcached.
port El puerto memcached.
persistent ¿Crear una conexión persistente a memcached?

APC Backend Options

This backend will store cached content on Alternative PHP Cache (APC). The available options for this backend are:

Opción Descripción
prefix Un prefijo se antepone automáticamente en las claves de caché.

APCU Backend Options

This backend will store cached content on Alternative PHP Cache (APCU). The available options for this backend are:

Opción Descripción
prefix Un prefijo se antepone automáticamente en las claves de caché.

Mongo Backend Options

This backend will store cached content on a MongoDB server (MongoDB). The available options for this backend are:

Opción Descripción
prefix Un prefijo se antepone automáticamente en las claves de caché.
server Una cadena de conexión de MongoDB.
db Nombre de la base de datos de Mongo.
collection Colección de Mongo en la base de datos.

XCache Backend Options

This backend will store cached content on XCache (XCache). The available options for this backend are:

Opción Descripción
prefix Un prefijo se antepone automáticamente en las claves de caché.

Redis Backend Options

This backend will store cached content on a Redis server (Redis). The available options for this backend are:

Opción Descripción
prefix Un prefijo se antepone automáticamente en las claves de caché.
host Servidor Redis.
port Puerto de Redis.
auth Contraseña para autenticarse en un servidor Redis con protección.
persistent Crear una conexión persistente a Redis.
index El índice a utilizar en la base de datos de Redis.

There are more adapters available for this components in the Phalcon Incubator