1# guzzle-cache-middleware
2
3[![Latest Stable Version](https://poser.pugx.org/kevinrob/guzzle-cache-middleware/v/stable)](https://packagist.org/packages/kevinrob/guzzle-cache-middleware) [![Total Downloads](https://poser.pugx.org/kevinrob/guzzle-cache-middleware/downloads)](https://packagist.org/packages/kevinrob/guzzle-cache-middleware) [![License](https://poser.pugx.org/kevinrob/guzzle-cache-middleware/license)](https://packagist.org/packages/kevinrob/guzzle-cache-middleware)
4[![Build Status](https://travis-ci.org/Kevinrob/guzzle-cache-middleware.svg?branch=master)](https://travis-ci.org/Kevinrob/guzzle-cache-middleware) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/Kevinrob/guzzle-cache-middleware/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/Kevinrob/guzzle-cache-middleware/?branch=master) [![SensioLabsInsight](https://insight.sensiolabs.com/projects/077ec9d6-9362-43be-83c9-cf1db2c9c802/mini.png)](https://insight.sensiolabs.com/projects/077ec9d6-9362-43be-83c9-cf1db2c9c802)
5[![Dependency Status](https://www.versioneye.com/php/kevinrob:guzzle-cache-middleware/badge.png)](https://www.versioneye.com/php/kevinrob:guzzle-cache-middleware) [![Code Coverage](https://scrutinizer-ci.com/g/Kevinrob/guzzle-cache-middleware/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/Kevinrob/guzzle-cache-middleware/?branch=master)
6
7
8A HTTP Cache for [Guzzle](https://github.com/guzzle/guzzle) 6. It's a simple Middleware to be added in the HandlerStack.
9
10## Goals
11- RFC 7234 compliance
12- Performance and transparency
13- Assured compatibility with PSR-7
14
15## Storage interfaces build-in
16- [Doctrine cache](https://github.com/doctrine/cache)
17- [Laravel cache](https://laravel.com/docs/5.2/cache)
18- [Flysystem](https://github.com/thephpleague/flysystem)
19- [PSR6](https://github.com/php-fig/cache)
20- [WordPress Object Cache](https://codex.wordpress.org/Class_Reference/WP_Object_Cache)
21
22## Installation
23
24`composer require kevinrob/guzzle-cache-middleware`
25
26or add it the your `composer.json` and make a `composer update kevinrob/guzzle-cache-middleware`.
27
28# Why?
29Performance. It's very common to do some HTTP calls to an API for rendering a page and it takes times to do it.
30
31# How?
32With a simple Middleware added at the top of the `HandlerStack` of Guzzle6.
33
34```php
35use GuzzleHttp\Client;
36use GuzzleHttp\HandlerStack;
37use Kevinrob\GuzzleCache\CacheMiddleware;
38
39// Create default HandlerStack
40$stack = HandlerStack::create();
41
42// Add this middleware to the top with `push`
43$stack->push(new CacheMiddleware(), 'cache');
44
45// Initialize the client with the handler option
46$client = new Client(['handler' => $stack]);
47```
48
49# Examples
50
51## Doctrine/Cache
52You can use a cache from `Doctrine/Cache`:
53```php
54[...]
55use Doctrine\Common\Cache\FilesystemCache;
56use Kevinrob\GuzzleCache\Strategy\PrivateCacheStrategy;
57use Kevinrob\GuzzleCache\Storage\DoctrineCacheStorage;
58
59[...]
60$stack->push(
61  new CacheMiddleware(
62    new PrivateCacheStrategy(
63      new DoctrineCacheStorage(
64        new FilesystemCache('/tmp/')
65      )
66    )
67  ),
68  'cache'
69);
70```
71
72You can use `ChainCache` for using multiple `CacheProvider`. With that provider, you have to sort the different cache from the faster to the slower. Like that, you can have a very fast cache.
73```php
74[...]
75use Doctrine\Common\Cache\ChainCache;
76use Doctrine\Common\Cache\ArrayCache;
77use Doctrine\Common\Cache\FilesystemCache;
78use Kevinrob\GuzzleCache\Strategy\PrivateCacheStrategy;
79use Kevinrob\GuzzleCache\Storage\DoctrineCacheStorage;
80
81[...]
82$stack->push(new CacheMiddleware(
83  new PrivateCacheStrategy(
84    new DoctrineCacheStorage(
85      new ChainCache([
86        new ArrayCache(),
87        new FilesystemCache('/tmp/'),
88      ])
89    )
90  )
91), 'cache');
92```
93
94## Laravel cache
95You can use a cache with Laravel, e.g. Redis, Memcache etc.:
96```php
97[...]
98use Illuminate\Support\Facades\Cache;
99use Kevinrob\GuzzleCache\Strategy\PrivateCacheStrategy;
100use Kevinrob\GuzzleCache\Storage\LaravelCacheStorage;
101
102[...]
103
104$stack->push(
105  new CacheMiddleware(
106    new PrivateCacheStrategy(
107      new LaravelCacheStorage(
108        Cache::store('redis')
109      )
110    )
111  ),
112  'cache'
113);
114```
115
116## Flysystem
117```php
118[...]
119use League\Flysystem\Adapter\Local;
120use Kevinrob\GuzzleCache\Strategy\PrivateCacheStrategy;
121use Kevinrob\GuzzleCache\Storage\FlysystemStorage;
122
123[...]
124
125$stack->push(
126  new CacheMiddleware(
127    new PrivateCacheStrategy(
128      new FlysystemStorage(
129        new Local('/path/to/cache')
130      )
131    )
132  ),
133  'cache'
134);
135```
136
137## WordPress Object Cache
138```php
139[...]
140use Kevinrob\GuzzleCache\Strategy\PrivateCacheStrategy;
141use Kevinrob\GuzzleCache\Storage\WordPressObjectCacheStorage;
142
143[...]
144
145$stack->push(
146  new CacheMiddleware(
147    new PrivateCacheStrategy(
148      new WordPressObjectCacheStorage()
149    )
150  ),
151  'cache'
152);
153```
154
155## Public and shared
156It's possible to add a public shared cache to the stack:
157```php
158[...]
159use Doctrine\Common\Cache\FilesystemCache;
160use Doctrine\Common\Cache\PredisCache;
161use Kevinrob\GuzzleCache\Strategy\PrivateCacheStrategy;
162use Kevinrob\GuzzleCache\Strategy\PublicCacheStrategy;
163use Kevinrob\GuzzleCache\Storage\DoctrineCacheStorage;
164
165[...]
166// Private caching
167$stack->push(
168  new CacheMiddleware(
169    new PrivateCacheStrategy(
170      new DoctrineCacheStorage(
171        new FilesystemCache('/tmp/')
172      )
173    )
174  ),
175  'private-cache'
176);
177
178// Public caching
179$stack->push(
180  new CacheMiddleware(
181    new PublicCacheStrategy(
182      new DoctrineCacheStorage(
183        new PredisCache(
184          new Predis\Client('tcp://10.0.0.1:6379')
185        )
186      )
187    )
188  ),
189  'shared-cache'
190);
191```
192
193## Greedy caching
194In some cases servers might send insufficient or no at all caching headers.
195Using the greedy caching strategy allows defining an expiry TTL on your own while
196disregarding any possibly present caching headers:
197```php
198[...]
199use Kevinrob\GuzzleCache\KeyValueHttpHeader;
200use Kevinrob\GuzzleCache\Strategy\GreedyCacheStrategy;
201use Kevinrob\GuzzleCache\Storage\DoctrineCacheStorage;
202use Doctrine\Common\Cache\FilesystemCache;
203
204[...]
205// Greedy caching
206$stack->push(
207  new CacheMiddleware(
208    new GreedyCacheStrategy(
209      new DoctrineCacheStorage(
210        new FilesystemCache('/tmp/')
211      ),
212      1800, // the TTL in seconds
213      new KeyValueHttpHeader(['Authorization']) // Optionnal - specify the headers that can change the cache key
214    )
215  ),
216  'greedy-cache'
217);
218```
219
220## Delegate caching
221Because your client may call different apps, on different domains, you may need to define which strategy is suitable to your requests.
222
223To solve this, all you have to do is to define a default cache strategy, and override it by implementing your own Request Matchers.
224
225Here's an example:
226```php
227namespace App\RequestMatcher;
228
229use Kevinrob\GuzzleCache\Strategy\Delegate\RequestMatcherInterface;
230use Psr\Http\Message\RequestInterface;
231
232class ExampleOrgRequestMatcher implements RequestMatcherInterface
233{
234
235    /**
236     * @inheritDoc
237     */
238    public function matches(RequestInterface $request)
239    {
240        return false !== strpos($request->getUri()->getHost(), 'example.org');
241    }
242}
243```
244
245```php
246namespace App\RequestMatcher;
247
248use Kevinrob\GuzzleCache\Strategy\Delegate\RequestMatcherInterface;
249use Psr\Http\Message\RequestInterface;
250
251class TwitterRequestMatcher implements RequestMatcherInterface
252{
253
254    /**
255     * @inheritDoc
256     */
257    public function matches(RequestInterface $request)
258    {
259        return false !== strpos($request->getUri()->getHost(), 'twitter.com');
260    }
261}
262```
263
264```php
265require_once __DIR__ . '/vendor/autoload.php';
266
267use App\RequestMatcher\ExampleOrgRequestMatcher;
268use App\RequestMatcher\TwitterRequestMatcher;
269use GuzzleHttp\Client;
270use GuzzleHttp\HandlerStack;
271use Kevinrob\GuzzleCache\CacheMiddleware;
272use Kevinrob\GuzzleCache\Strategy;
273
274$strategy = new Strategy\Delegate\DelegatingCacheStrategy($defaultStrategy = new Strategy\NullCacheStrategy());
275$strategy->registerRequestMatcher(new ExampleOrgRequestMatcher(), new Strategy\PublicCacheStrategy());
276$strategy->registerRequestMatcher(new TwitterRequestMatcher(), new Strategy\PrivateCacheStrategy());
277
278$stack = HandlerStack::create();
279$stack->push(new CacheMiddleware($strategy));
280$guzzle = new Client(['handler' => $stack]);
281```
282
283With this example:
284* All requests to `example.org` will be handled by `PublicCacheStrategy`
285* All requests to `twitter.com` will be handled by `PrivateCacheStrategy`
286* All other requests won't be cached.