From e17cbcbec62186b793eada671381ed5e652d9a90 Mon Sep 17 00:00:00 2001 From: Thibaud Fabre Date: Thu, 31 Jul 2014 14:22:16 +0200 Subject: [PATCH 1/5] Add exists method to Contents API Using only HEAD method to avoid downloading file contents when the file does exist --- lib/Github/Api/AbstractApi.php | 107 +++++++++--------- lib/Github/Api/Repository/Contents.php | 37 ++++++ .../Tests/Api/Repository/ContentsTest.php | 78 +++++++++++++ test/Github/Tests/Api/TestCase.php | 2 +- 4 files changed, 168 insertions(+), 56 deletions(-) diff --git a/lib/Github/Api/AbstractApi.php b/lib/Github/Api/AbstractApi.php index 7ef6e6cf353..645121c250d 100644 --- a/lib/Github/Api/AbstractApi.php +++ b/lib/Github/Api/AbstractApi.php @@ -12,6 +12,7 @@ */ abstract class AbstractApi implements ApiInterface { + /** * The client * @@ -22,11 +23,12 @@ abstract class AbstractApi implements ApiInterface /** * number of items per page (GitHub pagination) * - * @var null|int + * @var null int */ protected $perPage; /** + * * @param Client $client */ public function __construct(Client $client) @@ -35,11 +37,11 @@ public function __construct(Client $client) } public function configure() - { - } + {} /** - * @return null|int + * + * @return null int */ public function getPerPage() { @@ -47,6 +49,7 @@ public function getPerPage() } /** + * * @param null|int $perPage */ public function setPerPage($perPage) @@ -59,14 +62,14 @@ public function setPerPage($perPage) /** * Send a GET request with query parameters. * - * @param string $path Request path. - * @param array $parameters GET parameters. - * @param array $requestHeaders Request Headers. - * @return \Guzzle\Http\EntityBodyInterface|mixed|string + * @param string $path Request path. + * @param array $parameters GET parameters. + * @param array $requestHeaders Request Headers. + * @return \Guzzle\Http\EntityBodyInterface mixed string */ protected function get($path, array $parameters = array(), $requestHeaders = array()) { - if (null !== $this->perPage && !isset($parameters['per_page'])) { + if (null !== $this->perPage && ! isset($parameters['per_page'])) { $parameters['per_page'] = $this->perPage; } if (array_key_exists('ref', $parameters) && is_null($parameters['ref'])) { @@ -77,94 +80,88 @@ protected function get($path, array $parameters = array(), $requestHeaders = arr return ResponseMediator::getContent($response); } + /** + * Send a HEAD request with query parameters + * + * @param string $path Request path. + * @param array $parameters HEAD parameters. + * @param array $requestHeaders Request headers. + * @return \Guzzle\Http\Message\Response Response. + */ + protected function head($path, array $parameters = array(), $requestHeaders = array()) + { + $response = $this->client->getHttpClient()->request($path, null, 'HEAD', $requestHeaders, array( + 'query' => $parameters + )); + + return $response; + } + /** * Send a POST request with JSON-encoded parameters. * - * @param string $path Request path. - * @param array $parameters POST parameters to be JSON encoded. - * @param array $requestHeaders Request headers. + * @param string $path Request path. + * @param array $parameters POST parameters to be JSON encoded. + * @param array $requestHeaders Request headers. */ protected function post($path, array $parameters = array(), $requestHeaders = array()) { - return $this->postRaw( - $path, - $this->createJsonBody($parameters), - $requestHeaders - ); + return $this->postRaw($path, $this->createJsonBody($parameters), $requestHeaders); } /** * Send a POST request with raw data. * - * @param string $path Request path. - * @param $body Request body. - * @param array $requestHeaders Request headers. - * @return \Guzzle\Http\EntityBodyInterface|mixed|string + * @param string $path Request path. + * @param $body Request body. + * @param array $requestHeaders Request headers. + * @return \Guzzle\Http\EntityBodyInterface mixed string */ protected function postRaw($path, $body, $requestHeaders = array()) { - $response = $this->client->getHttpClient()->post( - $path, - $body, - $requestHeaders - ); + $response = $this->client->getHttpClient()->post($path, $body, $requestHeaders); return ResponseMediator::getContent($response); } - /** * Send a PATCH request with JSON-encoded parameters. * - * @param string $path Request path. - * @param array $parameters POST parameters to be JSON encoded. - * @param array $requestHeaders Request headers. + * @param string $path Request path. + * @param array $parameters POST parameters to be JSON encoded. + * @param array $requestHeaders Request headers. */ protected function patch($path, array $parameters = array(), $requestHeaders = array()) { - $response = $this->client->getHttpClient()->patch( - $path, - $this->createJsonBody($parameters), - $requestHeaders - ); + $response = $this->client->getHttpClient()->patch($path, $this->createJsonBody($parameters), $requestHeaders); return ResponseMediator::getContent($response); } - /** * Send a PUT request with JSON-encoded parameters. * - * @param string $path Request path. - * @param array $parameters POST parameters to be JSON encoded. - * @param array $requestHeaders Request headers. + * @param string $path Request path. + * @param array $parameters POST parameters to be JSON encoded. + * @param array $requestHeaders Request headers. */ protected function put($path, array $parameters = array(), $requestHeaders = array()) { - $response = $this->client->getHttpClient()->put( - $path, - $this->createJsonBody($parameters), - $requestHeaders - ); + $response = $this->client->getHttpClient()->put($path, $this->createJsonBody($parameters), $requestHeaders); return ResponseMediator::getContent($response); } - /** * Send a DELETE request with JSON-encoded parameters. * - * @param string $path Request path. - * @param array $parameters POST parameters to be JSON encoded. - * @param array $requestHeaders Request headers. + * @param string $path Request path. + * @param array $parameters POST parameters to be JSON encoded. + * @param array $requestHeaders Request headers. */ protected function delete($path, array $parameters = array(), $requestHeaders = array()) { - $response = $this->client->getHttpClient()->delete( - $path, - $this->createJsonBody($parameters), - $requestHeaders - ); + $response = $this->client->getHttpClient()->delete($path, $this->createJsonBody($parameters), $requestHeaders); return ResponseMediator::getContent($response); } @@ -172,8 +169,8 @@ protected function delete($path, array $parameters = array(), $requestHeaders = /** * Create a JSON encoded version of an array of parameters. * - * @param array $parameters Request parameters - * @return null|string + * @param array $parameters Request parameters + * @return null string */ protected function createJsonBody(array $parameters) { diff --git a/lib/Github/Api/Repository/Contents.php b/lib/Github/Api/Repository/Contents.php index 5ed185b362b..baee829770c 100644 --- a/lib/Github/Api/Repository/Contents.php +++ b/lib/Github/Api/Repository/Contents.php @@ -6,6 +6,7 @@ use Github\Exception\InvalidArgumentException; use Github\Exception\ErrorException; use Github\Exception\MissingArgumentException; +use Github\Exception\TwoFactorAuthenticationRequiredException; /** * @link http://developer.github.com/v3/repos/contents/ @@ -92,6 +93,42 @@ public function create($username, $repository, $path, $content, $message, $branc return $this->put($url, $parameters); } + /** + * Checks that a given path exists in a repository. + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * @param string $path path of file to check + * @param null|string $reference reference to a branch or commit + * @return boolean + */ + public function exists($username, $repository, $path, $reference = null) + { + $url = 'repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/contents'; + + if (null !== $path) { + $url .= '/'.rawurlencode($path); + } + + try { + $response = $this->head($url, array( + 'ref' => $reference + )); + + if ($response->getStatusCode() != 200) { + return false; + } + } + catch (TwoFactorAuthenticationRequiredException $ex) { + throw $ex; + } + catch (\Exception $ex) { + return false; + } + + return true; + } + /** * Updates the contents of a file in a repository * @link http://developer.github.com/v3/repos/contents/#update-a-file diff --git a/test/Github/Tests/Api/Repository/ContentsTest.php b/test/Github/Tests/Api/Repository/ContentsTest.php index 0f74f237fb8..1d746866224 100644 --- a/test/Github/Tests/Api/Repository/ContentsTest.php +++ b/test/Github/Tests/Api/Repository/ContentsTest.php @@ -3,6 +3,7 @@ namespace Github\Tests\Api\Repository; use Github\Tests\Api\TestCase; +use Github\Exception\TwoFactorAuthenticationRequiredException; class ContentsTest extends TestCase { @@ -38,6 +39,83 @@ public function shouldShowReadme() $this->assertEquals($expectedValue, $api->readme('KnpLabs', 'php-github-api')); } + /** + * @test + */ + public function shouldReturnTrueWhenFileExists() + { + $responseMock = $this->getMockBuilder('\Guzzle\Http\Message\Response') + ->disableOriginalConstructor() + ->getMock(); + + $responseMock->expects($this->any()) + ->method('getStatusCode') + ->willReturn(200); + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('head') + ->with('repos/KnpLabs/php-github-api/contents/composer.json', array('ref' => null)) + ->will($this->returnValue($responseMock)); + + $this->assertEquals(true, $api->exists('KnpLabs', 'php-github-api', 'composer.json')); + } + + private function getGuzzleResponseMock() + { + $responseMock = $this->getMockBuilder('\Guzzle\Http\Message\Response') + ->disableOriginalConstructor() + ->getMock(); + + return $responseMock; + } + + public function getFailureStubsForExistsTest() + { + $nonOkResponseMock =$this->getGuzzleResponseMock(); + + $nonOkResponseMock->expects($this->any()) + ->method('getStatusCode') + ->willReturn(403); + + return array( + array($this->throwException(new \ErrorException())), + array($this->returnValue($nonOkResponseMock)) + ); + } + + /** + * @test + * @dataProvider getFailureStubsForExistsTest + */ + public function shouldReturnFalseWhenFileIsNotFound(\PHPUnit_Framework_MockObject_Stub $failureStub) + { + $expectedValue = array('some-header' => 'value'); + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('head') + ->with('repos/KnpLabs/php-github-api/contents/composer.json', array('ref' => null)) + ->will($failureStub); + + $this->assertFalse($api->exists('KnpLabs', 'php-github-api', 'composer.json')); + } + + /** + * @test + * @expectedException \Github\Exception\TwoFactorAuthenticationRequiredException + */ + public function shouldBubbleTwoFactorAuthenticationRequiredExceptionsWhenCheckingFileRequiringAuth() + { + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('head') + ->with('repos/KnpLabs/php-github-api/contents/composer.json', array('ref' => null)) + ->will($this->throwException(new TwoFactorAuthenticationRequiredException(0))); + + $api->exists('KnpLabs', 'php-github-api', 'composer.json'); + } + /** * @test */ diff --git a/test/Github/Tests/Api/TestCase.php b/test/Github/Tests/Api/TestCase.php index b96f0e43bbb..a8f322e31e7 100644 --- a/test/Github/Tests/Api/TestCase.php +++ b/test/Github/Tests/Api/TestCase.php @@ -19,7 +19,7 @@ protected function getApiMock() $client->setHttpClient($mock); return $this->getMockBuilder($this->getApiClass()) - ->setMethods(array('get', 'post', 'postRaw', 'patch', 'delete', 'put')) + ->setMethods(array('get', 'post', 'postRaw', 'patch', 'delete', 'put', 'head')) ->setConstructorArgs(array($client)) ->getMock(); } From aa672395508a8646f00c05beec36ada958090d75 Mon Sep 17 00:00:00 2001 From: Thibaud Fabre Date: Thu, 31 Jul 2014 14:59:18 +0200 Subject: [PATCH 2/5] Fix doc comment formatting --- lib/Github/Api/AbstractApi.php | 95 +++++++++++++++++++++------------- 1 file changed, 60 insertions(+), 35 deletions(-) diff --git a/lib/Github/Api/AbstractApi.php b/lib/Github/Api/AbstractApi.php index 645121c250d..7ed7e56ca24 100644 --- a/lib/Github/Api/AbstractApi.php +++ b/lib/Github/Api/AbstractApi.php @@ -23,7 +23,7 @@ abstract class AbstractApi implements ApiInterface /** * number of items per page (GitHub pagination) * - * @var null int + * @var null|int */ protected $perPage; @@ -37,11 +37,12 @@ public function __construct(Client $client) } public function configure() - {} + { + } /** * - * @return null int + * @return null|int */ public function getPerPage() { @@ -62,14 +63,14 @@ public function setPerPage($perPage) /** * Send a GET request with query parameters. * - * @param string $path Request path. - * @param array $parameters GET parameters. - * @param array $requestHeaders Request Headers. - * @return \Guzzle\Http\EntityBodyInterface mixed string + * @param string $path Request path. + * @param array $parameters GET parameters. + * @param array $requestHeaders Request Headers. + * @return \Guzzle\Http\EntityBodyInterface|mixed|string */ protected function get($path, array $parameters = array(), $requestHeaders = array()) { - if (null !== $this->perPage && ! isset($parameters['per_page'])) { + if (null !== $this->perPage && !isset($parameters['per_page'])) { $parameters['per_page'] = $this->perPage; } if (array_key_exists('ref', $parameters) && is_null($parameters['ref'])) { @@ -83,13 +84,17 @@ protected function get($path, array $parameters = array(), $requestHeaders = arr /** * Send a HEAD request with query parameters * - * @param string $path Request path. - * @param array $parameters HEAD parameters. - * @param array $requestHeaders Request headers. - * @return \Guzzle\Http\Message\Response Response. + * @param string $path Request path. + * @param array $parameters HEAD parameters. + * @param array $requestHeaders Request headers. + * @return \Guzzle\Http\Message\Response */ protected function head($path, array $parameters = array(), $requestHeaders = array()) { + if (array_key_exists('ref', $parameters) && is_null($parameters['ref'])) { + unset($parameters['ref']); + } + $response = $this->client->getHttpClient()->request($path, null, 'HEAD', $requestHeaders, array( 'query' => $parameters )); @@ -100,26 +105,34 @@ protected function head($path, array $parameters = array(), $requestHeaders = ar /** * Send a POST request with JSON-encoded parameters. * - * @param string $path Request path. - * @param array $parameters POST parameters to be JSON encoded. - * @param array $requestHeaders Request headers. + * @param string $path Request path. + * @param array $parameters POST parameters to be JSON encoded. + * @param array $requestHeaders Request headers. */ protected function post($path, array $parameters = array(), $requestHeaders = array()) { - return $this->postRaw($path, $this->createJsonBody($parameters), $requestHeaders); + return $this->postRaw( + $path, + $this->createJsonBody($parameters), + $requestHeaders + ); } /** * Send a POST request with raw data. * - * @param string $path Request path. - * @param $body Request body. - * @param array $requestHeaders Request headers. - * @return \Guzzle\Http\EntityBodyInterface mixed string + * @param string $path Request path. + * @param $body Request body. + * @param array $requestHeaders Request headers. + * @return \Guzzle\Http\EntityBodyInterface|mixed|string */ protected function postRaw($path, $body, $requestHeaders = array()) { - $response = $this->client->getHttpClient()->post($path, $body, $requestHeaders); + $response = $this->client->getHttpClient()->post( + $path, + $body, + $requestHeaders + ); return ResponseMediator::getContent($response); } @@ -127,13 +140,17 @@ protected function postRaw($path, $body, $requestHeaders = array()) /** * Send a PATCH request with JSON-encoded parameters. * - * @param string $path Request path. - * @param array $parameters POST parameters to be JSON encoded. - * @param array $requestHeaders Request headers. + * @param string $path Request path. + * @param array $parameters POST parameters to be JSON encoded. + * @param array $requestHeaders Request headers. */ protected function patch($path, array $parameters = array(), $requestHeaders = array()) { - $response = $this->client->getHttpClient()->patch($path, $this->createJsonBody($parameters), $requestHeaders); + $response = $this->client->getHttpClient()->patch( + $path, + $this->createJsonBody($parameters), + $requestHeaders + ); return ResponseMediator::getContent($response); } @@ -141,13 +158,17 @@ protected function patch($path, array $parameters = array(), $requestHeaders = a /** * Send a PUT request with JSON-encoded parameters. * - * @param string $path Request path. - * @param array $parameters POST parameters to be JSON encoded. - * @param array $requestHeaders Request headers. + * @param string $path Request path. + * @param array $parameters POST parameters to be JSON encoded. + * @param array $requestHeaders Request headers. */ protected function put($path, array $parameters = array(), $requestHeaders = array()) { - $response = $this->client->getHttpClient()->put($path, $this->createJsonBody($parameters), $requestHeaders); + $response = $this->client->getHttpClient()->put( + $path, + $this->createJsonBody($parameters), + $requestHeaders + ); return ResponseMediator::getContent($response); } @@ -155,13 +176,17 @@ protected function put($path, array $parameters = array(), $requestHeaders = arr /** * Send a DELETE request with JSON-encoded parameters. * - * @param string $path Request path. - * @param array $parameters POST parameters to be JSON encoded. - * @param array $requestHeaders Request headers. + * @param string $path Request path. + * @param array $parameters POST parameters to be JSON encoded. + * @param array $requestHeaders Request headers. */ protected function delete($path, array $parameters = array(), $requestHeaders = array()) { - $response = $this->client->getHttpClient()->delete($path, $this->createJsonBody($parameters), $requestHeaders); + $response = $this->client->getHttpClient()->delete( + $path, + $this->createJsonBody($parameters), + $requestHeaders + ); return ResponseMediator::getContent($response); } @@ -169,8 +194,8 @@ protected function delete($path, array $parameters = array(), $requestHeaders = /** * Create a JSON encoded version of an array of parameters. * - * @param array $parameters Request parameters - * @return null string + * @param array $parameters Request parameters + * @return null|string */ protected function createJsonBody(array $parameters) { From b402495acfecbe9a61079c7151917da17bda44ae Mon Sep 17 00:00:00 2001 From: Thibaud Fabre Date: Thu, 31 Jul 2014 16:17:41 +0200 Subject: [PATCH 3/5] Undo extra/removed lines by autoformat --- lib/Github/Api/AbstractApi.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/Github/Api/AbstractApi.php b/lib/Github/Api/AbstractApi.php index 7ed7e56ca24..724aaf44b35 100644 --- a/lib/Github/Api/AbstractApi.php +++ b/lib/Github/Api/AbstractApi.php @@ -12,7 +12,6 @@ */ abstract class AbstractApi implements ApiInterface { - /** * The client * @@ -28,7 +27,6 @@ abstract class AbstractApi implements ApiInterface protected $perPage; /** - * * @param Client $client */ public function __construct(Client $client) @@ -41,7 +39,6 @@ public function configure() } /** - * * @return null|int */ public function getPerPage() @@ -50,7 +47,6 @@ public function getPerPage() } /** - * * @param null|int $perPage */ public function setPerPage($perPage) @@ -194,7 +190,7 @@ protected function delete($path, array $parameters = array(), $requestHeaders = /** * Create a JSON encoded version of an array of parameters. * - * @param array $parameters Request parameters + * @param array $parameters Request parameters * @return null|string */ protected function createJsonBody(array $parameters) From f9d2df810e41ef0650ccde86e3f7035b2259e61e Mon Sep 17 00:00:00 2001 From: Thibaud Fabre Date: Thu, 31 Jul 2014 16:38:31 +0200 Subject: [PATCH 4/5] Add doc for Contents API --- doc/index.md | 1 + doc/repo/contents.md | 56 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 doc/repo/contents.md diff --git a/doc/index.md b/doc/index.md index 6c3c8152de4..e278e2c931b 100644 --- a/doc/index.md +++ b/doc/index.md @@ -15,6 +15,7 @@ APIs: * [Pull Requests](pull_requests.md) * [Comments](pull_request/comments.md) * [Repositories](repos.md) + * [Contents](repo/contents.md) * [Releases](repo/releases.md) * [Assets](repo/assets.md) * [Users](users.md) diff --git a/doc/repo/contents.md b/doc/repo/contents.md new file mode 100644 index 00000000000..6307f5d449b --- /dev/null +++ b/doc/repo/contents.md @@ -0,0 +1,56 @@ +## Repo / Contents API +[Back to the "Repos API"](../repos.md) | [Back to the navigation](../index.md) + +### Get a repository's README + +```php +$readme = $client->api('repo')->contents()->readme('knp-labs', 'php-github-api', $reference); +``` + +### Get information about a repository file or directory + +```php +$fileInfo = $client->api('repo')->contents()->show('knp-labs', 'php-github-api', $path, $reference); +``` + +### Check that a file or directory exists in the repository +```php +$fileExists = $client->api('repo')->contents()->exists('knp-labs', 'php-github-api', $path, $reference); +``` + +### Create a file +```php +$committer = array('name' => 'KnpLabs', 'email' => 'info@knplabs.com'); + +$fileInfo = $client->api('repo')->contents()->create('knp-labs', 'php-github-api', $path, $content, $commitMessage, $branch, $committer); +``` + +### Update a file + +```php +$committer = array('name' => 'KnpLabs', 'email' => 'info@knplabs.com'); +$oldFile = $client->api('repo')->contents()->show('knp-labs', 'php-github-api', $path, $branch); + +$fileInfo = $client->api('repo')->contents()->create('knp-labs', 'php-github-api', $path, $content, $commitMessage, $oldFile['sha'], $branch, $committer); +``` + +### Remove a file + +```php +$committer = array('name' => 'KnpLabs', 'email' => 'info@knplabs.com'); +$oldFile = $client->api('repo')->contents()->show('knp-labs', 'php-github-api', $path, $branch); + +$fileInfo = $client->api('repo')->contents()->rm('knp-labs', 'php-github-api', $path, $commitMessage, $oldFile['sha'], $branch, $committer); +``` + +### Get repository archive + +```php +// @todo Document +``` + +### Download a file + +```php +$fileContent = $client->api('repo')->contents()->download('knp-labs', 'php-github-api', $path, $reference); +``` From b4f3acd8616a5d047a538a4ce5ee11ebd4761f53 Mon Sep 17 00:00:00 2001 From: Thibaud Fabre Date: Fri, 1 Aug 2014 02:36:36 +0200 Subject: [PATCH 5/5] Formatting --- lib/Github/Api/Repository/Contents.php | 6 ++---- .../Tests/Api/Repository/ContentsTest.php | 19 ++++++++++--------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/lib/Github/Api/Repository/Contents.php b/lib/Github/Api/Repository/Contents.php index baee829770c..2889c1c7e8b 100644 --- a/lib/Github/Api/Repository/Contents.php +++ b/lib/Github/Api/Repository/Contents.php @@ -118,11 +118,9 @@ public function exists($username, $repository, $path, $reference = null) if ($response->getStatusCode() != 200) { return false; } - } - catch (TwoFactorAuthenticationRequiredException $ex) { + } catch (TwoFactorAuthenticationRequiredException $ex) { throw $ex; - } - catch (\Exception $ex) { + } catch (\Exception $ex) { return false; } diff --git a/test/Github/Tests/Api/Repository/ContentsTest.php b/test/Github/Tests/Api/Repository/ContentsTest.php index 1d746866224..a57ed3245ac 100644 --- a/test/Github/Tests/Api/Repository/ContentsTest.php +++ b/test/Github/Tests/Api/Repository/ContentsTest.php @@ -61,15 +61,6 @@ public function shouldReturnTrueWhenFileExists() $this->assertEquals(true, $api->exists('KnpLabs', 'php-github-api', 'composer.json')); } - private function getGuzzleResponseMock() - { - $responseMock = $this->getMockBuilder('\Guzzle\Http\Message\Response') - ->disableOriginalConstructor() - ->getMock(); - - return $responseMock; - } - public function getFailureStubsForExistsTest() { $nonOkResponseMock =$this->getGuzzleResponseMock(); @@ -324,4 +315,14 @@ protected function getApiClass() { return 'Github\Api\Repository\Contents'; } + + + private function getGuzzleResponseMock() + { + $responseMock = $this->getMockBuilder('\Guzzle\Http\Message\Response') + ->disableOriginalConstructor() + ->getMock(); + + return $responseMock; + } }