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:

  • You are making complex calculations that every time return the same result (changing infrequently)
  • You are using a lot of helpers and the output generated is almost always the same
  • You are accessing database data constantly and these data rarely change

NOTE Even after implementing the cache, you should check the hit ratio of your cache over a period of time. This can easily be done, especially in the case of Memcache or Apc, with the relevant tools that the backends provide.

Caching Behavior

The caching process is divided into 2 parts:

  • Frontend: This part is responsible for checking if a key has expired and perform additional transformations to the data before storing and after retrieving them from the backend-
  • Backend: This part is responsible for communicating, writing/reading the data required by the frontend.

Factory

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

Traditional way

  1. <?php
  2. use Phalcon\Cache\Backend\File as BackFile;
  3. use Phalcon\Cache\Frontend\Data as FrontData;
  4. // Create an Output frontend. Cache the files for 2 days
  5. $frontCache = new FrontData(
  6. [
  7. 'lifetime' => 172800,
  8. ]
  9. );
  10. // Create the component that will cache from the 'Output' to a 'File' backend
  11. // Set the cache file directory - it's important to keep the '/' at the end of
  12. // the value for the folder
  13. $cache = new BackFile(
  14. $frontCache,
  15. [
  16. 'cacheDir' => '../app/cache/',
  17. ]
  18. );

or using the Factory object as follows:

  1. <?php
  2. use Phalcon\Cache\Frontend\Factory as FFactory;
  3. use Phalcon\Cache\Backend\Factory as BFactory;
  4. $options = [
  5. 'lifetime' => 172800,
  6. 'adapter' => 'data',
  7. ];
  8. $frontendCache = FFactory::load($options);
  9. $options = [
  10. 'cacheDir' => '../app/cache/',
  11. 'prefix' => 'app-data',
  12. 'frontend' => $frontendCache,
  13. 'adapter' => 'file',
  14. ];
  15. $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 capturedfrom 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.

  1. <?php
  2. use Phalcon\Tag;
  3. use Phalcon\Cache\Backend\File as BackFile;
  4. use Phalcon\Cache\Frontend\Output as FrontOutput;
  5. // Create an Output frontend. Cache the files for 2 days
  6. $frontCache = new FrontOutput(
  7. [
  8. 'lifetime' => 172800,
  9. ]
  10. );
  11. // Create the component that will cache from the 'Output' to a 'File' backend
  12. // Set the cache file directory - it's important to keep the '/' at the end of
  13. // the value for the folder
  14. $cache = new BackFile(
  15. $frontCache,
  16. [
  17. 'cacheDir' => '../app/cache/',
  18. ]
  19. );
  20. // Get/Set the cache file to ../app/cache/my-cache.html
  21. $content = $cache->start('my-cache.html');
  22. // If $content is null then the content will be generated for the cache
  23. if ($content === null) {
  24. // Print date and time
  25. echo date('r');
  26. // Generate a link to the sign-up action
  27. echo Tag::linkTo(
  28. [
  29. 'user/signup',
  30. 'Sign Up',
  31. 'class' => 'signup-button',
  32. ]
  33. );
  34. // Store the output into the cache file
  35. $cache->save();
  36. } else {
  37. // Echo the cached output
  38. echo $content;
  39. }

NOTE In the example above, our code remains the same, echoing output to the user as it has been doing before. Our cache component transparently captures that output and stores it in the cache file (when the cache is generated) or it sends it back to the user pre-compiled from a previous call, thus avoiding expensive operations.

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.

  1. <?php
  2. use Phalcon\Cache\Backend\File as BackFile;
  3. use Phalcon\Cache\Frontend\Data as FrontData;
  4. // Cache the files for 2 days using a Data frontend
  5. $frontCache = new FrontData(
  6. [
  7. 'lifetime' => 172800,
  8. ]
  9. );
  10. // Create the component that will cache 'Data' to a 'File' backend
  11. // Set the cache file directory - important to keep the `/` at the end of
  12. // the value for the folder
  13. $cache = new BackFile(
  14. $frontCache,
  15. [
  16. 'cacheDir' => '../app/cache/',
  17. ]
  18. );
  19. $cacheKey = 'robots_order_id.cache';
  20. // Try to get cached records
  21. $robots = $cache->get($cacheKey);
  22. if ($robots === null) {
  23. // $robots is null because of cache expiration or data does not exist
  24. // Make the database call and populate the variable
  25. $robots = Robots::find(
  26. [
  27. 'order' => 'id',
  28. ]
  29. );
  30. // Store it in the cache
  31. $cache->save($cacheKey, $robots);
  32. }
  33. // Use $robots :)
  34. foreach ($robots as $robot) {
  35. echo $robot->name, '\n';
  36. }

Memcached Backend Example

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

  1. <?php
  2. use Phalcon\Cache\Frontend\Data as FrontData;
  3. use Phalcon\Cache\Backend\Libmemcached as BackMemCached;
  4. // Cache data for one hour
  5. $frontCache = new FrontData(
  6. [
  7. 'lifetime' => 3600,
  8. ]
  9. );
  10. // Create the component that will cache 'Data' to a 'Memcached' backend
  11. // Memcached connection settings
  12. $cache = new BackMemCached(
  13. $frontCache,
  14. [
  15. 'servers' => [
  16. [
  17. 'host' => '127.0.0.1',
  18. 'port' => '11211',
  19. 'weight' => '1',
  20. ]
  21. ]
  22. ]
  23. );
  24. $cacheKey = 'robots_order_id.cache';
  25. // Try to get cached records
  26. $robots = $cache->get($cacheKey);
  27. if ($robots === null) {
  28. // $robots is null because of cache expiration or data does not exist
  29. // Make the database call and populate the variable
  30. $robots = Robots::find(
  31. [
  32. 'order' => 'id',
  33. ]
  34. );
  35. // Store it in the cache
  36. $cache->save($cacheKey, $robots);
  37. }
  38. // Use $robots :)
  39. foreach ($robots as $robot) {
  40. echo $robot->name, '\n';
  41. }

NOTE Calling save() will return a boolean, indicating success (true) or failure (false). Depending on the backend that you use, you will need to look at the relevant logs to identify failures.

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.

  1. <?php
  2. // Retrieve products by key 'myProducts'
  3. $products = $cache->get('myProducts');

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

  1. <?php
  2. // Query all keys used in the cache
  3. $keys = $cache->queryKeys();
  4. foreach ($keys as $key) {
  5. $data = $cache->get($key);
  6. echo 'Key=', $key, ' Data=', $data;
  7. }
  8. // Query keys in the cache that begins with 'my-prefix'
  9. $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.

  1. <?php
  2. // Delete an item with a specific key
  3. $cache->delete('someKey');
  4. $keys = $cache->queryKeys();
  5. // Delete all items from the cache
  6. foreach ($keys as $key) {
  7. $cache->delete($key);
  8. }

Checking cache existence

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

  1. <?php
  2. if ($cache->exists('someKey')) {
  3. echo $cache->get('someKey');
  4. } else {
  5. echo 'Cache does not exists!';
  6. }

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:

  1. <?php
  2. $cacheKey = 'my.cache';
  3. // Setting the cache when getting a result
  4. $robots = $cache->get($cacheKey, 3600);
  5. if ($robots === null) {
  6. $robots = 'some robots';
  7. // Store it in the cache
  8. $cache->save($cacheKey, $robots);
  9. }

Setting the lifetime when saving:

  1. <?php
  2. $cacheKey = 'my.cache';
  3. $robots = $cache->get($cacheKey);
  4. if ($robots === null) {
  5. $robots = 'some robots';
  6. // Setting the cache when saving data
  7. $cache->save($cacheKey, $robots, 3600);
  8. }

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:

  1. <?php
  2. use Phalcon\Cache\Multiple;
  3. use Phalcon\Cache\Backend\Apc as ApcCache;
  4. use Phalcon\Cache\Backend\File as FileCache;
  5. use Phalcon\Cache\Frontend\Data as DataFrontend;
  6. use Phalcon\Cache\Backend\Memcache as MemcacheCache;
  7. $ultraFastFrontend = new DataFrontend(
  8. [
  9. 'lifetime' => 3600,
  10. ]
  11. );
  12. $fastFrontend = new DataFrontend(
  13. [
  14. 'lifetime' => 86400,
  15. ]
  16. );
  17. $slowFrontend = new DataFrontend(
  18. [
  19. 'lifetime' => 604800,
  20. ]
  21. );
  22. // Backends are registered from the fastest to the slower
  23. $cache = new Multiple(
  24. [
  25. new ApcCache(
  26. $ultraFastFrontend,
  27. [
  28. 'prefix' => 'cache',
  29. ]
  30. ),
  31. new MemcacheCache(
  32. $fastFrontend,
  33. [
  34. 'prefix' => 'cache',
  35. 'host' => 'localhost',
  36. 'port' => '11211',
  37. ]
  38. ),
  39. new FileCache(
  40. $slowFrontend,
  41. [
  42. 'prefix' => 'cache',
  43. 'cacheDir' => '../app/cache/',
  44. ]
  45. ),
  46. ]
  47. );
  48. // Save, saves in every backend
  49. $cache->save('my-key', $data);

Frontend Adapters

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

AdapterDescription
Phalcon\Cache\Frontend\OutputRead input data from standard PHP output.
Phalcon\Cache\Frontend\DataIt’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\Base64It’s used to cache binary data. The data is serialized using base64_encode before be stored in the backend.
Phalcon\Cache\Frontend\JsonData 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\IgbinaryIt’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\NoneIt’s used to cache any kind of PHP data without serializing them.

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:

AdapterDescriptionInfoRequired Extensions
Phalcon\Cache\Backend\ApcStores data to the Alternative PHP Cache (APC).APCAPC
Phalcon\Cache\Backend\ApcuStores data to the APCu (APC without opcode caching)APCuAPCu
Phalcon\Cache\Backend\FileStores data to local plain files.
Phalcon\Cache\Backend\LibmemcachedStores data to a memcached server.MemcachedMemcached
Phalcon\Cache\Backend\MemcacheStores data to a memcached server.MemcacheMemcache
Phalcon\Cache\Backend\MemoryStores data in memory
Phalcon\Cache\Backend\MongoStores data to Mongo Database.MongoDBMongo
Phalcon\Cache\Backend\RedisStores data in Redis.RedisRedis
Phalcon\Cache\Backend\XcacheStores data in XCache.XCacheXCache
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 3.4.0 you can switch your \Apc classes to \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). The one you use will depend on the needs of your application. 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

  1. <?php
  2. use Phalcon\Cache\Backend\Factory;
  3. use Phalcon\Cache\Frontend\Data;
  4. $options = [
  5. 'prefix' => 'app-data',
  6. 'frontend' => new Data(),
  7. 'adapter' => 'apc',
  8. ];
  9. $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:

OptionDescription
prefixA prefix that is automatically prepended to the cache keys.
cacheDirA writable directory on which cached files will be placed.

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

OptionDescription
statsKeyUsed to tracking of cached keys.
prefixA prefix that is automatically prepended to the cache keys.
persistent_idTo create an instance that persists between requests, use persistent_id to specify a unique ID for the instance.

Servers options

OptionDescription
hostThe memcached host.
portThe memcached port.
weightThe weight parameter effects the consistent hashing used to determine which server to read/write keys from.

Client options

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

Example

  1. <?php
  2. use Phalcon\Cache\Backend\Libmemcached;
  3. use Phalcon\Cache\Frontend\Data as FrontData;
  4. // Cache data for 2 days
  5. $frontCache = new FrontData(
  6. [
  7. 'lifetime' => 172800,
  8. ]
  9. );
  10. // Create the Cache setting memcached connection options
  11. $cache = new Libmemcached(
  12. $frontCache,
  13. [
  14. 'servers' => [
  15. [
  16. 'host' => '127.0.0.1',
  17. 'port' => 11211,
  18. 'weight' => 1,
  19. ],
  20. ],
  21. 'client' => [
  22. \Memcached::OPT_HASH => \Memcached::HASH_MD5,
  23. \Memcached::OPT_PREFIX_KEY => 'prefix.',
  24. ],
  25. 'persistent_id' => 'my_app_cache',
  26. ]
  27. );

Memcache Backend Options

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

OptionDescription
prefixA prefix that is automatically prepended to the cache keys.
hostThe memcached host.
portThe memcached port.
persistentCreate a persistent connection to memcached?

APC Backend Options

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

OptionDescription
prefixA prefix that is automatically prepended to the cache keys.

APCU Backend Options

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

OptionDescription
prefixA prefix that is automatically prepended to the cache keys.

Mongo Backend Options

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

OptionDescription
prefixA prefix that is automatically prepended to the cache keys.
serverA MongoDB connection string.
dbMongo database name.
collectionMongo collection in the database.

XCache Backend Options

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

OptionDescription
prefixA prefix that is automatically prepended to the cache keys.

Redis Backend Options

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

OptionDescription
prefixA prefix that is automatically prepended to the cache keys.
hostRedis host.
portRedis port.
authPassword to authenticate to a password-protected Redis server.
persistentCreate a persistent connection to Redis.
indexThe index of the Redis database to use.

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