A cache component is always useful both as in server and the client side. Sometimes when you work in the server side with PHP and using the Symfony Framework, you will need to request information from another server in your own server. Guzzle, the extensible PHP HTTP client is one of the most used libraries for requesting information from web APIs using PHP, because it makes easy to send HTTP requests and trivial to integrate with web services.
If the web service that you're trying to access, requires you to implement a cache, due to the limitation of requests, you can always implement a cache for the Guzzle Client, so you won't never get the 429 Too Many Requests exception in your code. This can be done, as mentioned, because the API you are using requires it or just because you want to speed up the requests in your server (as usually a service should already implement a cache). In this article, you will learn how to implement a file system based cache for Guzzle following the PSR-6 Interface. The goal of this PSR is to allow developers to create cache-aware libraries that can be integrated into existing frameworks and systems without the need for custom development.
How the file system cache works?
By using the Cached Client of Guzzle that we are going to implement using the filesystem instead of a plain client, a new folder will be created in the cache directory of your symfony project (in this case the name of our new folder will be GuzzleFileCache
, so the folder will be located at yoursymfonyproject/var/cache/GuzzleFileCache
). The Client can be used as any normal Guzzle Http client.
Important
Don't forget to give properly the permissions to write into the folder (at least in production environment in case you're deploying to your server).
Implementation
To implement a file based cache client, you can follow the 2 simple steps:
1. Install Cache Libraries
Besides of Guzzle (obviously) you will need to install the Symfony Cache Component and the Guzzle Cache Middleware libraries. The Cache Component of Symfony provides an extended PSR-6 implementation as well as a PSR-16 "Simple Cache" implementation for adding cache to your applications. It is designed to have a low overhead and it ships with ready to use adapters for the most common caching backends. The Cache Middleware of Guzzle augments the functionality of handlers by invoking them in the process of generating responses.
To install the required libraries, run the following composer commands in the directory of your project with your terminal:
REM Install the Cache Component of Symfony:
composer require symfony/cache
REM And install the Cache Middleware for Guzzle:
composer require kevinrob/guzzle-cache-middleware
After the installation of the 2 libraries you will be able to use their classes in your own controller (or services).
2. Creating a cached client instance
The usage of the client should be the same as always, the thing that changes is the process to create and instantiate the client. We are going to make the logic simple by wrapping everything into a single function namely getGuzzleFileCachedClient
that returns an instance of a HTTP client that implements a file based cache.
To get started, you need to know which classes of the libraries are going to be used:
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use Kevinrob\GuzzleCache\CacheMiddleware;
use Kevinrob\GuzzleCache\Storage\Psr6CacheStorage;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Kevinrob\GuzzleCache\Strategy\PrivateCacheStrategy;
use Kevinrob\GuzzleCache\Strategy\GreedyCacheStrategy;
The Cache Storage that we'll use will be the File System with PSR-6, so you need to create an instance of it as first that receives as first argument an instance of the FilesystemAdapter. This instance of FilesystemAdapter specifies the TTL of the cache, its container name and absolute path. The TTL (or time to live) is the amount of time between when that item is stored and it is considered stale. The TTL is normally defined by an integer representing time in seconds, or a DateInterval
object. This time is providen in the FilesystemAdapter and the GreedyCacheStrategy.
Then, you can add the Cache Method to Guzzle using the push method of an empty HandlerStack that receives as first argument an instance of the Cache Middleware and as second argument the greedy-cache method, that in turns receives as first argument an instance of the GreedyCacheStrategy. This last instance receives as first argument the created instance of the File Cache Storage and the TTL:
<?php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
// Required Classes to create the client
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use Kevinrob\GuzzleCache\CacheMiddleware;
use Kevinrob\GuzzleCache\Storage\Psr6CacheStorage;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Kevinrob\GuzzleCache\Strategy\PrivateCacheStrategy;
use Kevinrob\GuzzleCache\Strategy\GreedyCacheStrategy;
class DefaultController extends Controller
{
/**
* @Route("/", name="homepage")
*/
public function indexAction()
{
// Create a Custom Cached Guzzle Client
$client = $this->getGuzzleFileCachedClient();
// Use the client as you want and need, for example:
$res = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle');
dump($res->getStatusCode());
}
/**
* Returns a GuzzleClient that uses a file based cache manager
*
* @return Guzzle Http Client
*/
private function getGuzzleFileCachedClient(){
// Create a HandlerStack
$stack = HandlerStack::create();
// 10 minutes to keep the cache
// This value will obviously change as you need
$TTL = 600;
// Create Folder GuzzleFileCache inside the providen cache folder path
$requestCacheFolderName = 'GuzzleFileCache';
// Retrieve the cache folder path of your Symfony Project
$cacheFolderPath = $this->get('kernel')->getRootDir() . '/../var/cache';
// Instantiate the cache storage: a PSR-6 file system cache with
// a default lifetime of 10 minutes (60 seconds).
$cache_storage = new Psr6CacheStorage(
new FilesystemAdapter(
$requestCacheFolderName,
$TTL,
$cacheFolderPath
)
);
// Add Cache Method
$stack->push(
new CacheMiddleware(
new GreedyCacheStrategy(
$cache_storage,
$TTL // the TTL in seconds
)
),
'greedy-cache'
);
// Initialize the client with the handler option and return it
return new Client(['handler' => $stack]);
}
}
Obviously, to keep your controllers so thin as possible, you can create a new symfony service to inject the cached client too. The usage of the client, will create a folder in the var/cache
folder of your project with the given name:
It will contain (once you execute some requests) your cached requests in PSR-6. As every cache, removing the content of the folder (or the folder itself) will clean the cache or it will clean itself when the TTL expires.
Happy coding !