Skip to content

Commit 0056aa2

Browse files
author
Michael Babker
committed
Implement custom GitHub API connector (Fix #155)
1 parent a876a35 commit 0056aa2

File tree

6 files changed

+385
-37
lines changed

6 files changed

+385
-37
lines changed

administrator/components/com_patchtester/PatchTester/Controller/StartfetchController.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
namespace PatchTester\Controller;
1010

11+
use PatchTester\GitHub\Exception\UnexpectedResponse;
1112
use PatchTester\Helper;
1213
use PatchTester\Model\PullsModel;
1314
use PatchTester\Model\TestsModel;
@@ -47,13 +48,12 @@ public function execute()
4748
}
4849

4950
// Make sure we can fetch the data from GitHub - throw an error on < 10 available requests
50-
$github = Helper::initializeGithub();
51-
5251
try
5352
{
54-
$rate = $github->authorization->getRateLimit();
53+
$rateResponse = Helper::initializeGithub()->getRateLimit();
54+
$rate = json_decode($rateResponse->body);
5555
}
56-
catch (\Exception $e)
56+
catch (UnexpectedResponse $e)
5757
{
5858
$response = new \JResponseJson(
5959
new \Exception(
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
/**
3+
* Patch testing component for the Joomla! CMS
4+
*
5+
* @copyright Copyright (C) 2011 - 2012 Ian MacLennan, Copyright (C) 2013 - 2016 Open Source Matters, Inc. All rights reserved.
6+
* @license GNU General Public License version 2 or later
7+
*/
8+
9+
namespace PatchTester\GitHub\Exception;
10+
11+
/**
12+
* Exception representing an unexpected response
13+
*
14+
* @since __DEPLOY_VERSION__
15+
*/
16+
class UnexpectedResponse extends \DomainException
17+
{
18+
/**
19+
* The Response object.
20+
*
21+
* @var \JHttpResponse
22+
* @since __DEPLOY_VERSION__
23+
*/
24+
private $response;
25+
26+
/**
27+
* Constructor
28+
*
29+
* @param \JHttpResponse $response The Response object.
30+
* @param string $message The Exception message to throw.
31+
* @param integer $code The Exception code.
32+
* @param \Exception $previous The previous exception used for the exception chaining.
33+
*
34+
* @since __DEPLOY_VERSION__
35+
*/
36+
public function __construct(\JHttpResponse $response, $message = '', $code = 0, \Exception $previous = null)
37+
{
38+
parent::__construct($message, $code, $previous);
39+
40+
$this->response = $response;
41+
}
42+
43+
/**
44+
* Get the Response object.
45+
*
46+
* @return \JHttpResponse
47+
*
48+
* @since __DEPLOY_VERSION__
49+
*/
50+
public function getResponse()
51+
{
52+
return $this->response;
53+
}
54+
}
Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
<?php
2+
/**
3+
* Patch testing component for the Joomla! CMS
4+
*
5+
* @copyright Copyright (C) 2011 - 2012 Ian MacLennan, Copyright (C) 2013 - 2016 Open Source Matters, Inc. All rights reserved.
6+
* @license GNU General Public License version 2 or later
7+
*/
8+
9+
namespace PatchTester\GitHub;
10+
11+
use Joomla\Registry\Registry;
12+
13+
/**
14+
* Helper class for interacting with the GitHub API.
15+
*
16+
* @since __DEPLOY_VERSION__
17+
*/
18+
class GitHub
19+
{
20+
/**
21+
* Options for the connector.
22+
*
23+
* @var Registry
24+
* @since __DEPLOY_VERSION__
25+
*/
26+
protected $options;
27+
28+
/**
29+
* The HTTP client object to use in sending HTTP requests.
30+
*
31+
* @var \JHttp
32+
* @since __DEPLOY_VERSION__
33+
*/
34+
protected $client;
35+
36+
/**
37+
* Constructor.
38+
*
39+
* @param Registry $options Connector options.
40+
* @param \JHttp $client The HTTP client object.
41+
*
42+
* @since __DEPLOY_VERSION__
43+
*/
44+
public function __construct(Registry $options = null, \JHttp $client = null)
45+
{
46+
$this->options = $options ?: new Registry;
47+
$this->client = $client ?: \JHttpFactory::getHttp($options);
48+
}
49+
50+
/**
51+
* Build and return a full request URL.
52+
*
53+
* This method will add appropriate pagination details and basic authentication credentials if necessary
54+
* and also prepend the API url to have a complete URL for the request.
55+
*
56+
* @param string $path URL to inflect
57+
* @param integer $page Page to request
58+
* @param integer $limit Number of results to return per page
59+
*
60+
* @return string The request URL.
61+
*
62+
* @since __DEPLOY_VERSION__
63+
*/
64+
protected function fetchUrl($path, $page = 0, $limit = 0)
65+
{
66+
// Get a new JUri object fousing the api url and given path.
67+
$uri = new \JUri($this->options->get('api.url') . $path);
68+
69+
// Only apply basic authentication if an access token is not set
70+
if ($this->options->get('gh.token', false) === false)
71+
{
72+
// Use basic authentication
73+
if ($this->options->get('api.username', false))
74+
{
75+
$username = $this->options->get('api.username');
76+
$username = str_replace('@', '%40', $username);
77+
$uri->setUser($username);
78+
}
79+
80+
if ($this->options->get('api.password', false))
81+
{
82+
$password = $this->options->get('api.password');
83+
$password = str_replace('@', '%40', $password);
84+
$uri->setPass($password);
85+
}
86+
}
87+
88+
// If we have a defined page number add it to the JUri object.
89+
if ($page > 0)
90+
{
91+
$uri->setVar('page', (int) $page);
92+
}
93+
94+
// If we have a defined items per page add it to the JUri object.
95+
if ($limit > 0)
96+
{
97+
$uri->setVar('per_page', (int) $limit);
98+
}
99+
100+
return (string) $uri;
101+
}
102+
103+
/**
104+
* Get the HTTP client for this connector.
105+
*
106+
* @return \JHttp
107+
*
108+
* @since __DEPLOY_VERSION__
109+
*/
110+
public function getClient()
111+
{
112+
return $this->client;
113+
}
114+
115+
/**
116+
* Get the diff for a pull request.
117+
*
118+
* @param string $user The name of the owner of the GitHub repository.
119+
* @param string $repo The name of the GitHub repository.
120+
* @param integer $pullId The pull request number.
121+
*
122+
* @return \JHttpResponse
123+
*
124+
* @since __DEPLOY_VERSION__
125+
*/
126+
public function getDiffForPullRequest($user, $repo, $pullId)
127+
{
128+
// Build the request path.
129+
$path = "/repos/$user/$repo/pulls/" . (int) $pullId;
130+
131+
// Build the request headers.
132+
$headers = array('Accept' => 'application/vnd.github.diff');
133+
134+
$prepared = $this->prepareRequest($path, 0, 0, $headers);
135+
136+
return $this->processResponse($this->client->get($prepared['url'], $prepared['headers']));
137+
}
138+
139+
/**
140+
* Get the list of modified files for a pull request.
141+
*
142+
* @param string $user The name of the owner of the GitHub repository.
143+
* @param string $repo The name of the GitHub repository.
144+
* @param integer $pullId The pull request number.
145+
*
146+
* @return \JHttpResponse
147+
*
148+
* @since __DEPLOY_VERSION__
149+
*/
150+
public function getFilesForPullRequest($user, $repo, $pullId)
151+
{
152+
// Build the request path.
153+
$path = "/repos/$user/$repo/pulls/" . (int) $pullId . '/files';
154+
155+
$prepared = $this->prepareRequest($path, 0, 0, $headers);
156+
157+
return $this->processResponse($this->client->get($prepared['url'], $prepared['headers']));
158+
}
159+
160+
/**
161+
* Get a list of the open issues for a repository.
162+
*
163+
* @param string $user The name of the owner of the GitHub repository.
164+
* @param string $repo The name of the GitHub repository.
165+
* @param integer $page The page number from which to get items.
166+
* @param integer $limit The number of items on a page.
167+
*
168+
* @return \JHttpResponse
169+
*
170+
* @since __DEPLOY_VERSION__
171+
*/
172+
public function getOpenIssues($user, $repo, $page = 0, $limit = 0)
173+
{
174+
$prepared = $this->prepareRequest("/repos/$user/$repo/issues", $page, $limit);
175+
176+
return $this->processResponse($this->client->get($prepared['url'], $prepared['headers']));
177+
}
178+
179+
/**
180+
* Get an option from the connector.
181+
*
182+
* @param string $key The name of the option to get.
183+
* @param mixed $default The default value if the option is not set.
184+
*
185+
* @return mixed The option value.
186+
*
187+
* @since __DEPLOY_VERSION__
188+
*/
189+
public function getOption($key, $default = null)
190+
{
191+
return $this->options->get($key, $default);
192+
}
193+
194+
/**
195+
* Get a single pull request.
196+
*
197+
* @param string $user The name of the owner of the GitHub repository.
198+
* @param string $repo The name of the GitHub repository.
199+
* @param integer $pullId The pull request number.
200+
*
201+
* @return \JHttpResponse
202+
*
203+
* @since __DEPLOY_VERSION__
204+
*/
205+
public function getPullRequest($user, $repo, $pullId)
206+
{
207+
// Build the request path.
208+
$path = "/repos/$user/$repo/pulls/" . (int) $pullId;
209+
210+
$prepared = $this->prepareRequest($path);
211+
212+
return $this->processResponse($this->client->get($prepared['url'], $prepared['headers']));
213+
}
214+
215+
/**
216+
* Get the rate limit for the authenticated user.
217+
*
218+
* @return \JHttpResponse
219+
*
220+
* @since __DEPLOY_VERSION__
221+
*/
222+
public function getRateLimit()
223+
{
224+
$prepared = $this->prepareRequest('/rate_limit');
225+
226+
return $this->processResponse($this->client->get($prepared['url'], $prepared['headers']));
227+
}
228+
229+
/**
230+
* Process the response and return it.
231+
*
232+
* @param \JHttpResponse $response The response.
233+
* @param integer $expectedCode The expected response code.
234+
*
235+
* @return \JHttpResponse
236+
*
237+
* @since __DEPLOY_VERSION__
238+
* @throws Exception\UnexpectedResponse
239+
*/
240+
protected function processResponse(\JHttpResponse $response, $expectedCode = 200)
241+
{
242+
// Validate the response code.
243+
if ($response->code != $expectedCode)
244+
{
245+
// Decode the error response and throw an exception.
246+
$body = json_decode($response->body);
247+
248+
throw new Exception\UnexpectedResponse($response, $body->error, $response->code);
249+
}
250+
251+
return $response;
252+
}
253+
254+
/**
255+
* Method to build and return a full request URL for the request.
256+
*
257+
* This method will add appropriate pagination details if necessary and also prepend the API url to have a complete URL for the request.
258+
*
259+
* @param string $path Path to process
260+
* @param integer $page Page to request
261+
* @param integer $limit Number of results to return per page
262+
* @param array $headers The headers to send with the request
263+
*
264+
* @return array Associative array containing the prepared URL and request headers
265+
*
266+
* @since __DEPLOY_VERSION__
267+
*/
268+
protected function prepareRequest($path, $page = 0, $limit = 0, array $headers = array())
269+
{
270+
$url = $this->fetchUrl($path, $page, $limit);
271+
272+
if ($token = $this->options->get('gh.token', false))
273+
{
274+
$headers['Authorization'] = "token $token";
275+
}
276+
277+
return array('url' => $url, 'headers' => $headers);
278+
}
279+
280+
/**
281+
* Set an option for the connector.
282+
*
283+
* @param string $key The name of the option to set.
284+
* @param mixed $value The option value to set.
285+
*
286+
* @return $this
287+
*
288+
* @since __DEPLOY_VERSION__
289+
*/
290+
public function setOption($key, $value)
291+
{
292+
$this->options->set($key, $value);
293+
294+
return $this;
295+
}
296+
}

0 commit comments

Comments
 (0)