Skip to content

Commit 910a807

Browse files
committed
Merge pull request #20 from KnpLabs/since
Use If-modified-since headers to avoid X-Rate-Limit decrease
2 parents 4b39942 + 977dc58 commit 910a807

File tree

13 files changed

+315
-16
lines changed

13 files changed

+315
-16
lines changed

README.markdown

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,21 @@ $repositories = $client->api('user')->repositories('ornicar');
5858

5959
From `$client` object, you can access to all GitHub.
6060

61+
## Cache usage
62+
63+
```php
64+
<?php
65+
66+
// This file is generated by Composer
67+
require_once 'vendor/autoload.php';
68+
69+
$client = new Github\Client(new CachedHttpClient(new FilesystemCache('/tmp/github-api-cache')));
70+
```
71+
72+
Using cache, the client will get cached responses if resources haven't changed since last time,
73+
**without** reaching the `X-Rate-Limit` [imposed by github](http://developer.github.com/v3/#rate-limiting).
74+
75+
6176
## Documentation
6277

6378
See the `doc` directory for more detailed documentation.

lib/Github/Client.php

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,15 +67,11 @@ class Client
6767
/**
6868
* Instantiate a new GitHub client
6969
*
70-
* @param null|ClientInterface $httpClient Buzz client
70+
* @param null|HttpClientInterface $httpClient Github http client
7171
*/
72-
public function __construct(ClientInterface $httpClient = null)
72+
public function __construct(HttpClientInterface $httpClient = null)
7373
{
74-
$httpClient = $httpClient ?: new Curl();
75-
$httpClient->setTimeout($this->options['timeout']);
76-
$httpClient->setVerifyPeer(false);
77-
78-
$this->httpClient = new HttpClient($this->options, $httpClient);
74+
$this->httpClient = $httpClient ?: new HttpClient($this->options);
7975
}
8076

8177
/**
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace Github\HttpClient\Cache;
4+
5+
use Github\HttpClient\Message\Response;
6+
7+
/**
8+
* Caches github api responses
9+
*
10+
* @author Florian Klein <[email protected]>
11+
*/
12+
interface CacheInterface
13+
{
14+
/**
15+
* @param string the id of the cached resource
16+
* @return int the modified since timestamp
17+
**/
18+
public function getModifiedSince($id);
19+
20+
/**
21+
* @param string the id of the cached resource
22+
* @return Response The cached response object
23+
**/
24+
public function get($id);
25+
26+
/**
27+
* @param string the id of the cached resource
28+
* @param Response the response to cache
29+
**/
30+
public function set($id, Response $response);
31+
}
32+
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
namespace Github\HttpClient\Cache;
4+
5+
use Github\HttpClient\Message\Response;
6+
7+
class FilesystemCache implements CacheInterface
8+
{
9+
protected $path;
10+
11+
public function __construct($path = null)
12+
{
13+
$this->path = $path ?: sys_get_temp_dir().DIRECTORY_SEPARATOR.'php-github-api-cache';
14+
}
15+
16+
public function get($id)
17+
{
18+
if (false !== $content = @file_get_contents($this->getPath($id))) {
19+
return unserialize($content);
20+
}
21+
22+
throw new \InvalidArgumentException(sprintf('File "%s" not found', $this->getPath($id)));
23+
}
24+
25+
public function set($id, Response $response)
26+
{
27+
if (!is_dir($this->path)) {
28+
mkdir($this->path, 0777, true);
29+
}
30+
31+
if (false === $bytes = @file_put_contents($this->getPath($id), serialize($response))) {
32+
throw new \InvalidArgumentException(sprintf('Cannot put content in file "%s"', $this->getPath($id)));
33+
}
34+
}
35+
36+
public function getModifiedSince($id)
37+
{
38+
if (file_exists($this->getPath($id))) {
39+
return filemtime($this->getPath($id));
40+
}
41+
}
42+
43+
protected function getPath($id)
44+
{
45+
return sprintf('%s%s%s', rtrim($this->path, DIRECTORY_SEPARATOR), DIRECTORY_SEPARATOR, md5($id));
46+
}
47+
}
48+
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
namespace Github\HttpClient;
4+
5+
use Buzz\Client\ClientInterface;
6+
use Buzz\Message\MessageInterface;
7+
use Buzz\Message\RequestInterface;
8+
use Buzz\Listener\ListenerInterface;
9+
10+
use Github\Exception\ErrorException;
11+
use Github\Exception\RuntimeException;
12+
use Github\HttpClient\Listener\ErrorListener;
13+
use Github\HttpClient\Message\Request;
14+
use Github\HttpClient\Message\Response;
15+
use Github\HttpClient\Cache\CacheInterface;
16+
use Github\HttpClient\Cache\FilesystemCache;
17+
18+
/**
19+
* Performs requests on GitHub API using If-Modified-Since headers.
20+
* Returns a cached version if not modified
21+
* Avoids increasing the X-Rate-Limit, which is cool
22+
*
23+
* @author Florian Klein <[email protected]>
24+
*/
25+
class CachedHttpClient extends HttpClient
26+
{
27+
protected $cache;
28+
29+
public function __construct(array $options = array(), ClientInterface $client = null, CacheInterface $cache = null)
30+
{
31+
parent::__construct($options, $client);
32+
33+
$this->cache = $cache ?: new FilesystemCache;
34+
}
35+
36+
public function request($path, array $parameters = array(), $httpMethod = 'GET', array $headers = array(), Response $response = null)
37+
{
38+
$response = parent::request($path, $parameters, $httpMethod, $headers, $response);
39+
40+
$key = trim($this->options['base_url'].$path, '/');
41+
if ($response->isNotModified()) {
42+
return $this->cache->get($key);
43+
}
44+
45+
$this->cache->set($key, $response);
46+
47+
return $response;
48+
}
49+
50+
/**
51+
* Create requests with If-Modified-Since headers
52+
* @param string $httpMethod
53+
* @param string $url
54+
*
55+
* @return Request
56+
*/
57+
protected function createRequest($httpMethod, $url)
58+
{
59+
$request = parent::createRequest($httpMethod, $url);
60+
$modifiedSince = date('r', $this->cache->getModifiedSince($url));
61+
$request->addHeader(sprintf('If-Modified-Since: %s', $modifiedSince));
62+
63+
return $request;
64+
}
65+
}

lib/Github/HttpClient/HttpClient.php

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use Github\HttpClient\Listener\ErrorListener;
1313
use Github\HttpClient\Message\Request;
1414
use Github\HttpClient\Message\Response;
15+
use Buzz\Client\Curl;
1516

1617
/**
1718
* Performs requests on GitHub API. API documentation should be self-explanatory.
@@ -48,8 +49,12 @@ class HttpClient implements HttpClientInterface
4849
* @param array $options
4950
* @param ClientInterface $client
5051
*/
51-
public function __construct(array $options, ClientInterface $client)
52+
public function __construct(array $options = array(), ClientInterface $client = null)
5253
{
54+
$client = $client ?: new Curl();
55+
$client->setTimeout($this->options['timeout']);
56+
$client->setVerifyPeer(false);
57+
5358
$this->options = array_merge($this->options, $options);
5459
$this->client = $client;
5560

@@ -139,7 +144,7 @@ public function put($path, array $parameters = array(), array $headers = array()
139144
/**
140145
* {@inheritDoc}
141146
*/
142-
public function request($path, array $parameters = array(), $httpMethod = 'GET', array $headers = array())
147+
public function request($path, array $parameters = array(), $httpMethod = 'GET', array $headers = array(), Response $response = null)
143148
{
144149
$path = trim($this->options['base_url'].$path, '/');
145150

@@ -154,7 +159,9 @@ public function request($path, array $parameters = array(), $httpMethod = 'GET',
154159
}
155160
}
156161

157-
$response = new Response();
162+
if (null === $response) {
163+
$response = new Response;
164+
}
158165

159166
try {
160167
$this->client->send($request, $response);
@@ -198,7 +205,7 @@ public function getLastResponse()
198205
*
199206
* @return Request
200207
*/
201-
private function createRequest($httpMethod, $url)
208+
protected function createRequest($httpMethod, $url)
202209
{
203210
$request = new Request($httpMethod);
204211
$request->setHeaders($this->headers);

lib/Github/HttpClient/HttpClientInterface.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Github\HttpClient;
44

55
use Github\Exception\InvalidArgumentException;
6+
use Github\HttpClient\Message\Response;
67

78
/**
89
* Performs requests on GitHub API. API documentation should be self-explanatory.
@@ -77,7 +78,7 @@ public function delete($path, array $parameters = array(), array $headers = arra
7778
*
7879
* @return array Data
7980
*/
80-
public function request($path, array $parameters = array(), $httpMethod = 'GET', array $headers = array());
81+
public function request($path, array $parameters = array(), $httpMethod = 'GET', array $headers = array(), Response $response = null);
8182

8283
/**
8384
* Change an option value.

lib/Github/HttpClient/Message/Response.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,14 @@ public function getApiLimit()
6464
throw new ApiLimitExceedException($this->options['api_limit']);
6565
}
6666
}
67+
68+
/**
69+
* Is not modified
70+
*
71+
* @return Boolean
72+
*/
73+
public function isNotModified()
74+
{
75+
return 304 === $this->getStatusCode();
76+
}
6777
}

test/Github/Tests/Api/AbstractApiTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ protected function getAbstractApiObject($client)
126126
*/
127127
protected function getClientMock()
128128
{
129-
return new \Github\Client($this->getHttpClientMock());
129+
return new \Github\Client($this->getHttpMock());
130130
}
131131

132132
/**

test/Github/Tests/Api/TestCase.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ protected function getApiMock()
2323

2424
$mock = $this->getMock('Github\HttpClient\HttpClient', array(), array(array(), $httpClient));
2525

26-
$client = new \Github\Client($httpClient);
26+
$client = new \Github\Client($mock);
2727
$client->setHttpClient($mock);
2828

2929
return $this->getMockBuilder($this->getApiClass())

0 commit comments

Comments
 (0)