Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit c6c44ab

Browse filesBrowse files
committed
Refactoring using a GitHubRequestHandler
1 parent 93b734d commit c6c44ab
Copy full SHA for c6c44ab
Expand file treeCollapse file tree

18 files changed

+233
-122
lines changed

‎.gitignore

Copy file name to clipboardExpand all lines: .gitignore
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/app/bootstrap.php.cache
22
/app/cache/*
33
!app/cache/.gitkeep
4+
/app/config/github.yml
45
/app/config/parameters.yml
56
/app/logs/*
67
!app/logs/.gitkeep

‎app/config/config.yml

Copy file name to clipboardExpand all lines: app/config/config.yml
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ imports:
22
- { resource: parameters.yml }
33
- { resource: security.yml }
44
- { resource: services.yml }
5+
- { resource: github.yml }
56

67
# Put parameters here that don't need to change on each machine where the app is deployed
78
# http://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration

‎app/config/github.yml.dist

Copy file name to clipboard
+16Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
parameters:
2+
# token used to update labels on the repository, etc
3+
github_token: XXX
4+
5+
# defines symfony repository as default
6+
default_repository: symfony/symfony
7+
8+
# point to the main symfony repositories
9+
repositories:
10+
symfony/symfony:
11+
listeners:
12+
- AppBundle\Listener\SymfonyIssueListener
13+
secret: change_me
14+
symfony/symfony-docs:
15+
listeners:
16+
- AppBundle\Listener\SymfonyDocsIssueListener

‎app/config/parameters.yml.dist

Copy file name to clipboardExpand all lines: app/config/parameters.yml.dist
-10Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,3 @@ parameters:
1717

1818
# A secret key that's used to generate certain security-related tokens
1919
secret: ThisTokenIsNotSoSecretChangeIt
20-
21-
# token used to update labels on the repository, etc
22-
github_token: XXXX
23-
# defines symfony repository as default
24-
default_repository: symfony/symfony
25-
# point to the main symfony repositories
26-
github_listeners:
27-
symfony/symfony: 'AppBundle\Listener\SymfonyIssueListener'
28-
symfony/symfony-docs: 'AppBundle\Listener\SymfonyDocsIssueListener'
29-
symfony/symfony-standard: 'AppBundle\Listener\SymfonyStandardIssueListener'

‎app/config/services.yml

Copy file name to clipboardExpand all lines: app/config/services.yml
+5-4Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,12 @@ services:
3030
class: AppBundle\Issues\GitHub\GitHubStatusApi
3131
arguments: ['@app.github.cached_labels_api', '%default_repository%']
3232

33-
app.github.listener_factory:
34-
class: AppBundle\Issues\GitHubListenerFactory
35-
arguments: ['@app.github.labels_api', '%github_listeners%']
33+
app.github.request_handler:
34+
class: AppBundle\Issues\GitHubRequestHandler
35+
arguments: ['@app.github.labels_api', '@event_dispatcher', '%repositories%']
3636

3737
app.github.exception_listener:
38-
class: Appbundle\Listener\ExceptionListener
38+
class: Appbundle\Listener\GitHubExceptionListener
39+
arguments: ['%kernel.debug%']
3940
tags:
4041
- { name: kernel.event_subscriber }

‎src/AppBundle/Controller/WebhookController.php

Copy file name to clipboardExpand all lines: src/AppBundle/Controller/WebhookController.php
+1-37Lines changed: 1 addition & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
namespace AppBundle\Controller;
44

5-
use AppBundle\Event\GitHubEvent;
6-
use AppBundle\Exception\GitHubException;
75
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
86
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
97
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
@@ -18,42 +16,8 @@ class WebhookController extends Controller
1816
*/
1917
public function githubAction(Request $request)
2018
{
21-
$data = json_decode($request->getContent(), true);
22-
if (null === $data) {
23-
throw new GitHubException('Invalid JSON body!');
24-
}
25-
26-
$repository = isset($data['repository']['full_name']) ? $data['repository']['full_name'] : null;
27-
if (empty($repository)) {
28-
throw new GitHubException('No repository name!');
29-
}
30-
31-
$listener = $this->get('app.github.listener_factory')->createFromRepository($repository);
32-
33-
$dispatcher = $this->get('event_dispatcher');
34-
$dispatcher->addSubscriber($listener);
35-
36-
$event = new GitHubEvent($data);
37-
$eventName = $request->headers->get('X-Github-Event');
38-
39-
try {
40-
$dispatcher->dispatch('github.'.$eventName, $event);
41-
} catch (\Exception $e) {
42-
throw new GitHubException(sprintf('Failed dispatching "%s" event for "%s" repository.', $eventName, $repository), 0, $e);
43-
}
44-
45-
$responseData = $event->getResponseData();
46-
47-
if (empty($responseData)) {
48-
$responseData['unsupported_event'] = $eventName;
49-
}
19+
$responseData = $this->get('app.github.request_handler')->handle($request);
5020

5121
return new JsonResponse($responseData);
52-
53-
// 1 read in what event they have
54-
// 2 perform some action
55-
// 3 return JSON
56-
57-
// log something to the database?
5822
}
5923
}
+14Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace AppBundle\Exception;
4+
5+
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
6+
7+
/**
8+
* GitHubAccessDeniedException.
9+
*
10+
* @author Jules Pietri <jules@heahprod.com>
11+
*/
12+
class GitHubAccessDeniedException extends AccessDeniedHttpException implements GitHubExceptionInterface
13+
{
14+
}
+14Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace AppBundle\Exception;
4+
5+
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
6+
7+
/**
8+
* GitHubBadRequestException.
9+
*
10+
* @author Jules Pietri <jules@heahprod.com>
11+
*/
12+
class GitHubBadRequestException extends BadRequestHttpException implements GitHubExceptionInterface
13+
{
14+
}

‎src/AppBundle/Exception/GitHubException.php

Copy file name to clipboardExpand all lines: src/AppBundle/Exception/GitHubException.php
-21Lines changed: 0 additions & 21 deletions
This file was deleted.
+12Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
namespace AppBundle\Exception;
4+
5+
/**
6+
* GitHubExceptionInterface.
7+
*
8+
* @author Jules Pietri <jules@heahprod.com>
9+
*/
10+
interface GitHubExceptionInterface
11+
{
12+
}
+12Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
namespace AppBundle\Exception;
4+
5+
/**
6+
* GitHubInvalidConfigurationException.
7+
*
8+
* @author Jules Pietri <jules@heahprod.com>
9+
*/
10+
class GitHubInvalidConfigurationException extends \LogicException implements GitHubExceptionInterface
11+
{
12+
}
+14Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace AppBundle\Exception;
4+
5+
use Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException;
6+
7+
/**
8+
* GitHubPreconditionFailedException.
9+
*
10+
* @author Jules Pietri <jules@heahprod.com>
11+
*/
12+
class GitHubPreconditionFailedException extends PreconditionFailedHttpException implements GitHubExceptionInterface
13+
{
14+
}
+12Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
namespace AppBundle\Exception;
4+
5+
/**
6+
* GitHubRuntimeException.
7+
*
8+
* @author Jules Pietri <jules@heahprod.com>
9+
*/
10+
class GitHubRuntimeException extends \RuntimeException implements GitHubExceptionInterface
11+
{
12+
}

‎src/AppBundle/Issues/GitHubListenerFactory.php

Copy file name to clipboardExpand all lines: src/AppBundle/Issues/GitHubListenerFactory.php
-40Lines changed: 0 additions & 40 deletions
This file was deleted.
+108Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
3+
namespace AppBundle\Issues;
4+
5+
use AppBundle\Event\GitHubEvent;
6+
use AppBundle\Exception\GitHubAccessDeniedException;
7+
use AppBundle\Exception\GitHubBadRequestException;
8+
use AppBundle\Exception\GitHubExceptionInterface;
9+
use AppBundle\Exception\GitHubInvalidConfigurationException;
10+
use AppBundle\Exception\GitHubPreconditionFailedException;
11+
use AppBundle\Exception\GitHubRuntimeException;
12+
use AppBundle\Issues\GitHub\CachedLabelsApi;
13+
use AppBundle\Issues\GitHub\GitHubStatusApi;
14+
use Github\Api\Issue\Labels;
15+
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
16+
use Symfony\Component\HttpFoundation\Request;
17+
18+
/**
19+
* Class GitHubRequestHandler.
20+
*
21+
* @author Jules Pietri <jules@heahprod.com>
22+
*/
23+
class GitHubRequestHandler
24+
{
25+
private $labelsApi;
26+
private $dispatcher;
27+
private $repositories;
28+
29+
public function __construct(Labels $labelsApi, EventDispatcherInterface $dispatcher, array $repositories)
30+
{
31+
$this->labelsApi = $labelsApi;
32+
$this->dispatcher = $dispatcher;
33+
$this->repositories = $repositories;
34+
}
35+
36+
/**
37+
* @return array The response data
38+
*
39+
* @throws GitHubExceptionInterface When the request or the configuration are invalid
40+
*/
41+
public function handle(Request $request)
42+
{
43+
$data = json_decode($request->getContent(), true);
44+
if (null === $data) {
45+
throw new GitHubBadRequestException('Invalid JSON body!');
46+
}
47+
48+
$repository = isset($data['repository']['full_name']) ? $data['repository']['full_name'] : null;
49+
if (empty($repository)) {
50+
throw new GitHubBadRequestException('No repository name!');
51+
}
52+
53+
if (!isset($this->repositories[$repository])) {
54+
throw new GitHubPreconditionFailedException(sprintf('Unsupported repository "%s".', $repository));
55+
}
56+
57+
$config = $this->repositories[$repository];
58+
if (isset($config['secret'])) {
59+
if (!$request->headers->has('X-Hub-Signature')) {
60+
throw new GitHubAccessDeniedException('The request is not secured.');
61+
}
62+
if (!$this->authenticate($request->headers->get('X-Hub-Signature'), $config['secret'], $request->getContent())) {
63+
throw new GitHubAccessDeniedException('Invalid signature.');
64+
}
65+
}
66+
if (empty($config['listeners'])) {
67+
throw new GitHubInvalidConfigurationException('The repository "%s" has no listeners configured.');
68+
}
69+
70+
$api = new GitHubStatusApi(
71+
new CachedLabelsApi($this->labelsApi, $repository),
72+
$repository
73+
);
74+
foreach ($config['listeners'] as $listener) {
75+
if (!is_subclass_of($listener, __NAMESPACE__.'\IssueListener')) {
76+
throw new GitHubInvalidConfigurationException(sprintf('The listener "%s" must be an instance of "\AppBundle\Issues\IssueListener".', $listener));
77+
}
78+
79+
$this->dispatcher->addSubscriber(new $listener($api));
80+
}
81+
82+
$event = new GitHubEvent($data);
83+
$eventName = $request->headers->get('X-Github-Event');
84+
85+
try {
86+
$this->dispatcher->dispatch('github.'.$eventName, $event);
87+
} catch (\Exception $e) {
88+
throw new GitHubRuntimeException(sprintf('Failed dispatching "%s" event for "%s" repository.', $eventName, $repository), 0, $e);
89+
}
90+
91+
$responseData = $event->getResponseData();
92+
93+
if (empty($responseData)) {
94+
throw new GitHubPreconditionFailedException(sprintf('Unsupported event "%s"', $eventName));
95+
}
96+
97+
return $responseData;
98+
}
99+
100+
private function authenticate($hash, $key, $data)
101+
{
102+
if (!extension_loaded('hash')) {
103+
throw new GitHubInvalidConfigurationException('"hash" extension is needed to check request signature.');
104+
}
105+
106+
return $hash !== 'sha1='.hash_hmac('sha1', $data, $key);
107+
}
108+
}

‎src/AppBundle/Issues/IssueListener.php

Copy file name to clipboardExpand all lines: src/AppBundle/Issues/IssueListener.php
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
abstract class IssueListener implements EventSubscriberInterface
99
{
10-
private static $triggerWordToStatus = [
10+
protected static $triggerWordToStatus = [
1111
'needs review' => Status::NEEDS_REVIEW,
1212
'needs work' => Status::NEEDS_WORK,
1313
'works for me' => Status::WORKS_FOR_ME,
@@ -17,7 +17,7 @@ abstract class IssueListener implements EventSubscriberInterface
1717
/**
1818
* @var StatusApi
1919
*/
20-
private $statusApi;
20+
protected $statusApi;
2121

2222
public function __construct(StatusApi $statusApi)
2323
{

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.