Skip to content

Commit e17cbcb

Browse files
author
Thibaud Fabre
committed
Add exists method to Contents API
Using only HEAD method to avoid downloading file contents when the file does exist
1 parent 9b57f84 commit e17cbcb

File tree

4 files changed

+168
-56
lines changed

4 files changed

+168
-56
lines changed

lib/Github/Api/AbstractApi.php

Lines changed: 52 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
*/
1313
abstract class AbstractApi implements ApiInterface
1414
{
15+
1516
/**
1617
* The client
1718
*
@@ -22,11 +23,12 @@ abstract class AbstractApi implements ApiInterface
2223
/**
2324
* number of items per page (GitHub pagination)
2425
*
25-
* @var null|int
26+
* @var null int
2627
*/
2728
protected $perPage;
2829

2930
/**
31+
*
3032
* @param Client $client
3133
*/
3234
public function __construct(Client $client)
@@ -35,18 +37,19 @@ public function __construct(Client $client)
3537
}
3638

3739
public function configure()
38-
{
39-
}
40+
{}
4041

4142
/**
42-
* @return null|int
43+
*
44+
* @return null int
4345
*/
4446
public function getPerPage()
4547
{
4648
return $this->perPage;
4749
}
4850

4951
/**
52+
*
5053
* @param null|int $perPage
5154
*/
5255
public function setPerPage($perPage)
@@ -59,14 +62,14 @@ public function setPerPage($perPage)
5962
/**
6063
* Send a GET request with query parameters.
6164
*
62-
* @param string $path Request path.
63-
* @param array $parameters GET parameters.
64-
* @param array $requestHeaders Request Headers.
65-
* @return \Guzzle\Http\EntityBodyInterface|mixed|string
65+
* @param string $path Request path.
66+
* @param array $parameters GET parameters.
67+
* @param array $requestHeaders Request Headers.
68+
* @return \Guzzle\Http\EntityBodyInterface mixed string
6669
*/
6770
protected function get($path, array $parameters = array(), $requestHeaders = array())
6871
{
69-
if (null !== $this->perPage && !isset($parameters['per_page'])) {
72+
if (null !== $this->perPage && ! isset($parameters['per_page'])) {
7073
$parameters['per_page'] = $this->perPage;
7174
}
7275
if (array_key_exists('ref', $parameters) && is_null($parameters['ref'])) {
@@ -77,103 +80,97 @@ protected function get($path, array $parameters = array(), $requestHeaders = arr
7780
return ResponseMediator::getContent($response);
7881
}
7982

83+
/**
84+
* Send a HEAD request with query parameters
85+
*
86+
* @param string $path Request path.
87+
* @param array $parameters HEAD parameters.
88+
* @param array $requestHeaders Request headers.
89+
* @return \Guzzle\Http\Message\Response Response.
90+
*/
91+
protected function head($path, array $parameters = array(), $requestHeaders = array())
92+
{
93+
$response = $this->client->getHttpClient()->request($path, null, 'HEAD', $requestHeaders, array(
94+
'query' => $parameters
95+
));
96+
97+
return $response;
98+
}
99+
80100
/**
81101
* Send a POST request with JSON-encoded parameters.
82102
*
83-
* @param string $path Request path.
84-
* @param array $parameters POST parameters to be JSON encoded.
85-
* @param array $requestHeaders Request headers.
103+
* @param string $path Request path.
104+
* @param array $parameters POST parameters to be JSON encoded.
105+
* @param array $requestHeaders Request headers.
86106
*/
87107
protected function post($path, array $parameters = array(), $requestHeaders = array())
88108
{
89-
return $this->postRaw(
90-
$path,
91-
$this->createJsonBody($parameters),
92-
$requestHeaders
93-
);
109+
return $this->postRaw($path, $this->createJsonBody($parameters), $requestHeaders);
94110
}
95111

96112
/**
97113
* Send a POST request with raw data.
98114
*
99-
* @param string $path Request path.
100-
* @param $body Request body.
101-
* @param array $requestHeaders Request headers.
102-
* @return \Guzzle\Http\EntityBodyInterface|mixed|string
115+
* @param string $path Request path.
116+
* @param $body Request body.
117+
* @param array $requestHeaders Request headers.
118+
* @return \Guzzle\Http\EntityBodyInterface mixed string
103119
*/
104120
protected function postRaw($path, $body, $requestHeaders = array())
105121
{
106-
$response = $this->client->getHttpClient()->post(
107-
$path,
108-
$body,
109-
$requestHeaders
110-
);
122+
$response = $this->client->getHttpClient()->post($path, $body, $requestHeaders);
111123

112124
return ResponseMediator::getContent($response);
113125
}
114126

115-
116127
/**
117128
* Send a PATCH request with JSON-encoded parameters.
118129
*
119-
* @param string $path Request path.
120-
* @param array $parameters POST parameters to be JSON encoded.
121-
* @param array $requestHeaders Request headers.
130+
* @param string $path Request path.
131+
* @param array $parameters POST parameters to be JSON encoded.
132+
* @param array $requestHeaders Request headers.
122133
*/
123134
protected function patch($path, array $parameters = array(), $requestHeaders = array())
124135
{
125-
$response = $this->client->getHttpClient()->patch(
126-
$path,
127-
$this->createJsonBody($parameters),
128-
$requestHeaders
129-
);
136+
$response = $this->client->getHttpClient()->patch($path, $this->createJsonBody($parameters), $requestHeaders);
130137

131138
return ResponseMediator::getContent($response);
132139
}
133140

134-
135141
/**
136142
* Send a PUT request with JSON-encoded parameters.
137143
*
138-
* @param string $path Request path.
139-
* @param array $parameters POST parameters to be JSON encoded.
140-
* @param array $requestHeaders Request headers.
144+
* @param string $path Request path.
145+
* @param array $parameters POST parameters to be JSON encoded.
146+
* @param array $requestHeaders Request headers.
141147
*/
142148
protected function put($path, array $parameters = array(), $requestHeaders = array())
143149
{
144-
$response = $this->client->getHttpClient()->put(
145-
$path,
146-
$this->createJsonBody($parameters),
147-
$requestHeaders
148-
);
150+
$response = $this->client->getHttpClient()->put($path, $this->createJsonBody($parameters), $requestHeaders);
149151

150152
return ResponseMediator::getContent($response);
151153
}
152154

153-
154155
/**
155156
* Send a DELETE request with JSON-encoded parameters.
156157
*
157-
* @param string $path Request path.
158-
* @param array $parameters POST parameters to be JSON encoded.
159-
* @param array $requestHeaders Request headers.
158+
* @param string $path Request path.
159+
* @param array $parameters POST parameters to be JSON encoded.
160+
* @param array $requestHeaders Request headers.
160161
*/
161162
protected function delete($path, array $parameters = array(), $requestHeaders = array())
162163
{
163-
$response = $this->client->getHttpClient()->delete(
164-
$path,
165-
$this->createJsonBody($parameters),
166-
$requestHeaders
167-
);
164+
$response = $this->client->getHttpClient()->delete($path, $this->createJsonBody($parameters), $requestHeaders);
168165

169166
return ResponseMediator::getContent($response);
170167
}
171168

172169
/**
173170
* Create a JSON encoded version of an array of parameters.
174171
*
175-
* @param array $parameters Request parameters
176-
* @return null|string
172+
* @param array $parameters Request parameters
173+
* @return null string
177174
*/
178175
protected function createJsonBody(array $parameters)
179176
{

lib/Github/Api/Repository/Contents.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Github\Exception\InvalidArgumentException;
77
use Github\Exception\ErrorException;
88
use Github\Exception\MissingArgumentException;
9+
use Github\Exception\TwoFactorAuthenticationRequiredException;
910

1011
/**
1112
* @link http://developer.github.com/v3/repos/contents/
@@ -92,6 +93,42 @@ public function create($username, $repository, $path, $content, $message, $branc
9293
return $this->put($url, $parameters);
9394
}
9495

96+
/**
97+
* Checks that a given path exists in a repository.
98+
*
99+
* @param string $username the user who owns the repository
100+
* @param string $repository the name of the repository
101+
* @param string $path path of file to check
102+
* @param null|string $reference reference to a branch or commit
103+
* @return boolean
104+
*/
105+
public function exists($username, $repository, $path, $reference = null)
106+
{
107+
$url = 'repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/contents';
108+
109+
if (null !== $path) {
110+
$url .= '/'.rawurlencode($path);
111+
}
112+
113+
try {
114+
$response = $this->head($url, array(
115+
'ref' => $reference
116+
));
117+
118+
if ($response->getStatusCode() != 200) {
119+
return false;
120+
}
121+
}
122+
catch (TwoFactorAuthenticationRequiredException $ex) {
123+
throw $ex;
124+
}
125+
catch (\Exception $ex) {
126+
return false;
127+
}
128+
129+
return true;
130+
}
131+
95132
/**
96133
* Updates the contents of a file in a repository
97134
* @link http://developer.github.com/v3/repos/contents/#update-a-file

test/Github/Tests/Api/Repository/ContentsTest.php

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Github\Tests\Api\Repository;
44

55
use Github\Tests\Api\TestCase;
6+
use Github\Exception\TwoFactorAuthenticationRequiredException;
67

78
class ContentsTest extends TestCase
89
{
@@ -38,6 +39,83 @@ public function shouldShowReadme()
3839
$this->assertEquals($expectedValue, $api->readme('KnpLabs', 'php-github-api'));
3940
}
4041

42+
/**
43+
* @test
44+
*/
45+
public function shouldReturnTrueWhenFileExists()
46+
{
47+
$responseMock = $this->getMockBuilder('\Guzzle\Http\Message\Response')
48+
->disableOriginalConstructor()
49+
->getMock();
50+
51+
$responseMock->expects($this->any())
52+
->method('getStatusCode')
53+
->willReturn(200);
54+
55+
$api = $this->getApiMock();
56+
$api->expects($this->once())
57+
->method('head')
58+
->with('repos/KnpLabs/php-github-api/contents/composer.json', array('ref' => null))
59+
->will($this->returnValue($responseMock));
60+
61+
$this->assertEquals(true, $api->exists('KnpLabs', 'php-github-api', 'composer.json'));
62+
}
63+
64+
private function getGuzzleResponseMock()
65+
{
66+
$responseMock = $this->getMockBuilder('\Guzzle\Http\Message\Response')
67+
->disableOriginalConstructor()
68+
->getMock();
69+
70+
return $responseMock;
71+
}
72+
73+
public function getFailureStubsForExistsTest()
74+
{
75+
$nonOkResponseMock =$this->getGuzzleResponseMock();
76+
77+
$nonOkResponseMock->expects($this->any())
78+
->method('getStatusCode')
79+
->willReturn(403);
80+
81+
return array(
82+
array($this->throwException(new \ErrorException())),
83+
array($this->returnValue($nonOkResponseMock))
84+
);
85+
}
86+
87+
/**
88+
* @test
89+
* @dataProvider getFailureStubsForExistsTest
90+
*/
91+
public function shouldReturnFalseWhenFileIsNotFound(\PHPUnit_Framework_MockObject_Stub $failureStub)
92+
{
93+
$expectedValue = array('some-header' => 'value');
94+
95+
$api = $this->getApiMock();
96+
$api->expects($this->once())
97+
->method('head')
98+
->with('repos/KnpLabs/php-github-api/contents/composer.json', array('ref' => null))
99+
->will($failureStub);
100+
101+
$this->assertFalse($api->exists('KnpLabs', 'php-github-api', 'composer.json'));
102+
}
103+
104+
/**
105+
* @test
106+
* @expectedException \Github\Exception\TwoFactorAuthenticationRequiredException
107+
*/
108+
public function shouldBubbleTwoFactorAuthenticationRequiredExceptionsWhenCheckingFileRequiringAuth()
109+
{
110+
$api = $this->getApiMock();
111+
$api->expects($this->once())
112+
->method('head')
113+
->with('repos/KnpLabs/php-github-api/contents/composer.json', array('ref' => null))
114+
->will($this->throwException(new TwoFactorAuthenticationRequiredException(0)));
115+
116+
$api->exists('KnpLabs', 'php-github-api', 'composer.json');
117+
}
118+
41119
/**
42120
* @test
43121
*/

test/Github/Tests/Api/TestCase.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ protected function getApiMock()
1919
$client->setHttpClient($mock);
2020

2121
return $this->getMockBuilder($this->getApiClass())
22-
->setMethods(array('get', 'post', 'postRaw', 'patch', 'delete', 'put'))
22+
->setMethods(array('get', 'post', 'postRaw', 'patch', 'delete', 'put', 'head'))
2323
->setConstructorArgs(array($client))
2424
->getMock();
2525
}

0 commit comments

Comments
 (0)