Page MenuHomeDevCentral

No OneTemporary

diff --git a/app/Actions/ActionsReport.php b/app/Actions/ActionsReport.php
index a1ae343..7879aa4 100644
--- a/app/Actions/ActionsReport.php
+++ b/app/Actions/ActionsReport.php
@@ -1,90 +1,88 @@
<?php
namespace Nasqueron\Notifications\Actions;
class ActionsReport {
/**
* List of actions
*
* @var Action[]
*/
public $actions = [];
/**
* Report created date
*
* @var int
*/
public $created;
/**
* The entry gate
*
* @var string
*/
public $gate;
/**
* The entry door
*
* @var string
*/
public $door;
/**
* Initializes a new instance of an actions report
*/
public function __construct () {
$this->created = time();
}
///
/// Properties
///
/**
* Sets the gate and the door for this report
*
* @param string $gate The gate
* @param string $door The door
*/
public function attachToGate (string $gate, string $door) : void {
$this->gate = $gate;
$this->door = $door;
}
/**
* Adds an action to the list of actions to report
*
* @param Action $action The action to add
*/
public function addAction (Action $action) : void {
$this->actions[] = $action;
}
/**
* Determines if one of the action has failed.
- *
- * @return bool
*/
public function containsError () : bool {
foreach ($this->actions as $action) {
if ($action->error !== null) {
return true;
}
}
return false;
}
///
/// Output
///
/**
* Gets a JSON string representation of the current instance
*/
public function __toString () : string {
return json_encode($this, JSON_PRETTY_PRINT);
}
}
diff --git a/app/Analyzers/BasePayloadAnalyzer.php b/app/Analyzers/BasePayloadAnalyzer.php
index f531724..fd5f69b 100644
--- a/app/Analyzers/BasePayloadAnalyzer.php
+++ b/app/Analyzers/BasePayloadAnalyzer.php
@@ -1,180 +1,174 @@
<?php
namespace Nasqueron\Notifications\Analyzers;
use BadMethodCallException;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Storage;
abstract class BasePayloadAnalyzer {
///
/// Constants
///
/**
* The name of the service, used to get specific classes and config
*/
const SERVICE_NAME = "UnknownService";
///
/// Private members
///
/**
* The project name, used to load specific configuration and offer defaults
* @var string
*/
protected $project;
/**
* The request content, as a structured data
* @var \stdClass
*/
protected $payload;
/**
* The configuration for the payload analyzer
* @var PayloadAnalyzerConfiguration
*/
protected $configuration;
///
/// Constructor
///
/**
* Creates a new JenkinsPayloadAnalyzer instance.
*
* @param string $project
* @param \stdClass $payload
*/
public function __construct(string $project, \stdClass $payload) {
$this->project = $project;
$this->payload = $payload;
$this->loadConfiguration();
}
///
/// Configuration
///
/**
* The default name of the configuration file
*/
const CONFIG_DEFAULT_FILE = 'default.json';
/**
* Gets the full path to the configuration file.
- *
- * @return string
*/
public function getConfigurationFileName () : string {
$dir = Config::get(
'services.'
. strtolower(static::SERVICE_NAME)
. '.analyzer.configDir'
);
$filename = $dir . '/' . $this->project . '.json';
if (!Storage::exists($filename)) {
return $dir . '/' . static::CONFIG_DEFAULT_FILE;
}
return $filename;
}
/**
- * Gets full qualified class name for configuration.
- *
- * @return string
+ * Gets full qualified class name for configuration
*/
private function getCandidateConfigurationClassName() : string {
return 'Nasqueron\Notifications\Analyzers\\' . static::SERVICE_NAME //ns
. "\\"
. static::SERVICE_NAME . 'PayloadAnalyzerConfiguration'; // class
}
/**
* Gets full qualified class name for configuration if existing,
* or PayloadAnalyzerConfiguration class if not.
*
* @return string The configuration class to use
*/
private function getConfigurationClassName () : string {
$class = $this->getCandidateConfigurationClassName();
if (class_exists($class)) {
return $class;
}
return PayloadAnalyzerConfiguration::class;
}
/**
* Loads configuration for the analyzer
*/
public function loadConfiguration () : void {
$fileName = $this->getConfigurationFileName();
$class = $this->getConfigurationClassName();
$mapper = new \JsonMapper();
$this->configuration = $mapper->map(
json_decode(Storage::get($fileName)),
new $class($this->project)
);
}
///
/// Properties
///
/**
* Gets the name of the item.
*
* @var string
*/
public function getItemName () : string {
throw new BadMethodCallException(<<<MSG
The getItemName method must be implemented in the analyzer class if used.
MSG
);
}
/**
* Determines if the event isn't related to a specific item,
* but to the general service.
- *
- * @return bool
*/
public function isAdministrativeEvent () : bool {
return false;
}
/**
* Gets the group for a specific payload.
*
* @return string The group, central part of the routing key
*/
public function getGroup () : string {
// Some events are organization-level only and can't be mapped
// to projects.
if ($this->isAdministrativeEvent()) {
return $this->configuration->administrativeGroup;
}
// If the payload is about some repository matching a table of
// symbols, we need to sort it to the right group.
$item = $this->getItemName();
foreach ($this->configuration->map as $mapping) {
if ($mapping->doesItemBelong($item)) {
return $mapping->group;
}
}
return $this->configuration->getDefaultGroup();
}
}
diff --git a/app/Analyzers/DockerHub/BaseEvent.php b/app/Analyzers/DockerHub/BaseEvent.php
index 106d021..d4e3c37 100644
--- a/app/Analyzers/DockerHub/BaseEvent.php
+++ b/app/Analyzers/DockerHub/BaseEvent.php
@@ -1,48 +1,44 @@
<?php
namespace Nasqueron\Notifications\Analyzers\DockerHub;
abstract class BaseEvent {
/**
* @var \stdClass
*/
protected $payload;
/**
* Initializes a new instance of the BaseEvent object.
*
* @param \stdClass $payload The payload to analyze
*/
public function __construct (\stdClass $payload) {
$this->payload = $payload;
}
///
/// Public methods
///
/**
* Gets notification payload.
*
* This method allows analyzer to edit the payload.
*/
- public function getPayload () {
+ public function getPayload () : \stdClass {
return $this->payload;
}
/**
* Gets notification text for this event.
- *
- * @return string
*/
- abstract public function getText();
+ abstract public function getText() : string;
/**
* Gets notification link related to this event.
- *
- * @return string
*/
- abstract public function getLink();
+ abstract public function getLink() : string;
}
diff --git a/app/Analyzers/DockerHub/BuildFailureEvent.php b/app/Analyzers/DockerHub/BuildFailureEvent.php
index 35ec1c8..6047a33 100644
--- a/app/Analyzers/DockerHub/BuildFailureEvent.php
+++ b/app/Analyzers/DockerHub/BuildFailureEvent.php
@@ -1,67 +1,58 @@
<?php
namespace Nasqueron\Notifications\Analyzers\DockerHub;
use Nasqueron\Notifications\Facades\Mailgun;
class BuildFailureEvent extends BaseEvent {
/**
* Initializes a new instance of the BuildFailureEvent object.
*
* @param \stdClass $payload The payload to analyze
*/
public function __construct (\stdClass $payload) {
parent::__construct($payload);
$this->payload = $this->getMailGunPayload();
}
/**
* Gets a MailGun message.
- *
- * @return \stdClass
*/
- private function getMailGunPayload () {
+ private function getMailGunPayload () : \stdClass {
return Mailgun::fetchMessageFromPayload($this->payload);
}
- /**
- * @return string
- */
- private function getMailBody () {
+ private function getMailBody () : string {
$bodyProperty = 'body-plain';
return $this->payload->$bodyProperty;
}
/**
* Extracts a regular expression from the mail body.
*
* @param $string Regular expression
* @return string
*/
- private function extractFromBody ($regex) {
+ private function extractFromBody ($regex) : string {
preg_match($regex, $this->getMailBody(), $matches);
return $matches[1];
}
/**
* Gets text from payload.
- *
- * @return string
*/
- public function getText() {
+ public function getText() : string {
$repo = $this->extractFromBody("@\"(.*?\/.*?)\"@");
return "Image build by Docker Hub registry failure for $repo";
}
/**
* Gets link from payload.
- *
- * @return string
*/
- public function getLink() {
+ public function getLink() : string {
return $this->extractFromBody("@(https\:\/\/hub.docker.com\/r.*)@");
}
}
diff --git a/app/Analyzers/DockerHub/PushEvent.php b/app/Analyzers/DockerHub/PushEvent.php
index 8be97bb..4d2980f 100644
--- a/app/Analyzers/DockerHub/PushEvent.php
+++ b/app/Analyzers/DockerHub/PushEvent.php
@@ -1,28 +1,24 @@
<?php
namespace Nasqueron\Notifications\Analyzers\DockerHub;
class PushEvent extends BaseEvent {
/**
* Gets text from payload.
- *
- * @return string
*/
- public function getText() {
+ public function getText() : string {
$repo = $this->payload->repository->repo_name;
$who = $this->payload->push_data->pusher;
return "New image pushed to Docker Hub registry for $repo by $who";
}
/**
* Gets link from payload.
- *
- * @return string
*/
- public function getLink() {
+ public function getLink() : string {
return $this->payload->repository->repo_url;
}
}
diff --git a/app/Analyzers/GitHub/Events/CommitCommentEvent.php b/app/Analyzers/GitHub/Events/CommitCommentEvent.php
index d24087b..ae19ef1 100644
--- a/app/Analyzers/GitHub/Events/CommitCommentEvent.php
+++ b/app/Analyzers/GitHub/Events/CommitCommentEvent.php
@@ -1,38 +1,34 @@
<?php
namespace Nasqueron\Notifications\Analyzers\GitHub\Events;
/**
* CommitCommentEvent payload analyzer
*
* @link https://developer.github.com/v3/activity/events/types/#commitcommentevent
*/
class CommitCommentEvent extends Event {
/**
* Gets description for the payload
- *
- * @return string
*/
public function getDescription () : string {
$comment = $this->payload->comment;
return trans(
'GitHub.EventsDescriptions.CommitCommentEvent',
[
'author' => $comment->user->login,
'commit' => substr($comment->commit_id, 0, 8),
'excerpt' => self::cut($comment->body),
]
);
}
/**
* Gets link for the payload
- *
- * @return string
*/
public function getLink () : string {
return $this->payload->comment->html_url;
}
}
diff --git a/app/Analyzers/GitHub/Events/CreateEvent.php b/app/Analyzers/GitHub/Events/CreateEvent.php
index fb4bec3..a0b5023 100644
--- a/app/Analyzers/GitHub/Events/CreateEvent.php
+++ b/app/Analyzers/GitHub/Events/CreateEvent.php
@@ -1,78 +1,74 @@
<?php
namespace Nasqueron\Notifications\Analyzers\GitHub\Events;
/**
* CreateEvent payload analyzer
*
* We don't support the repository ref type, as, according the documentation,
* "webhooks will not receive this event for created repositories".webhooks
*
* Another case when we won't receive the event is when at least four tags are
* pushed at once.
*
* @link https://developer.github.com/v3/activity/events/types/#createevent
*/
class CreateEvent extends Event {
use WithRef;
/**
* Gets description for the payload
- *
- * @return string
*/
public function getDescription () : string {
$repository = $this->payload->repository->full_name;
$type = $this->payload->ref_type;
$ref = $this->payload->ref;
if (!self::isValidRefType($type)) {
return trans(
'GitHub.EventsDescriptions.CreateEventUnknown',
[
'type' => $type,
'ref' => $ref,
]
);
}
return trans(
'GitHub.EventsDescriptions.CreateEvent',
[
'type' => $type,
'ref' => $ref,
'repository' => $repository,
]
);
}
/**
* Gets link segments for the type
*
* @return array
* @SuppressWarnings(PHPMD.UnusedPrivateMethod)
*/
private function getLinkRefSegments () : array {
return [
'tag' => '/releases/tag/',
'branch' => '/tree/',
];
}
/**
* Gets link for the payload
- *
- * @return string
*/
public function getLink () : string {
$type = $this->payload->ref_type;
$ref = $this->payload->ref;
$url = $this->payload->repository->html_url;
$url .= $this->getLinkRefSegment($type);
$url .= $ref;
return $url;
}
}
diff --git a/app/Analyzers/GitHub/Events/DefaultBranchEvent.php b/app/Analyzers/GitHub/Events/DefaultBranchEvent.php
index d6b5bca..265a434 100644
--- a/app/Analyzers/GitHub/Events/DefaultBranchEvent.php
+++ b/app/Analyzers/GitHub/Events/DefaultBranchEvent.php
@@ -1,41 +1,37 @@
<?php
namespace Nasqueron\Notifications\Analyzers\GitHub\Events;
/**
* DefaultBranchEvent payload analyzer
*
* @link https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#repository
*/
class DefaultBranchEvent extends Event {
/**
* Gets description for the payload
- *
- * @return string
*/
public function getDescription () : string {
$repository = $this->payload->repository->full_name;
$old = $this->payload->changes->default_branch->from;
$new = $this->payload->repository->default_branch;
return trans(
'GitHub.EventsDescriptions.DefaultBranchEvent',
[
'old' => $old,
'new' => $new,
'repository' => $repository,
]
);
}
/**
* Gets link for the payload
- *
- * @return string
*/
public function getLink () : string {
return $this->payload->repository->html_url;
}
}
diff --git a/app/Analyzers/GitHub/Events/DeleteEvent.php b/app/Analyzers/GitHub/Events/DeleteEvent.php
index 20a8a6d..b2cb660 100644
--- a/app/Analyzers/GitHub/Events/DeleteEvent.php
+++ b/app/Analyzers/GitHub/Events/DeleteEvent.php
@@ -1,73 +1,67 @@
<?php
namespace Nasqueron\Notifications\Analyzers\GitHub\Events;
/**
* DeleteEvent payload analyzer
*
* Another case when we won't receive the event is when at least four tags are
* deleted at once.
*
* @link https://developer.github.com/v3/activity/events/types/#deleteevent
*/
class DeleteEvent extends Event {
use WithRef;
/**
* Gets description for the payload
- *
- * @return string
*/
public function getDescription () : string {
$repository = $this->payload->repository->full_name;
$type = $this->payload->ref_type;
$ref = $this->payload->ref;
if (!self::isValidRefType($type)) {
return trans(
'GitHub.EventsDescriptions.DeleteEventUnknown',
[
'type' => $type,
'ref' => $ref,
]
);
}
return trans(
'GitHub.EventsDescriptions.DeleteEvent',
[
'type' => $type,
'ref' => $ref,
'repository' => $repository,
]
);
}
/**
* Gets link segments for the type
- *
- * @return Array
* @SuppressWarnings(PHPMD.UnusedPrivateMethod)
*/
- private function getLinkRefSegments () {
+ private function getLinkRefSegments () : array {
return [
'tag' => '/tags',
'branch' => '/branches',
];
}
/**
* Gets link for the payload
- *
- * @return string
*/
public function getLink () : string {
$type = $this->payload->ref_type;
$url = $this->payload->repository->html_url;
$url .= $this->getLinkRefSegment($type);
return $url;
}
}
diff --git a/app/Analyzers/GitHub/Events/Event.php b/app/Analyzers/GitHub/Events/Event.php
index 33c7606..77b474f 100644
--- a/app/Analyzers/GitHub/Events/Event.php
+++ b/app/Analyzers/GitHub/Events/Event.php
@@ -1,88 +1,87 @@
<?php
namespace Nasqueron\Notifications\Analyzers\GitHub\Events;
class Event {
///
/// Properties
///
/**
* The payload
*
* @var \stdClass
*/
protected $payload;
///
/// Constructor
///
public function __construct ($payload) {
$this->payload = $payload;
}
///
/// Gets or initialize relevant class
///
/**
* Gets class name from the GitHub webhooks event name
*
* @param string $eventName The event name (e.g. commit_comment)
* @return string The event class name (e.g. CommitCommentEvent)
*/
- public static function getClass (string $eventName) {
+ public static function getClass (string $eventName) : string {
return __NAMESPACE__ . '\\' . self::toCamelCase($eventName) . 'Event';
}
private static function toCamelCase (string $string) : string {
return str_replace(" ", "", ucwords(str_replace("_", " ", $string)));
}
/**
* Gets an instance of the event class, from the
*
* @param string $eventName The event name (e.g. commit_comment)
- * @return Event
*/
- public static function forPayload (string $eventName, $payload) {
+ public static function forPayload (string $eventName, $payload) : Event {
$class = self::getClass($eventName);
if (!class_exists($class)) {
throw new \InvalidArgumentException(
"Class doesn't exist: $class (for $eventName)"
);
}
return new $class($payload);
}
///
/// Helper methods
///
/**
* Cuts a text
*
* @param string $text The text to cut
* @param int $strLen The amount of characters to allow [optional]
* @param string $symbol The symbol to append to a cut text [optional]
*/
public static function cut (
string $text,
int $strLen = 114,
string $symbol = '…'
- ) {
+ ) : string {
$len = strlen($text);
if ($len <= $strLen) {
return $text;
}
if ($strLen < 1) {
return $symbol;
}
return substr($text, 0, $strLen - 1) . $symbol;
}
}
diff --git a/app/Analyzers/GitHub/Events/ForkEvent.php b/app/Analyzers/GitHub/Events/ForkEvent.php
index 078a4fb..7609956 100644
--- a/app/Analyzers/GitHub/Events/ForkEvent.php
+++ b/app/Analyzers/GitHub/Events/ForkEvent.php
@@ -1,37 +1,33 @@
<?php
namespace Nasqueron\Notifications\Analyzers\GitHub\Events;
/**
* ForkEvent payload analyzer
*
* Triggered when a repository is forked.
*
* @link https://developer.github.com/v3/activity/events/types/#forkevent
*/
class ForkEvent extends Event {
/**
* Gets description for the payload
- *
- * @return string
*/
public function getDescription () : string {
return trans(
'GitHub.EventsDescriptions.ForkEvent',
[
'repo_base' => $this->payload->repository->full_name,
'repo_fork' => $this->payload->forkee->full_name,
]
);
}
/**
* Gets link for the payload
- *
- * @return string
*/
public function getLink () : string {
return $this->payload->forkee->html_url;
}
}
diff --git a/app/Analyzers/GitHub/Events/IssueCommentEvent.php b/app/Analyzers/GitHub/Events/IssueCommentEvent.php
index 40f5556..5d293dc 100644
--- a/app/Analyzers/GitHub/Events/IssueCommentEvent.php
+++ b/app/Analyzers/GitHub/Events/IssueCommentEvent.php
@@ -1,64 +1,60 @@
<?php
namespace Nasqueron\Notifications\Analyzers\GitHub\Events;
/**
* IssueCommentEvent payload analyzer
*
* @link https://developer.github.com/v3/activity/events/types/#issuecommentevent
*/
class IssueCommentEvent extends Event {
/**
* Determines if the action is valid.
*
* @param string $action The action to check
* @return bool true if the action is valid; otherwise, false
*/
- protected static function isValidAction (string $action) {
+ protected static function isValidAction (string $action) : bool {
$actions = ['created', 'edited', 'deleted'];
return in_array($action, $actions);
}
/**
* Gets description for the payload.
- *
- * @return string
*/
public function getDescription () : string {
$action = $this->payload->action;
if (!static::isValidAction($action)) {
return trans(
'GitHub.EventsDescriptions.IssueCommentEventUnknown',
['action' => $action]
);
}
$key = 'GitHub.EventsDescriptions.IssueCommentEventPerAction.';
$key .= $action;
$comment = $this->payload->comment;
$issue = $this->payload->issue;
return trans(
$key,
[
'author' => $comment->user->login,
'issueNumber' => $issue->number,
'issueTitle' => $issue->title,
'excerpt' => self::cut($comment->body),
]
);
}
/**
* Gets link for the payload.
- *
- * @return string
*/
public function getLink () : string {
return $this->payload->comment->html_url;
}
}
diff --git a/app/Analyzers/GitHub/Events/PingEvent.php b/app/Analyzers/GitHub/Events/PingEvent.php
index 7583108..fd562c6 100644
--- a/app/Analyzers/GitHub/Events/PingEvent.php
+++ b/app/Analyzers/GitHub/Events/PingEvent.php
@@ -1,35 +1,31 @@
<?php
namespace Nasqueron\Notifications\Analyzers\GitHub\Events;
/**
* PingEvent payload analyzer
*
* @link https://developer.github.com/webhooks/#ping-event
*/
class PingEvent extends Event {
/**
* Gets description for the payload
- *
- * @return string
*/
public function getDescription () : string {
return trans(
'GitHub.EventsDescriptions.PingEvent',
[
'zen' => $this->payload->zen,
'hook_id' => $this->payload->hook_id,
]
);
}
/**
* Gets link for the payload
- *
- * @return string
*/
public function getLink () : string {
return '';
}
}
diff --git a/app/Analyzers/GitHub/Events/PullRequestEvent.php b/app/Analyzers/GitHub/Events/PullRequestEvent.php
index bbe0b1e..39369b2 100644
--- a/app/Analyzers/GitHub/Events/PullRequestEvent.php
+++ b/app/Analyzers/GitHub/Events/PullRequestEvent.php
@@ -1,91 +1,85 @@
<?php
namespace Nasqueron\Notifications\Analyzers\GitHub\Events;
/**
* PingEvent payload analyzer
*
* @link https://developer.github.com/v3/activity/events/types/#pullrequestevent
*/
class PullRequestEvent extends Event {
/**
* Determines if the action is valid.
*
* @param string $action The action to check
* @return bool true if the action is valid; otherwise, false
*/
- protected static function isValidAction(string $action) {
+ protected static function isValidAction(string $action) : bool {
$actions = [
'assigned', 'unassigned',
'labeled', 'unlabeled',
'opened', 'closed',
'edited', 'reopened',
];
return in_array($action, $actions);
}
/**
* Gets description for the payload.
- *
- * @return string
*/
public function getDescription () : string {
$action = $this->payload->action;
if (!static::isValidAction($action)) {
return trans(
'GitHub.EventsDescriptions.PullRequestEventUnknown',
['action' => $action]
);
}
$key = 'GitHub.EventsDescriptions.PullRequestEventPerAction.';
$key .= $action;
return trans($key, $this->getLocalisationParameters());
}
/**
* Gets the parameters to pass to the localisation message
- *
- * @return array
*/
- private function getLocalisationParameters () {
+ private function getLocalisationParameters () : array {
$parameters = [
'author' => $this->payload->sender->login,
'number' => $this->payload->number,
'title' => $this->payload->pull_request->title,
];
switch ($this->payload->action) {
case 'assigned':
$parameters['assignee'] = $this->getLastAssignee();
break;
}
return $parameters;
}
/**
* @return string The last assignee username, or "" if there is no assignee.
*/
- private function getLastAssignee() {
+ private function getLastAssignee() : string {
$assignees = $this->payload->pull_request->assignees;
if (count($assignees) === 0) {
return ""; // No assignee.
}
$assignee = array_pop($assignees);
return $assignee->login;
}
/**
* Gets link for the payload.
- *
- * @return string
*/
public function getLink () : string {
return $this->payload->pull_request->html_url;
}
}
diff --git a/app/Analyzers/GitHub/Events/PushEvent.php b/app/Analyzers/GitHub/Events/PushEvent.php
index 6d005bb..b31d200 100644
--- a/app/Analyzers/GitHub/Events/PushEvent.php
+++ b/app/Analyzers/GitHub/Events/PushEvent.php
@@ -1,67 +1,63 @@
<?php
namespace Nasqueron\Notifications\Analyzers\GitHub\Events;
/**
* PushEvent payload analyzer
*
* @link https://developer.github.com/v3/activity/events/types/#pushevent
*/
class PushEvent extends Event {
use WithCommit;
use WithRepoAndBranch;
/**
* Gets the description message key according the amount of commits
*
* @param int $count The count of commits
* @return string The l10n message key for description
*/
- private static function getDescriptionMessageKey (int $count) {
+ private static function getDescriptionMessageKey (int $count) : string {
$key = 'GitHub.EventsDescriptions.PushEvent';
if ($count === 0) {
return $key . '.0';
}
return $key . '.n';
}
/**
* Gets description for the payload
- *
- * @return string
*/
public function getDescription () : string {
$n = count($this->payload->commits);
if ($n === 1) {
// If only one commit is pushed at the time,
// we want a description for this commit.
return $this->getHeadCommitDescription();
}
// Otherwise, we want a description for the push.
return trans(self::getDescriptionMessageKey($n), [
'user' => $this->payload->pusher->name,
'count' => $n,
'repoAndBranch' => $this->getWhere(),
]);
}
/**
* Gets link for the payload
- *
- * @return string
*/
public function getLink () : string {
$n = count($this->payload->commits);
if ($n === 1) {
return $this->payload->head_commit->url;
}
return $this->payload->compare;
}
}
diff --git a/app/Analyzers/GitHub/Events/RepositoryEvent.php b/app/Analyzers/GitHub/Events/RepositoryEvent.php
index ffa9335..3b6e84c 100644
--- a/app/Analyzers/GitHub/Events/RepositoryEvent.php
+++ b/app/Analyzers/GitHub/Events/RepositoryEvent.php
@@ -1,66 +1,62 @@
<?php
namespace Nasqueron\Notifications\Analyzers\GitHub\Events;
/**
* RepositoryEvent payload analyzer
*
* @link https://developer.github.com/v3/activity/events/types/#repositoryevent
*/
class RepositoryEvent extends Event {
/**
* Determines if the action is valid.
*
* @param string $action The action to check
* @return bool true if the action is valid; otherwise, false
*/
- protected static function isValidAction (string $action) {
+ protected static function isValidAction (string $action) : bool {
$actions = ['created', 'deleted', 'publicized', 'privatized'];
return in_array($action, $actions);
}
/**
* Gets description for the payload
- *
- * @return string
*/
public function getDescription () : string {
$action = $this->payload->action;
if (!static::isValidAction($action)) {
return trans(
'GitHub.EventsDescriptions.RepositoryEventUnknown',
['action' => $action]
);
}
$key = 'GitHub.EventsDescriptions.RepositoryEventPerAction.';
$key .= $action;
$repository = $this->payload->repository->full_name;
$message = trans($key, ['repository' => $repository]);
if ($this->payload->repository->fork) {
$message .= trans('GitHub.EventsDescriptions.RepositoryEventFork');
}
$description = (string)$this->payload->repository->description;
if ($description !== "") {
$message .= trans('GitHub.Separator');
$message .= $description;
}
return $message;
}
/**
* Gets link for the payload
- *
- * @return string
*/
public function getLink () : string {
return $this->payload->repository->html_url;
}
}
diff --git a/app/Analyzers/GitHub/Events/StatusEvent.php b/app/Analyzers/GitHub/Events/StatusEvent.php
index f7b5079..e23181d 100644
--- a/app/Analyzers/GitHub/Events/StatusEvent.php
+++ b/app/Analyzers/GitHub/Events/StatusEvent.php
@@ -1,65 +1,57 @@
<?php
namespace Nasqueron\Notifications\Analyzers\GitHub\Events;
/**
* StatusEvent payload analyzer
*
* @link https://developer.github.com/v3/activity/events/types/#statusevent
*/
class StatusEvent extends Event {
/**
* Gets state localized message
- *
- * @return string
*/
- private function getState () {
+ private function getState () : string {
$state = $this->payload->state; // pending, success, failure, or error
$key = 'GitHub.StatusEventState.' . $state;
return trans($key);
}
/**
* Gets status result
- *
- * @return string
*/
- private function getStatusResult () {
+ private function getStatusResult () : string {
$glue = trans('GitHub.Separator');
$fragments = array_filter([
$this->payload->context,
$this->payload->description,
$this->getState(),
]);
return implode($glue, $fragments);
}
/**
* Gets description for the payload
- *
- * @return string
*/
public function getDescription () : string {
return trans('GitHub.EventsDescriptions.StatusEvent', [
'commit' => substr($this->payload->sha, 0, 8),
'status' => $this->getStatusResult(),
]);
}
/**
* Gets link for the payload
- *
- * @return string
*/
public function getLink () : string {
$url = $this->payload->target_url;
if ($url === null) {
return "";
}
return $url;
}
}
diff --git a/app/Analyzers/GitHub/Events/UnknownEvent.php b/app/Analyzers/GitHub/Events/UnknownEvent.php
index 9e38340..44eeecc 100644
--- a/app/Analyzers/GitHub/Events/UnknownEvent.php
+++ b/app/Analyzers/GitHub/Events/UnknownEvent.php
@@ -1,45 +1,41 @@
<?php
namespace Nasqueron\Notifications\Analyzers\GitHub\Events;
/**
* Unknown event payload analyzer
*
* This is a fallack when no specific class exists for this event type.
*/
class UnknownEvent extends Event {
/**
* @var string
*/
private $eventType;
/**
* Initializes a new instance of the UnknownEvent class, an Event analyzer
* class to handle unknown events type.
*
* @param string $eventType The event type (e.g. push)
*/
public function __construct ($eventType, $payload = null) {
$this->eventType = $eventType;
parent::__construct($payload);
}
/**
* Gets description for the payload
- *
- * @return string
*/
public function getDescription () : string {
return "Some $this->eventType happened";
}
/**
* Gets link for the payload
- *
- * @return string
*/
public function getLink () : string {
return "";
}
}
diff --git a/app/Analyzers/GitHub/Events/WatchEvent.php b/app/Analyzers/GitHub/Events/WatchEvent.php
index c118d01..f929304 100644
--- a/app/Analyzers/GitHub/Events/WatchEvent.php
+++ b/app/Analyzers/GitHub/Events/WatchEvent.php
@@ -1,41 +1,37 @@
<?php
namespace Nasqueron\Notifications\Analyzers\GitHub\Events;
/**
* WatchEvent payload analyzer
*
* Triggered when an user stars a repository.
*
* The current starring action on GitHub is the old watch action. To avoid
* to break code, former event name have been kept in the API.
*
* @link https://developer.github.com/changes/2012-09-05-watcher-api/
* @link https://developer.github.com/v3/activity/events/types/#watchevent
*/
class WatchEvent extends Event {
/**
* Gets description for the payload
- *
- * @return string
*/
public function getDescription () : string {
return trans(
'GitHub.EventsDescriptions.WatchEvent',
[
'user' => $this->payload->sender->login,
'repository' => $this->payload->repository->full_name,
]
);
}
/**
* Gets link for the payload
- *
- * @return string
*/
public function getLink () : string {
return $this->payload->sender->html_url;
}
}
diff --git a/app/Analyzers/GitHub/Events/WithCommit.php b/app/Analyzers/GitHub/Events/WithCommit.php
index 87fc4aa..780dadb 100644
--- a/app/Analyzers/GitHub/Events/WithCommit.php
+++ b/app/Analyzers/GitHub/Events/WithCommit.php
@@ -1,63 +1,60 @@
<?php
namespace Nasqueron\Notifications\Analyzers\GitHub\Events;
/**
* Helper methods for events with a need to specify commit information
* (e.g. push)
*
* @link https://developer.github.com/v3/activity/events/types/#pushevent
*/
trait WithCommit {
/**
* Gets the title of the head commit
- * @return string
*/
- private function getHeadCommitTitle () {
+ private function getHeadCommitTitle () : string {
return static::getCommitTitle($this->payload->head_commit->message);
}
/**
* Extracts the commit title from the whole commit message.
*
* @param string $message The commit message
* @return string The commit title
*/
- public static function getCommitTitle (string $message) {
+ public static function getCommitTitle (string $message) : string {
// Discards extra lines
$pos = strpos($message, "\n");
if ($pos > 0) {
$message = substr($message, 0, $pos);
}
// Short messages are returned as is
// Longer messages are truncated
return self::cut($message, 72);
}
/**
* Gets the description text for the head commit.
- *
- * @return string
*/
- private function getHeadCommitDescription () {
+ private function getHeadCommitDescription () : string {
$commit = $this->payload->head_commit;
$committer = $commit->committer->username;
$author = $commit->author->username;
$message = trans('GitHub.Commits.Message', [
'committer' => $committer,
'title' => $this->getHeadCommitTitle(),
]);
if ($committer !== $author) {
$message .= trans('GitHub.Commits.Authored', [
'author' => $author,
]);
}
return $message;
}
}
diff --git a/app/Analyzers/GitHub/Events/WithRef.php b/app/Analyzers/GitHub/Events/WithRef.php
index 2454188..2d289a4 100644
--- a/app/Analyzers/GitHub/Events/WithRef.php
+++ b/app/Analyzers/GitHub/Events/WithRef.php
@@ -1,43 +1,43 @@
<?php
namespace Nasqueron\Notifications\Analyzers\GitHub\Events;
/**
* References helper methods for events using ref and ref_type fields.
* (e.g. create and delete)
*
* @link https://developer.github.com/v3/activity/events/types/#createevent
* @link https://developer.github.com/v3/activity/events/types/#deleteevent
*/
trait WithRef {
/**
* Determines if the ref type is valid.
*
* The ref type 'repository' is deemed invalid, as we shouldn't receive it.
*
* @param string $type The ref type to check
* @return bool true if the ref type id valid; otherwise, false
*/
- protected static function isValidRefType (string $type) {
+ protected static function isValidRefType (string $type) : bool {
$types = ['branch', 'tag'];
return in_array($type, $types);
}
/**
* Gets link ref segment for the payload
*
* @param string $type The reference type
* @return string the part of the URL for this reference type (e.g. /tree/)
*/
- protected function getLinkRefSegment (string $type) {
+ protected function getLinkRefSegment (string $type) : string {
$segments = $this->getLinkRefSegments();
if (!array_key_exists($type, $segments)) {
throw new \InvalidArgumentException;
}
return $segments[$type];
}
}
diff --git a/app/Analyzers/GitHub/GitHubPayloadAnalyzer.php b/app/Analyzers/GitHub/GitHubPayloadAnalyzer.php
index 58a18dd..6f63197 100644
--- a/app/Analyzers/GitHub/GitHubPayloadAnalyzer.php
+++ b/app/Analyzers/GitHub/GitHubPayloadAnalyzer.php
@@ -1,128 +1,121 @@
<?php
namespace Nasqueron\Notifications\Analyzers\GitHub;
use Nasqueron\Notifications\Analyzers\BasePayloadAnalyzer;
use Nasqueron\Notifications\Analyzers\GitHub\Events\Event;
use Nasqueron\Notifications\Analyzers\GitHub\Events\UnknownEvent;
class GitHubPayloadAnalyzer extends BasePayloadAnalyzer {
/**
* The name of the service, used to get specific classes and config
*/
const SERVICE_NAME = "GitHub";
///
/// Private members
///
/**
* The GitHub event triggering this request
* @var string
*/
private $event;
/**
* The payload analyzer event
*
* @var \Nasqueron\Notifications\Analyzers\GitHub\Events\Event
*/
private $analyzerEvent;
///
/// Constructor
///
/**
* Creates a new GitHubPayloadAnalyzer instance.
*
* @param string $project
* @param string $event
* @param \stdClass $payload
*/
public function __construct(
string $project,
string $event,
\stdClass $payload
) {
parent::__construct($project, $payload);
$this->event = self::getEvent($event, $payload);
try {
$this->analyzerEvent = Event::forPayload($event, $payload);
} catch (\InvalidArgumentException $ex) {
$this->analyzerEvent = new UnknownEvent($event);
}
}
private static function getEvent (string $event, \stdClass $payload) : string {
// Some payload uses a specialized event class.
if (
$event == "repository" && $payload->action == "edited" &&
property_exists($payload->changes, "default_branch")
) {
return "default_branch";
}
return $event;
}
///
/// Properties
///
/**
* Gets the name of the item, ie here of the name of the repository.
- *
- * @var string
*/
public function getItemName () : string {
if ($this->isAdministrativeEvent()) {
return '';
}
return $this->payload->repository->name;
}
///
/// Qualification of the payload
///
- /**
- * @return bool
- */
public function isAdministrativeEvent () : bool {
$administrativeEvents = [
'membership', // Member added to team
'ping', // Special ping pong event, fired on new hook
'repository', // Repository created
];
return in_array($this->event, $administrativeEvents);
}
///
/// Description of the payload
///
/**
* Gets a short textual description of the event.
- *
- * @return string
*/
public function getDescription () : string {
return $this->analyzerEvent->getDescription();
}
/**
* Gets a link to view the event on GitHub.
*
* @return string The most relevant URL
*/
public function getLink () : string {
return $this->analyzerEvent->getLink();
}
}
diff --git a/app/Analyzers/ItemGroupMapping.php b/app/Analyzers/ItemGroupMapping.php
index 4037b0d..cc995ad 100644
--- a/app/Analyzers/ItemGroupMapping.php
+++ b/app/Analyzers/ItemGroupMapping.php
@@ -1,65 +1,63 @@
<?php
namespace Nasqueron\Notifications\Analyzers;
use Illuminate\Support\Str;
/**
* Map items (repositories, projects, items, etc.) names to groups
*/
class ItemGroupMapping {
///
/// Properties
///
/**
* The group the mapped items belong to
*
* @var string
*/
public $group;
/**
* An array of the items to map, each item a string with the name of the
* repository, project or item used for mapping.
* The wildcard '*' is allowed to specify several items.
*
* @var array
*/
public $items = [];
///
/// Helper methods
///
/**
* Determines if the specified item matches a pattern.
*
* @param string $pattern The pattern, with * allowed as wildcard character
* @param string $item The item name to compare with the pattern
* @return bool
*/
public static function doesItemMatch (
string $pattern,
string $item
) : bool {
return Str::is($pattern, $item);
}
/**
* Determines if the specified item belong to this mapping
- *
- * @return bool
*/
public function doesItemBelong (string $actualItem) : bool {
foreach ($this->items as $candidateItem) {
if (static::doesItemMatch($candidateItem, $actualItem)) {
return true;
}
}
return false;
}
}
diff --git a/app/Analyzers/Jenkins/JenkinsPayloadAnalyzer.php b/app/Analyzers/Jenkins/JenkinsPayloadAnalyzer.php
index b004cee..7165fc5 100644
--- a/app/Analyzers/Jenkins/JenkinsPayloadAnalyzer.php
+++ b/app/Analyzers/Jenkins/JenkinsPayloadAnalyzer.php
@@ -1,82 +1,77 @@
<?php
namespace Nasqueron\Notifications\Analyzers\Jenkins;
use Nasqueron\Notifications\Analyzers\BasePayloadAnalyzer;
class JenkinsPayloadAnalyzer extends BasePayloadAnalyzer {
/**
* The name of the service, used to get specific classes and config
*/
const SERVICE_NAME = "Jenkins";
///
/// Payload custom properties
///
/**
* Gets the name of the item, ie here of the job.
*
* @var string
*/
public function getItemName () : string {
return $this->payload->name;
}
///
/// Notify only on failure helper methods
///
/**
* Tries to get build status.
*
* @param out string &$status
* @return bool indicates if the build status is defined in the payload
*/
private function tryGetBuildStatus (string &$status) : bool {
if (!isset($this->payload->build->status)) {
return false;
}
$status = $this->payload->build->status;
return true;
}
- /**
- * @return bool
- */
public function shouldNotifyOnlyOnFailure () : bool {
return in_array(
$this->getItemName(),
$this->configuration->notifyOnlyOnFailure
);
}
/**
* Determines if the build status is a failure.
- *
- * @return bool
*/
public function isFailure () : bool {
$status = "";
if (!$this->tryGetBuildStatus($status)) {
return false;
}
return $status === "FAILURE"
|| $status === "ABORTED"
|| $status === "UNSTABLE";
}
/**
* Indicates if we should handle this payload to trigger a notification.
*
* @return bool if false, this payload is to be ignored for notifications
*/
public function shouldNotify () : bool {
return $this->isFailure() || !$this->shouldNotifyOnlyOnFailure();
}
}
diff --git a/app/Analyzers/Phabricator/PhabricatorGroupMapping.php b/app/Analyzers/Phabricator/PhabricatorGroupMapping.php
index a89ff66..c4c90b8 100644
--- a/app/Analyzers/Phabricator/PhabricatorGroupMapping.php
+++ b/app/Analyzers/Phabricator/PhabricatorGroupMapping.php
@@ -1,39 +1,37 @@
<?php
namespace Nasqueron\Notifications\Analyzers\Phabricator;
use Nasqueron\Notifications\Analyzers\ItemGroupMapping;
use Nasqueron\Notifications\Phabricator\PhabricatorStory;
class PhabricatorGroupMapping extends ItemGroupMapping {
///
/// Extra properties
///
/**
* An array of words, each item a string with a word to find in the story.
*
* @var array
*/
public $words = [];
///
/// Helper methods to process words
///
/**
* Determines if the specified story belong to this mapping
- *
- * @return bool
*/
public function doesStoryBelong (PhabricatorStory $story) : bool {
foreach ($this->words as $word) {
if (stripos($story->text, $word) !== false) {
return true;
}
}
return false;
}
}
diff --git a/app/Config/Reporting/ServiceReportEntry.php b/app/Config/Reporting/ServiceReportEntry.php
index 1266c5f..148fe79 100644
--- a/app/Config/Reporting/ServiceReportEntry.php
+++ b/app/Config/Reporting/ServiceReportEntry.php
@@ -1,129 +1,127 @@
<?php
namespace Nasqueron\Notifications\Config\Reporting;
use Nasqueron\Notifications\Config\Services\Service;
use Nasqueron\Notifications\Facades\ProjectsMap;
final class ServiceReportEntry extends BaseReportEntry {
///
/// Private members
///
/**
* @var Service
*/
private $service;
///
/// Public properties
///
/**
* @var string
*/
public $gate;
/**
* @var string
*/
public $door;
/**
* @var string
*/
public $instance;
/**
* @var string
*/
public $status = "";
///
/// Constructor
///
public function __construct (Service $service) {
$this->service = $service;
$this->query();
}
///
/// Report builder
///
/**
* Queries the service to fill public properties.
*/
protected function query () : void {
// Direct properties
$this->gate = $this->service->gate;
$this->door = $this->service->door;
$this->instance = (string)$this->service->instance;
// Properties to query with business logic
$this->status = $this->getServiceStatus();
}
/**
* @return string An issue to fix, or an empty string if all looks good.
*/
protected function getServiceStatus () : string {
if ($this->isPhabricatorServiceWithNotCachedProjectsMap()) {
return "Projects map not cached.";
}
return "";
}
/**
* Determines if the service matches the following issue to report:
* - service is Phabricator
* - instance doesn't have the projects' name/PHID map in cache
- *
- * @return bool
*/
protected function isPhabricatorServiceWithNotCachedProjectsMap () : bool {
if ($this->service->gate !== 'Phabricator') {
return false;
}
$map = ProjectsMap::fetch($this->service->door);
return !$map->isCached();
}
///
/// Format
///
/**
* Gets the entry as an array. Formats empty string.
*
* @return string[]
*/
public function toArray () : array {
return [
$this->gate,
$this->door,
$this->instance,
$this->status,
];
}
/**
* Gets the entry as an array. Formats empty string.
*
* @return string[]
*/
public function toFancyArray () : array {
return [
$this->gate,
$this->door,
self::fancyString($this->instance, 'ø'),
self::fancyString($this->status, '✓'),
];
}
}
diff --git a/app/Config/Services/Services.php b/app/Config/Services/Services.php
index 22b5928..2d2fc10 100644
--- a/app/Config/Services/Services.php
+++ b/app/Config/Services/Services.php
@@ -1,107 +1,105 @@
<?php
namespace Nasqueron\Notifications\Config\Services;
use Illuminate\Support\Facades\Storage;
class Services {
///
/// Properties
///
/**
* @var Service[]
*/
public $services = [];
///
/// Constructors
///
/**
* @param string $file The JSON file to deserialize
* @return Services The deserialized instance
*/
public static function loadFromJson (string $file) : Services {
$data = json_decode(Storage::get($file));
$mapper = new \JsonMapper();
return $mapper->map($data, new self());
}
///
/// Methods to get a list of services
///
/**
* Gets the services found in credentials.json configuration file.
- *
- * @return Service[]
*/
- public function get () {
+ public function get () : array {
return $this->services;
}
/**
* Gets all the services for a specific gate.
*
* @param string $gate The gate (e.g. GitHub)
* @return Service[]
*/
public function getForGate (string $gate) : array {
$services = [];
foreach ($this->services as $service) {
if ($service->gate === $gate) {
$services[] = $service;
}
}
return $services;
}
///
/// Methods to find a service matching criteria
///
/**
* Gets the service for a specific gate and door
*
* @param string $gate The gate (e.g. GitHub)
* @param string $door The door (e.g. Nasqueron)
* @return Service|null The service information is found; otherwise, null.
*/
public function findServiceByDoor (string $gate, string $door) : ?Service {
foreach ($this->services as $service) {
if ($service->gate === $gate && $service->door === $door) {
return $service;
}
}
return null;
}
/**
* Finds a service for a specific gate, property and value
*
* @param string $gate The gate (e.g. Phabricator)
* @param string $property The property to check (e.g. instance)
* @param mixed $value The property value to find
* (e.g. 'http://devcentral.nasqueron.org')
* @return Service|null The service information is found; otherwise, null.
*/
public function findServiceByProperty (
string $gate,
string $property,
mixed $value
) : ?Service {
foreach ($this->services as $service) {
if ($service->gate === $gate && $service->$property === $value) {
return $service;
}
}
return null;
}
}
diff --git a/app/Console/Commands/ConfigShow.php b/app/Console/Commands/ConfigShow.php
index c69ed23..ec53de0 100644
--- a/app/Console/Commands/ConfigShow.php
+++ b/app/Console/Commands/ConfigShow.php
@@ -1,105 +1,105 @@
<?php
namespace Nasqueron\Notifications\Console\Commands;
use Illuminate\Console\Command;
use Nasqueron\Notifications\Config\Reporting\ConfigReport;
class ConfigShow extends Command {
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'config:show';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Show notifications center configuration';
/**
* @var \Nasqueron\Notifications\Config\Reporting\ConfigReport
*/
private $report;
///
/// Prepare information tables
///
/**
* Gets the services (defined in credentials.json) as table rows.
*
- * @return array
+ * @return string[][]
*/
protected function getServicesTableRows () : array {
$rows = [];
foreach ($this->report->services as $service) {
$rows[] = $service->toFancyArray();
}
return $rows;
}
/**
* Gets features as table rows
*
- * @return array
+ * @return string[][]
*/
protected function getFeaturesTableRows () : array {
$rows = [];
foreach ($this->report->features as $feature) {
$rows[] = $feature->toFancyArray();
}
return $rows;
}
///
/// Handle the command
///
/**
* Executes the console command.
*/
public function handle () : void {
$this->prepareReport();
$this->printGates();
$this->printFeatures();
$this->printServices();
}
protected final function prepareReport() : void {
$this->report = new ConfigReport();
}
protected final function printGates () : void {
$this->info("Gates:\n");
foreach ($this->report->gates as $gate) {
$this->line('- ' . $gate);
}
}
protected final function printFeatures () : void {
$this->info("\nFeatures:\n");
$this->table(
['Feature', 'Enabled'],
$this->getFeaturesTableRows()
);
}
protected final function printServices () : void {
$this->info("\nServices declared in credentials:\n");
$this->table(
['Gate', 'Door', 'Instance', 'Status'],
$this->getServicesTableRows()
);
}
}
diff --git a/app/Console/Commands/NotificationsPayload.php b/app/Console/Commands/NotificationsPayload.php
index 77d6f5a..8ce3065 100644
--- a/app/Console/Commands/NotificationsPayload.php
+++ b/app/Console/Commands/NotificationsPayload.php
@@ -1,225 +1,219 @@
<?php
namespace Nasqueron\Notifications\Console\Commands;
use Nasqueron\Notifications\Notifications\Notification;
use Nasqueron\Notifications\Phabricator\PhabricatorStory;
use Illuminate\Console\Command;
use InvalidArgumentException;
use ReflectionClass;
class NotificationsPayload extends Command {
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'notifications:payload {service} {payload} {args*}';
/**
* The console command description.
*
* @var string
*/
protected $description = <<<'TXT'
Gets a notification payload from a service payload
TXT;
/**
* The service to handle a payload for.
*
* @var string
*/
private $service;
/**
* The payload.
*
* @var string
*/
private $payload;
/**
* The parameters to pass to the notifications class constructor.
*
* An array with arguments' names as keys, arguments' values as values.
*
* @var array
*/
private $constructor;
/**
* Executes the console command.
*/
public function handle() : void {
if ($this->parseArguments()) {
$this->printNotification();
}
}
/**
* Parses arguments passed to the command.
*
* @return bool true if arguments looks good; otherwise, false.
*/
private function parseArguments () : bool {
try {
$this->parseService();
$this->parsePayload();
$this->parseConstructorParameters();
} catch (InvalidArgumentException $ex) {
$this->error($ex->getMessage());
return false;
}
return true;
}
/**
* Parses service argument.
*
* Fills it to the service property.
*
* @throws InvalidArgumentException when a notification class can't be
* found for the requested service.
*/
private function parseService () : void {
$this->service = $this->argument('service');
if (!class_exists($this->getNotificationClass())) {
throw new InvalidArgumentException(
"Unknown service: $this->service"
);
}
}
/**
* Parses path to the payload argument.
*
* Fills the content of the file to the payload property.
*
* @throws InvalidArgumentException when payload file is not found.
*/
private function parsePayload () : void {
$payloadFile = $this->argument('payload');
if (!file_exists($payloadFile)) {
throw new InvalidArgumentException("File not found: $payloadFile");
}
$this->payload = file_get_contents($payloadFile);
}
/**
* Parses all the extra arguments and sets the constructor property
* as an array of constructor arguments.
*
* @throws InvalidArgumentException on wrong arguments count.
*/
private function parseConstructorParameters () : void {
$keys = $this->getNotificationConstructorParameters();
$values = $this->argument('args');
$values['payload'] = $this->payload;
$this->constructor = self::argumentsArrayCombine($keys, $values);
$this->constructor['payload'] = $this->formatPayload();
}
/**
* Formats payload to pass to constructor
*
- * @return PhabricatorStory|stdClass A deserialization of the payload
+ * @return PhabricatorStory|\stdClass A deserialization of the payload
*/
- private function formatPayload() {
+ private function formatPayload() : \stdClass|PhabricatorStory {
if ($this->service === "Phabricator") {
$project = $this->constructor['project'];
return PhabricatorStory::loadFromJson($project, $this->payload);
}
return json_decode($this->payload);
}
/**
* Creates an array by using one array for keys and another for its values.
*
* @param array $keys
* @param array $values
* @return array
*
* @throws InvalidArgumentException when keys and values counts don't match
*/
public static function argumentsArrayCombine (
array $keys, array $values
) : array {
$countKeys = count($keys);
$countValues = count($values);
if ($countKeys != $countValues) {
throw new InvalidArgumentException(<<<MSG
Number of arguments mismatch: got $countValues but expected $countKeys.
MSG
);
}
return array_combine($keys, $values);
}
/**
* Initializes a new instance of the relevant notification class,
* with the arguments given in the constructor property.
- *
- * @return \Nasqueron\Notifications\Notifications\Notification
*/
private function getNotification () : Notification {
$class = $this->getNotificationClass();
$args = array_values($this->constructor);
return new $class(...$args);
}
/**
* Gets the notification in JSON format.
- *
- * @return string
*/
private function formatNotification () : string {
return json_encode($this->getNotification(), JSON_PRETTY_PRINT);
}
/**
* Prints the notification for the service, payload and specified arguments.
*/
private function printNotification () : void {
$this->line($this->formatNotification());
}
/**
- * Gets the notification class for the specified service.
- *
- * @return string
+ * Gets the notification class for the specified service
*/
private function getNotificationClass () : string {
$namespace = "Nasqueron\Notifications\Notifications\\";
return $namespace . $this->service . "Notification";
}
/**
* Gets an array with the parameters to pass to the constructor
* of the notification class for the specified service.
*
- * @return array
+ * @return string[]
*/
private function getNotificationConstructorParameters () : array {
$parameters = [];
$class = new ReflectionClass($this->getNotificationClass());
foreach ($class->getConstructor()->getParameters() as $parameter) {
$parameters[] = $parameter->getName();
}
return $parameters;
}
}
diff --git a/app/Contracts/APIClient.php b/app/Contracts/APIClient.php
index 7bfe9d1..5cf3b85 100644
--- a/app/Contracts/APIClient.php
+++ b/app/Contracts/APIClient.php
@@ -1,24 +1,24 @@
<?php
namespace Nasqueron\Notifications\Contracts;
interface APIClient {
/**
* Sets API end point
*
* @param string $url The API end point URL
* @return void
*/
- public function setEndPoint (string $url);
+ public function setEndPoint (string $url) : void;
/**
* Calls an API method
*
* @param string $method The method to call
* @param array $arguments The arguments to use
* @return mixed The API result
*/
- public function call (string $method, array $arguments = []);
+ public function call (string $method, array $arguments = []) : mixed;
}
diff --git a/app/Contracts/APIFactory.php b/app/Contracts/APIFactory.php
index 797a695..879c211 100644
--- a/app/Contracts/APIFactory.php
+++ b/app/Contracts/APIFactory.php
@@ -1,15 +1,14 @@
<?php
namespace Nasqueron\Notifications\Contracts;
interface APIFactory {
/**
* Gets an instance of the API client class
*
* @param string $endPoint The API end point
- * @return APIClient
*/
- public function get (string $endPoint);
+ public function get (string $endPoint) : APIClient;
}
diff --git a/app/Events/DockerHubPayloadEvent.php b/app/Events/DockerHubPayloadEvent.php
index 4372c91..ffff94a 100644
--- a/app/Events/DockerHubPayloadEvent.php
+++ b/app/Events/DockerHubPayloadEvent.php
@@ -1,52 +1,50 @@
<?php
namespace Nasqueron\Notifications\Events;
use Illuminate\Queue\SerializesModels;
class DockerHubPayloadEvent extends Event {
use SerializesModels;
/**
* The gate door which receives the request
* @var string
*/
public $door;
/**
* The event triggering this request
* @var string
*/
public $event;
/**
* The request content, as a structured data
* @var \stdClass
*/
public $payload;
/**
* Gets event according the kind of payload we receive.
- *
- * @return string
*/
public function getEvent () : string {
if (isset($this->payload->repository->repo_url)) {
return "push";
}
return "buildFailure";
}
/**
* Creates a new event instance.
*
* @param string $door
* @param \stdClass $payload
*/
public function __construct(string $door, \stdClass $payload) {
$this->door = $door;
$this->payload = $payload;
$this->event = $this->getEvent();
}
}
diff --git a/app/Events/PhabricatorPayloadEvent.php b/app/Events/PhabricatorPayloadEvent.php
index c08e4ec..3449960 100644
--- a/app/Events/PhabricatorPayloadEvent.php
+++ b/app/Events/PhabricatorPayloadEvent.php
@@ -1,54 +1,52 @@
<?php
namespace Nasqueron\Notifications\Events;
use Nasqueron\Notifications\Phabricator\PhabricatorStory;
use Illuminate\Queue\SerializesModels;
class PhabricatorPayloadEvent extends Event {
use SerializesModels;
/**
* The gate door which receives the request
* @var string
*/
public $door;
/**
* The raw payload
* @var iterable
*/
public $payload;
/**
* The story sent by the request
* @var PhabricatorStory
*/
public $story;
/**
* Gets story from the request
- *
- * @return PhabricatorStory
*/
- protected function getStory () {
+ protected function getStory () : PhabricatorStory {
return PhabricatorStory::loadFromIterable(
$this->door,
$this->payload
);
}
/**
* Creates a new event instance.
*
* @param string $door
* @param iterable $payload
*/
public function __construct(string $door, iterable $payload) {
$this->door = $door;
$this->payload = $payload;
$this->story = $this->getStory();
}
}
diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php
index 2d031b9..a9b46f0 100644
--- a/app/Exceptions/Handler.php
+++ b/app/Exceptions/Handler.php
@@ -1,75 +1,73 @@
<?php
namespace Nasqueron\Notifications\Exceptions;
use Nasqueron\Notifications\Facades\Raven;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Validation\ValidationException;
use Illuminate\Session\TokenMismatchException;
use Illuminate\Support\Facades\Config;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Exception;
class Handler extends ExceptionHandler {
/**
* A list of the exception types that should not be reported.
*
* @var string[]
*/
protected $dontReport = [
AuthorizationException::class,
CommandNotFoundException::class,
HttpException::class,
ModelNotFoundException::class,
TokenMismatchException::class,
ValidationException::class,
];
/**
* Reports or logs an exception.
*
* This is a great spot to send exceptions to Sentry, Bugsnag, etc.
*
* @param \Exception|\Throwable $e
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function report(Exception|\Throwable $e) : void {
if (!$this->shouldReport($e)) {
return;
}
if ($this->shouldReportToSentry()) {
$this->reportToSentry($e);
}
$log = $this->container->make(LoggerInterface::class);
$log->error((string)$e);
}
/**
* Determines if the error handler should report to Sentry
- *
- * @return bool
*/
protected function shouldReportToSentry () : bool {
return Raven::isConfigured() && Config::get('app.env') !== 'testing';
}
/**
* Reports the exception to Sentry
*
* @param Exception $e The exception to report
*/
protected function reportToSentry (Exception $e) : void {
Raven::captureException($e);
}
}
diff --git a/app/Facades/Broker.php b/app/Facades/Broker.php
index 9b1f6b7..1dd2c6b 100644
--- a/app/Facades/Broker.php
+++ b/app/Facades/Broker.php
@@ -1,21 +1,19 @@
<?php
namespace Nasqueron\Notifications\Facades;
use Illuminate\Support\Facades\Facade;
/**
* @see \Keruald\Broker\Broker
*/
class Broker extends Facade {
/**
* Gets the registered name of the component.
- *
- * @return string
*/
protected static function getFacadeAccessor() : string {
return 'broker';
}
}
diff --git a/app/Facades/DockerHub.php b/app/Facades/DockerHub.php
index 573905f..2359e67 100644
--- a/app/Facades/DockerHub.php
+++ b/app/Facades/DockerHub.php
@@ -1,21 +1,19 @@
<?php
namespace Nasqueron\Notifications\Facades;
use Illuminate\Support\Facades\Facade;
/**
* @see \Keruald\DockerHub\Build\TriggerBuildFactory
*/
class DockerHub extends Facade {
/**
* Gets the registered name of the component.
- *
- * @return string
*/
protected static function getFacadeAccessor() : string {
return 'dockerhub';
}
}
diff --git a/app/Facades/Mailgun.php b/app/Facades/Mailgun.php
index 5ca14eb..8ea5d37 100644
--- a/app/Facades/Mailgun.php
+++ b/app/Facades/Mailgun.php
@@ -1,21 +1,19 @@
<?php
namespace Nasqueron\Notifications\Facades;
use Illuminate\Support\Facades\Facade;
/**
* @see \Keruald\Mailgun\MailgunMessageFactory
*/
class Mailgun extends Facade {
/**
* Gets the registered name of the component.
- *
- * @return string
*/
protected static function getFacadeAccessor() : string {
return 'mailgun';
}
}
diff --git a/app/Facades/PhabricatorAPI.php b/app/Facades/PhabricatorAPI.php
index b3235a2..69dc18c 100644
--- a/app/Facades/PhabricatorAPI.php
+++ b/app/Facades/PhabricatorAPI.php
@@ -1,21 +1,19 @@
<?php
namespace Nasqueron\Notifications\Facades;
use Illuminate\Support\Facades\Facade;
/**
* @see \Nasqueron\Notifications\Phabricator\PhabricatorAPIFactory
*/
class PhabricatorAPI extends Facade {
/**
* Gets the registered name of the component.
- *
- * @return string
*/
protected static function getFacadeAccessor() : string {
return 'phabricator-api';
}
}
diff --git a/app/Facades/ProjectsMap.php b/app/Facades/ProjectsMap.php
index ccac4dc..c3b6827 100644
--- a/app/Facades/ProjectsMap.php
+++ b/app/Facades/ProjectsMap.php
@@ -1,21 +1,19 @@
<?php
namespace Nasqueron\Notifications\Facades;
use Illuminate\Support\Facades\Facade;
/**
* @see \Nasqueron\Notifications\Phabricator\PhabricatorAPIFactory
*/
class ProjectsMap extends Facade {
/**
* Gets the registered name of the component.
- *
- * @return string
*/
protected static function getFacadeAccessor() : string {
return 'phabricator-projectsmap';
}
}
diff --git a/app/Facades/Raven.php b/app/Facades/Raven.php
index 8473298..05a662c 100644
--- a/app/Facades/Raven.php
+++ b/app/Facades/Raven.php
@@ -1,28 +1,26 @@
<?php
namespace Nasqueron\Notifications\Facades;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Facade;
/**
* @see \Raven_Client
*/
class Raven extends Facade {
/**
* Gets the registered name of the component.
- *
- * @return string
*/
protected static function getFacadeAccessor() : string {
return 'raven';
}
/**
* Determines if a Sentry DSN is provided in the configuration
*/
public static function isConfigured () : bool {
return Config::get('services.sentry.dsn') !== null;
}
}
diff --git a/app/Facades/Report.php b/app/Facades/Report.php
index 6fe1da4..2da78c7 100644
--- a/app/Facades/Report.php
+++ b/app/Facades/Report.php
@@ -1,21 +1,19 @@
<?php
namespace Nasqueron\Notifications\Facades;
use Illuminate\Support\Facades\Facade;
/**
* @see \Keruald\Broker\Broker
*/
class Report extends Facade {
/**
* Gets the registered name of the component.
- *
- * @return string
*/
protected static function getFacadeAccessor() : string {
return 'report';
}
}
diff --git a/app/Facades/Services.php b/app/Facades/Services.php
index a846473..957f085 100644
--- a/app/Facades/Services.php
+++ b/app/Facades/Services.php
@@ -1,21 +1,19 @@
<?php
namespace Nasqueron\Notifications\Facades;
use Illuminate\Support\Facades\Facade;
/**
* @see \Nasqueron\Notifications\Config\Services\Services
*/
class Services extends Facade {
/**
* Gets the registered name of the component.
- *
- * @return string
*/
protected static function getFacadeAccessor() : string {
return 'services';
}
}
diff --git a/app/Http/Controllers/Gate/GateController.php b/app/Http/Controllers/Gate/GateController.php
index a3325ae..803b92f 100644
--- a/app/Http/Controllers/Gate/GateController.php
+++ b/app/Http/Controllers/Gate/GateController.php
@@ -1,120 +1,118 @@
<?php
namespace Nasqueron\Notifications\Http\Controllers\Gate;
use Nasqueron\Notifications\Config\Features;
use Nasqueron\Notifications\Config\Services\Service;
use Nasqueron\Notifications\Facades\Services;
use Nasqueron\Notifications\Facades\Report;
use Nasqueron\Notifications\Http\Controllers\Controller;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Response;
use Illuminate\View\View;
use Symfony\Component\HttpFoundation\Response as BaseResponse;
/**
* Represents a controller handling an entry-point for API payloads
*/
class GateController extends Controller {
///
/// Private members
///
/**
* @var string
*/
protected $door;
///
/// Requests
///
/**
* Handles GET requests
*/
public function onGet () : View {
// Virtually all the push APIs will send they payloads
// using a POST request, so we can provide a sensible
// default GET error message.
return view('gate/ispostonly');
}
/**
* Logs the request
*/
protected function logRequest (array $extraContextualData = []) : void {
Log::info('[Gate] New payload.', [
'service' => $this->getServiceName(),
'door' => $this->door,
] + $extraContextualData);
}
///
/// Reports
///
/**
* Initializes the report and registers it
*/
protected function initializeReport () : void {
if (Features::isEnabled('ActionsReport')) {
Report::attachToGate($this->getServiceName(), $this->door);
}
}
/**
* Renders the report
- *
- * @return \Symfony\Component\HttpFoundation\Response
*/
protected function renderReport () : BaseResponse {
if (!Features::isEnabled('ActionsReport')) {
return response("");
}
$report = App::make('report');
$statusCode = $report->containsError() ? 503 : 200;
return Response::json($report)
->setStatusCode($statusCode);
}
///
/// Credentials
///
/**
* Gets service credentials for this gate and door
*/
public function getService () : ?Service {
return Services::findServiceByDoor(
$this->getServiceName(),
$this->door
);
}
/**
* Checks if a registered service exists for this service and door.
*/
protected function doesServiceExist () : bool {
return $this->getService() !== null;
}
/**
* Gets secret for this service and door.
*
* @return string the secret, or if unknown, an empty string
*/
protected function getSecret () : string {
$service= $this->getService();
if ($service !== null) {
return $service->secret;
}
return "";
}
}
diff --git a/app/Jobs/FireJenkinsNotification.php b/app/Jobs/FireJenkinsNotification.php
index e162c35..880d141 100644
--- a/app/Jobs/FireJenkinsNotification.php
+++ b/app/Jobs/FireJenkinsNotification.php
@@ -1,49 +1,47 @@
<?php
namespace Nasqueron\Notifications\Jobs;
use Nasqueron\Notifications\Events\JenkinsPayloadEvent;
use Nasqueron\Notifications\Events\NotificationEvent;
use Nasqueron\Notifications\Notifications\JenkinsNotification;
use Illuminate\Support\Facades\Event;
class FireJenkinsNotification extends Job {
/**
* @var JenkinsPayloadEvent
*/
private $event;
/**
* Initializes a new instance of FireJenkinsNotification
*
* @param JenkinsPayloadEvent $event The event to notify
*/
public function __construct (JenkinsPayloadEvent $event) {
$this->event = $event;
}
///
/// Task
///
/**
* Executes the job.
- *
- * @return void
*/
public function handle() : void {
$notification = $this->createNotification();
if ($notification->shouldNotify()) {
Event::dispatch(new NotificationEvent($notification));
}
}
protected function createNotification() : JenkinsNotification {
return new JenkinsNotification(
$this->event->door, // project
$this->event->payload // raw content
);
}
}
diff --git a/app/Jobs/NotifyNewCommitsToDiffusion.php b/app/Jobs/NotifyNewCommitsToDiffusion.php
index acd151f..887f867 100644
--- a/app/Jobs/NotifyNewCommitsToDiffusion.php
+++ b/app/Jobs/NotifyNewCommitsToDiffusion.php
@@ -1,200 +1,198 @@
<?php
namespace Nasqueron\Notifications\Jobs;
use Nasqueron\Notifications\Actions\ActionError;
use Nasqueron\Notifications\Actions\NotifyNewCommitsAction;
use Nasqueron\Notifications\Events\ReportEvent;
use Nasqueron\Notifications\Facades\PhabricatorAPI;
use Nasqueron\Notifications\Phabricator\PhabricatorAPI as API;
use Nasqueron\Notifications\Phabricator\PhabricatorAPIException;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Log;
use RuntimeException;
/**
* This class allows to notify Phabricator of new commits, so daemons can pull
* these new commits and add them into Diffusion.
*/
class NotifyNewCommitsToDiffusion extends Job {
///
/// Private members
///
/**
* The clone URL of the repository
*
* @var string
*/
private $repository;
/**
* @var \Nasqueron\Notifications\Phabricator\PhabricatorAPI
*/
private $api;
/**
* @var string
*/
private $callSign;
/**
* @var NotifyNewCommitsAction
*/
private $actionToReport;
/**
* @var string
*/
private $sourceProject;
///
/// Constructor
///
/**
* Initializes a new instance of NotifyNewCommitsToDiffusion.
*/
public function __construct ($sourceProject, $repository) {
$this->sourceProject = $sourceProject;
$this->repository = $repository;
}
///
/// Task
///
/**
* Executes the job.
- *
- * @return void
*/
public function handle () : void {
if (!$this->fetchRequirements()) {
return;
}
$this->initializeReport();
$this->notifyPhabricator();
$this->sendReport();
}
/**
* Initializes the actions report.
*/
private function initializeReport () : void {
$this->actionToReport = new NotifyNewCommitsAction($this->callSign);
}
/**
* Notifies Phabricator to pull from the repository.
*/
private function notifyPhabricator () : void {
try {
$this->callDiffusionLookSoon();
} catch (PhabricatorAPIException $ex) {
$actionError = new ActionError($ex);
$this->actionToReport->attachError($actionError);
Log::error($ex);
}
}
/**
* Fires a report event with the actions report.
*/
private function sendReport () : void {
$event = new ReportEvent($this->actionToReport);
Event::dispatch($event);
}
///
/// Helper methods to find correct Phabricator instance and get the API
///
/**
* Gets the relevant Phabricator project for the specified source project.
*
* @return string The Phabricator project name
*/
private function getPhabricatorProject () : string {
return $this->sourceProject;
}
///
/// Helper methods to populate object members
///
/**
* Fetches API and call sign.
*
* @return bool true if all requirement have been fetched
*/
private function fetchRequirements () : bool {
return $this->fetchAPI() && $this->fetchCallSign();
}
/**
* Fetches the Phabricator API to use for the current source project.
*
* @return bool true if an API instance has been fetched
*/
private function fetchAPI () : bool {
$project = $this->getPhabricatorProject();
try {
$this->api = PhabricatorAPI::getForProject($project);
return true;
} catch (RuntimeException $ex) {
return false;
}
}
/**
* Fetches the call sign matching the repository.
*
* @return bool true if a call sign have been found ; otherwise, false.
*/
private function fetchCallSign () : bool {
$this->callSign = $this->getCallSign();
return $this->callSign !== "";
}
///
/// Helper methods to query Phabricator API
///
/**
* Gets the call sign matching the repository URL (e.g. "OPS").
*
* @return string the repository call sign, or "" if not in Phabricator
*/
private function getCallSign () : string {
$reply = $this->api->call(
'repository.query',
[ 'remoteURIs[0]' => $this->repository ]
);
if ($reply === null || !count($reply)) {
return "";
}
return API::getFirstResult($reply)->callsign;
}
/**
* Calls the diffusion.looksoon API method.
*
* @throws PhabricatorAPIException
*/
private function callDiffusionLookSoon () : void {
$this->api->call(
'diffusion.looksoon',
[ 'callsigns[0]' => $this->callSign ]
);
}
}
diff --git a/app/Jobs/SendMessageToBroker.php b/app/Jobs/SendMessageToBroker.php
index 014f17c..c9b7545 100644
--- a/app/Jobs/SendMessageToBroker.php
+++ b/app/Jobs/SendMessageToBroker.php
@@ -1,112 +1,110 @@
<?php
namespace Nasqueron\Notifications\Jobs;
use Nasqueron\Notifications\Actions\ActionError;
use Nasqueron\Notifications\Actions\AMQPAction;
use Nasqueron\Notifications\Events\ReportEvent;
use Nasqueron\Notifications\Facades\Broker;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Log;
class SendMessageToBroker extends Job {
///
/// Private members
///
/**
* The routing key, for topic exchange
*
* @var string
*/
private $routingKey = '';
/**
* The message to send
*
* @var string
*/
private $message = '';
/**
* The target exchange
*
* @var string
*/
private $target = '';
/**
* If not null, an exception thrown during the task
*
* @var \Exception
*/
private $exception;
///
/// Constructor
///
/**
* Creates a new job instance.
*
* @param string $target The queue or exchange to send the message to
* @param string $routingKey The routing key, for topic exchange
* @param string $message The message to send
*
* @return void
*/
public function __construct (
string $target,
string $routingKey,
string $message
) {
$this->target = $target;
$this->routingKey = $routingKey;
$this->message = $message;
}
///
/// Task
///
/**
* Executes the job.
- *
- * @return void
*/
public function handle() : void {
$this->sendMessage();
$this->report();
}
/**
* Sends the message to the broker.
*/
protected function sendMessage () : void {
try {
Broker::setExchangeTarget($this->target, "topic", true)
->routeTo($this->routingKey)
->sendMessage($this->message);
} catch (\Exception $ex) {
$this->exception = $ex;
Log::error($ex);
}
}
/**
* Prepares a report and fires a report event.
*/
protected function report () : void {
$actionToReport = new AMQPAction(
"publish",
$this->target,
$this->routingKey
);
if ($this->exception !== null) {
$actionToReport->attachError(new ActionError($this->exception));
}
Event::dispatch(new ReportEvent($actionToReport));
}
}
diff --git a/app/Jobs/TriggerDockerHubBuild.php b/app/Jobs/TriggerDockerHubBuild.php
index fc1bab7..886702d 100644
--- a/app/Jobs/TriggerDockerHubBuild.php
+++ b/app/Jobs/TriggerDockerHubBuild.php
@@ -1,85 +1,83 @@
<?php
namespace Nasqueron\Notifications\Jobs;
use Nasqueron\Notifications\Actions\ActionError;
use Nasqueron\Notifications\Actions\TriggerDockerHubBuildAction;
use Nasqueron\Notifications\Events\ReportEvent;
use Nasqueron\Notifications\Facades\DockerHub;
use Exception;
use Illuminate\Support\Facades\Event;
/**
* This class allows to trigger a new Docker Hub build.
*/
class TriggerDockerHubBuild extends Job {
///
/// Private members
///
/**
* @var string The Docker image to trigger a build for
*/
private $image;
/**
* @var TriggerDockerHubBuildAction
*/
private $actionToReport;
///
/// Constructor
///
/**
* Initializes a new instance of TriggerDockerHubBuild.
*/
public function __construct ($image) {
$this->image = $image;
}
///
/// Task
///
/**
* Executes the job.
- *
- * @return void
*/
public function handle () : void {
$this->initializeReport();
$this->triggerBuild();
$this->sendReport();
}
/**
* Initializes the actions report.
*/
private function initializeReport () : void {
$this->actionToReport = new TriggerDockerHubBuildAction($this->image);
}
/**
* Triggers a new Docker Hub build.
*/
private function triggerBuild () : void {
try {
DockerHub::build($this->image);
} catch (Exception $ex) {
$actionError = new ActionError($ex);
$this->actionToReport->attachError($actionError);
}
}
/**
* Fires a report event with the actions report.
*/
private function sendReport () : void {
$event = new ReportEvent($this->actionToReport);
Event::dispatch($event);
}
}
diff --git a/app/Notifications/DockerHubNotification.php b/app/Notifications/DockerHubNotification.php
index 960db47..c14eb8e 100644
--- a/app/Notifications/DockerHubNotification.php
+++ b/app/Notifications/DockerHubNotification.php
@@ -1,91 +1,87 @@
<?php
namespace Nasqueron\Notifications\Notifications;
use Nasqueron\Notifications\Analyzers\DockerHub\BaseEvent;
use InvalidArgumentException;
/**
* A Docker Hub notification.
*
* As we always sort them to the 'docker' group, and the registry only fires
* one kind of event, this is pretty straightforward without any need for
* configuration files or analyser class.
*
* HOW TO IMPLEMENT PAYLOADS SORT PER REPOSITORY?
*
* If you want to extend this to sort Docker images through some rules, we
* suggest you add a feature request to Docker to include source repository
* for the image, then call the GitHubPayloadAnalyzer with this repo instead
* of implementing a new one. This will allows to avoid to maintain two sets
* of configuration, one for the GitHub repos, one for the Docker repos.
*
* Even without that, you can probably be safe with a class or a method to map
* GitHub and Docker names, either because they are the same, either because
* there is a prefix: e.g. nasqueron/arcanist and nasqueron/docker-arcanist.
*/
class DockerHubNotification extends Notification {
public function __construct (
string $project,
string $event,
\stdClass $payload
) {
// Straightforward properties
$this->service = "DockerHub";
$this->project = $project;
$this->type = $event;
$this->rawContent = $payload;
$this->group = "docker";
// Properties from the payload
$this->analyzeByEvent();
}
///
/// Analyze by event
///
/**
* Fills properties from event payload.
*/
public function analyzeByEvent () : void {
$analyzer = $this->getAnalyzer();
$this->rawContent = $analyzer->getPayload();
$this->text = $analyzer->getText();
$this->link = $analyzer->getLink();
}
/**
* Gets analyzer class name for the current event.
- *
- * @return string
*/
private function getAnalyzerClassName () : string {
return "Nasqueron\Notifications\Analyzers\DockerHub\\"
. ucfirst($this->type)
. "Event";
}
/**
* Gets analyzer for the current event.
- *
- * @return \Nasqueron\Notifications\Analyzers\DockerHub\BaseEvent
*/
private function getAnalyzer () : BaseEvent {
$class = $this->getAnalyzerClassName();
if (!class_exists($class)) {
throw new InvalidArgumentException(
"Event $this->type doesn't have a matching $class class."
);
}
return new $class($this->rawContent);
}
}
diff --git a/app/Notifications/GitHubNotification.php b/app/Notifications/GitHubNotification.php
index 6ffa0b5..55dfdf7 100644
--- a/app/Notifications/GitHubNotification.php
+++ b/app/Notifications/GitHubNotification.php
@@ -1,73 +1,69 @@
<?php
namespace Nasqueron\Notifications\Notifications;
use Nasqueron\Notifications\Analyzers\GitHub\GitHubPayloadAnalyzer;
class GitHubNotification extends Notification {
/**
* @var GitHubPayloadAnalyzer
*/
private $analyzer = null;
public function __construct (
string $project,
string $event,
\stdClass $payload
) {
// Straightforward properties
$this->service = "GitHub";
$this->project = $project;
$this->type = $event;
$this->rawContent = $payload;
// Analyzes and fills
$this->group = $this->getGroup();
$this->text = $this->getText();
$this->link = $this->getLink();
}
/**
* Gets analyzer
*/
private function getAnalyzer () : GitHubPayloadAnalyzer {
if ($this->analyzer === null) {
$this->analyzer = new GitHubPayloadAnalyzer(
$this->project,
$this->type,
$this->rawContent
);
}
return $this->analyzer;
}
/**
* Gets the target notificatrion group
*
* @return string the target group for the notification
*/
public function getGroup () : string {
return $this->getAnalyzer()->getGroup();
}
/**
* Gets the notification text.
* Intended to convey a short message (thing Twitter or IRC).
- *
- * @return string
*/
public function getText () : string {
return $this->getAnalyzer()->getDescription();
}
/**
* Gets the notification URL. Intended to be a widget or icon link.
- *
- * @return string
*/
public function getLink () : string {
return $this->getAnalyzer()->getLink();
}
}
diff --git a/app/Notifications/JenkinsNotification.php b/app/Notifications/JenkinsNotification.php
index aa01357..7f482d8 100644
--- a/app/Notifications/JenkinsNotification.php
+++ b/app/Notifications/JenkinsNotification.php
@@ -1,112 +1,104 @@
<?php
namespace Nasqueron\Notifications\Notifications;
use Nasqueron\Notifications\Analyzers\Jenkins\JenkinsPayloadAnalyzer;
/**
* A Jenkins notification.
*
* This handles the JSON payloads sent by the following plugin:
* https://wiki.jenkins-ci.org/display/JENKINS/Notification+Plugin
*/
class JenkinsNotification extends Notification {
/**
* @var \Nasqueron\Notifications\Analyzers\Jenkins\JenkinsPayloadAnalyzer
*/
private $analyzer = null;
/**
* Initializes a new instance of the JenkinsNotification class.
*
* @param string $project The project this message is for
* @param mixed $payload The message fired by Jenkins notification plugin
*/
public function __construct (string $project, mixed $payload) {
// Straightforward properties
$this->service = "Jenkins";
$this->project = $project;
$this->rawContent = $payload;
// Properties from the payload
$this->group = $this->getGroup();
$this->text = $this->getText();
$this->link = $payload->build->full_url;
$this->type = $this->getType();
}
/**
* Gets the notification type.
- *
- * @return string
*/
public function getType () : string {
$build = $this->rawContent->build;
$type = strtolower($build->phase);
if (property_exists($build, 'status')) {
$type .= '.';
$type .= $build->status;
}
return strtolower($type);
}
/**
* Gets the notification text. Intended to convey a short message
* (thing Twitter or IRC).
- *
- * @return string
*/
public function getText () : string {
$name = $this->rawContent->name;
$build = $this->rawContent->build;
$phase = strtolower($build->phase);
$text = "Jenkins job $name has been $phase";
if (property_exists($build, 'status')) {
$status = strtolower($build->status);
$text .= ": $status";
}
return $text;
}
/**
* Gets analyzer
- *
- * @return \Nasqueron\Notifications\Analyzers\Jenkins\JenkinsPayloadAnalyzer
*/
private function getAnalyzer () : JenkinsPayloadAnalyzer {
if ($this->analyzer === null) {
$this->analyzer = new JenkinsPayloadAnalyzer(
$this->project,
$this->rawContent
);
}
return $this->analyzer;
}
/**
* Gets the notification group.
- *
- * @return string
*/
public function getGroup () : string {
return $this->getAnalyzer()->getGroup();
}
/**
* Indicates if we should handle this payload to trigger a notification.
*
* @return bool if false, this payload is to be ignored for notifications
*/
public function shouldNotify () : bool {
return $this->getAnalyzer()->shouldNotify();
}
}
diff --git a/app/Notifications/PhabricatorNotification.php b/app/Notifications/PhabricatorNotification.php
index 697a5ce..2649dae 100644
--- a/app/Notifications/PhabricatorNotification.php
+++ b/app/Notifications/PhabricatorNotification.php
@@ -1,67 +1,63 @@
<?php
namespace Nasqueron\Notifications\Notifications;
use Nasqueron\Notifications\Analyzers\Phabricator\PhabricatorPayloadAnalyzer;
use Nasqueron\Notifications\Phabricator\PhabricatorStory;
class PhabricatorNotification extends Notification {
/**
* @var PhabricatorPayloadAnalyzer
*/
private $analyzer = null;
/**
* Initializes a new PhabricatorNotification instance
*
* @param string $project The project for this notification
* @param PhabricatorStory $payload The story to convert into a notification
*/
public function __construct (string $project, PhabricatorStory $payload) {
// Straightforward properties
$this->service = "Phabricator";
$this->project = $project;
$this->rawContent = $payload;
$this->text = $payload->text;
// Analyzes and fills
$this->type = $payload->getObjectType();
$this->group = $this->getGroup();
$this->link = $this->getLink();
}
/**
* Gets analyzer
- *
- * @return \Nasqueron\Notifications\Analyzers\Phabricator\PhabricatorPayloadAnalyzer
*/
private function getAnalyzer () : PhabricatorPayloadAnalyzer {
if ($this->analyzer === null) {
$this->analyzer = new PhabricatorPayloadAnalyzer(
$this->project,
$this->rawContent
);
}
return $this->analyzer;
}
/**
* Gets the target notificatrion group
*
* @return string the target group for the notification
*/
public function getGroup () : string {
return $this->getAnalyzer()->getGroup();
}
/**
* Gets the notification URL. Intended to be a widget or icon link.
- *
- * @return string
*/
public function getLink () : string {
return "";
}
}
diff --git a/app/Phabricator/PhabricatorAPI.php b/app/Phabricator/PhabricatorAPI.php
index 139352b..e48b107 100644
--- a/app/Phabricator/PhabricatorAPI.php
+++ b/app/Phabricator/PhabricatorAPI.php
@@ -1,164 +1,161 @@
<?php
namespace Nasqueron\Notifications\Phabricator;
use Nasqueron\Notifications\Contracts\APIClient;
use Nasqueron\Notifications\Facades\Services;
class PhabricatorAPI implements APIClient {
///
/// Private members
///
/**
* The Phabricator main URL
*
* @var string
*/
private $endPoint;
/**
* The token generated at /settings/panel/apitokens/ to query the API
*
* @var string
*/
private $apiToken;
///
/// Constructors
///
/**
* Initializes a new instance of the Phabricator API class
*
* @param string $endPoint The Phabricator main URL, without trailing slash
* @param string $apiToken The token generated at /settings/panel/apitokens/
*/
public function __construct (string $endPoint, string $apiToken) {
$this->endPoint = $endPoint;
$this->apiToken = $apiToken;
}
/**
* @throws \RuntimeException when the service isn't in credentials.json
*/
public static function forInstance ($instance) : PhabricatorAPI {
$service = Services::findServiceByProperty(
'Phabricator',
'instance',
$instance
);
if ($service === null) {
throw new \RuntimeException(
"No credentials for Phabricator instance $instance."
);
}
return new self($service->instance, $service->secret);
}
/**
* @throws \RuntimeException when the service isn't in credentials.json
*/
- public static function forProject ($project) {
+ public static function forProject ($project) : PhabricatorAPI {
$service = Services::findServiceByDoor('Phabricator', $project);
if ($service === null) {
throw new \RuntimeException(
"No credentials for Phabricator project $project."
);
}
return new self($service->instance, $service->secret);
}
///
/// APIClient implementation
///
/**
* Sets API end point
*
* @param string $url The API end point URL
*/
- public function setEndPoint (string $url) {
+public function setEndPoint (string $url) : void {
$this->endPoint = $url;
}
/**
* Calls a Conduit API method
*
* @param string $method The method to call (e.g. repository.create)
* @param array $arguments The arguments to use
*
* @return mixed The API result
*/
- public function call (string $method, array $arguments = []) {
+ public function call (string $method, array $arguments = []) : mixed {
$url = $this->endPoint . '/api/' . $method;
$arguments['api.token'] = $this->apiToken;
$reply = json_decode(static::post($url, $arguments));
if ($reply->error_code !== null) {
throw new PhabricatorAPIException(
$reply->error_code,
$reply->error_info
);
}
return $reply->result;
}
///
/// Helper methods
///
/**
* Gets the first result of an API reply.
- *
- * @param iterable $reply
- * @return mixed
*/
- public static function getFirstResult (iterable $reply) {
+ public static function getFirstResult (iterable $reply) : mixed {
if (is_object($reply) && property_exists($reply, 'data')) {
$reply = $reply->data;
}
foreach ($reply as $value) {
return $value;
}
}
///
/// CURL session
///
- protected static function getPostFields ($arguments) {
+ protected static function getPostFields ($arguments) : string {
$items = [];
foreach ($arguments as $key => $value) {
$items[] = urlencode($key) . '=' . urlencode($value);
}
return implode('&', $items);
}
- protected static function post ($url, $arguments) {
+ protected static function post ($url, $arguments) : bool|string {
$options = [
CURLOPT_URL => $url,
CURLOPT_HEADER => false,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => 1,
CURLOPT_POSTFIELDS => static::getPostFields($arguments),
];
$ch = curl_init();
curl_setopt_array($ch, $options);
$result = curl_exec($ch);
curl_close($ch);
if ($result === false) {
throw new \RuntimeException(
"Can't reach Phabricator API endpoint: $url"
);
}
return $result;
}
}
diff --git a/app/Phabricator/PhabricatorAPIFactory.php b/app/Phabricator/PhabricatorAPIFactory.php
index 8127f21..3fc284e 100644
--- a/app/Phabricator/PhabricatorAPIFactory.php
+++ b/app/Phabricator/PhabricatorAPIFactory.php
@@ -1,28 +1,28 @@
<?php
namespace Nasqueron\Notifications\Phabricator;
use Nasqueron\Notifications\Contracts\APIFactory;
class PhabricatorAPIFactory implements APIFactory {
/**
* Gets an instance of the Phabricator API client class.
*
* @param string $instance The Phabricator instance
* @return \Nasqueron\Notifications\Phabricator\PhabricatorAPI
*/
- public function get (string $instance) {
+ public function get (string $instance) : PhabricatorAPI {
return PhabricatorAPI::forInstance($instance);
}
/**
* Gets an instance of the Phabricator API client class for a project.
*
* @param string $project The Phabricator project name
* @return PhabricatorAPI
*/
- public function getForProject (string $project) {
+ public function getForProject (string $project) : PhabricatorAPI {
return PhabricatorAPI::forProject($project);
}
}
diff --git a/app/Phabricator/PhabricatorStory.php b/app/Phabricator/PhabricatorStory.php
index 2bf1837..ce0540f 100644
--- a/app/Phabricator/PhabricatorStory.php
+++ b/app/Phabricator/PhabricatorStory.php
@@ -1,331 +1,331 @@
<?php
namespace Nasqueron\Notifications\Phabricator;
use InvalidArgumentException;
class PhabricatorStory {
///
/// Properties
///
/**
* The Phabricator instance name
*
* @var string
*/
public $instanceName;
/**
* The unique identifier Phabricator assigns to each story
*
* @var int
*/
public $id;
/**
* Type of story (e.g. PhabricatorApplicationTransactionFeedStory)
*
* @var string
*/
public $type;
/**
* @var array|null
*/
public $data;
/**
* The person logged to Phabricator and triggering the event
*
* @var string
*/
public $authorPHID;
/**
* A short English textual description of the event
*
* @var string
*/
public $text;
/**
* The unixtime the event occured
*
* @var int
*/
public $epoch;
/**
* The projects attached to this story.
*
* When there is no project, [].
* When not yet queried, null.
*
* @var string[]|null
*/
private $projects = null;
///
/// Constructors
///
/**
* Initializes a new instance of the Phabricator story class
*
* @param string $instanceName The Phabricator instance name
*/
public function __construct (string $instanceName) {
$this->instanceName = $instanceName;
}
/**
* Initializes a new instance of PhabricatorStory from an iterable.
*
* This is intended to parse the feed.hooks payloads.
*
* @param string $instanceName The Phabricator instance name
* @param iterable $payload The data submitted by Phabricator
* @return PhabricatorStory
*/
public static function loadFromIterable (
string $instanceName, iterable $payload
- ) {
+ ) : PhabricatorStory {
$instance = new self($instanceName);
foreach ($payload as $key => $value) {
$property = self::mapPhabricatorFeedKey($key);
$instance->$property = $value;
}
return $instance;
}
public static function loadFromJson (
$instanceName,
$payload
- ) {
+ ) : PhabricatorStory {
$array = json_decode($payload, true);
if (!is_array($array)) {
throw new InvalidArgumentException(<<<MSG
Payload should be deserializable as an array.
MSG
);
}
return self::loadFromIterable($instanceName, $array);
}
///
/// Helper methods
///
private function hasVoidObjectType () : bool {
return $this->data === null || !isset($this->data['objectPHID']);
}
/**
* Gets object type (e.g. TASK for PHID-TASK-l34fw5wievp6n6rnvpuk)
*
* @return string The object type, as a 4 letters string (e.g. 'TASK')
*/
- public function getObjectType () {
+ public function getObjectType () : string {
if ($this->hasVoidObjectType()) {
return 'VOID';
}
return substr($this->data['objectPHID'], 5, 4);
}
/**
* Gets the identifier of the projets related to this task
*
* return string[] The list of project PHIDs
*/
- public function getProjectsPHIDs () {
+ public function getProjectsPHIDs () : array {
if (!array_key_exists('objectPHID', $this->data)) {
return [];
}
$objectPHID = $this->data['objectPHID'];
$objectType = $this->getObjectType();
switch ($objectType) {
case 'DREV':
return $this->getItemProjectsPHIDs(
'repository.query',
$this->getRepositoryPHID('differential.query')
);
case 'TASK':
return $this->getItemProjectsPHIDs(
'maniphest.query',
$objectPHID
);
case 'CMIT':
return $this->getItemProjectsPHIDs(
'repository.query',
$this->getRepositoryPHID('diffusion.querycommits')
);
case 'PSTE':
return $this->getItemProjectsPHIDsThroughApplicationSearch(
'paste.search',
$objectPHID
);
default:
return [];
}
}
/**
* Gets the PHID of a repository
*
* @param string $method The API method to call (e.g. differential.query)
* @return string The repository PHID or "" if not found
*/
- public function getRepositoryPHID (string $method) {
+ public function getRepositoryPHID (string $method) : string {
$objectPHID = $this->data['objectPHID'];
$api = PhabricatorAPI::forProject($this->instanceName);
$reply = $api->call(
$method,
[ 'phids[0]' => $objectPHID ]
);
if ($reply === []) {
return "";
}
$apiResult = PhabricatorAPI::getFirstResult($reply);
if ($apiResult === null) {
// Repository information can't be fetched (T1136).
// This occurs when the bot account used to fetch information
// doesn't have access to the repository, for example if it's
// in a private space or restricted to a group it doesn't belong to.
return "";
}
return $apiResult->repositoryPHID;
}
/**
* Gets the projects for a specific item
*
* @param string $method The API method to call (e.g. differential.query)
* @param string $objectPHID The object PHID to pass as method parameter
* @return string[] The list of project PHIDs
*/
- public function getItemProjectsPHIDs (string $method, string $objectPHID) {
+ public function getItemProjectsPHIDs (string $method, string $objectPHID) : array {
if (!$objectPHID) {
return [];
}
$api = PhabricatorAPI::forProject($this->instanceName);
$reply = $api->call(
$method,
[ 'phids[0]' => $objectPHID ]
);
if ($reply === []) {
return [];
}
return PhabricatorAPI::getFirstResult($reply)->projectPHIDs;
}
/**
* Gets the project for a specific item, using the new ApplicationSearch.
*
* This is a transitional method: when every Phabricator will have been
* migrated from info (generation 1) or query (generation 2) to search
* (generation 3), we'll rename it to getItemProjectsPHIDs and overwrite it.
*/
protected function getItemProjectsPHIDsThroughApplicationSearch (
$method,
$objectPHID
- ) {
+ ) : array {
if (!$objectPHID) {
return [];
}
$api = PhabricatorAPI::forProject($this->instanceName);
$reply = $api->call(
$method,
[
'constraints[phids][0]' => $objectPHID,
'attachments[projects]' => 1
]
);
$apiResult = PhabricatorAPI::getFirstResult($reply);
if ($apiResult === null) {
// Object information (e.g. a paste) can't be fetched (T1138).
// This occurs when the bot account used to fetch information
// doesn't have access to the object, for example if it's
// in a private space or restricted to a group it doesn't belong to.
return [];
}
return $apiResult->attachments->projects->projectPHIDs;
}
/**
* Gets the list of the projects associated to the story
*
* @return string[] The list of project PHIDs
*/
- public function getProjects () {
+ public function getProjects () : array {
if ($this->projects === null) {
$this->attachProjects();
}
return $this->projects;
}
/**
* Queries the list of the projects associated to the story
* and attached it to the projects property.
*/
- public function attachProjects () {
+ public function attachProjects () : void {
$this->projects = [];
$PHIDs = $this->getProjectsPHIDs();
if (count($PHIDs) == 0) {
// No project is attached to the story's object
return;
}
$map = ProjectsMap::load($this->instanceName);
foreach ($PHIDs as $PHID) {
$this->projects[] = $map->getProjectName($PHID);
}
}
///
/// Static helper methods
///
/**
* Maps a field of the API reply to a property of the PhabricatorStory class
*
* @param string $key The field of the API reply
* @return string The property's name
*/
- public static function mapPhabricatorFeedKey (string $key) {
+ public static function mapPhabricatorFeedKey (string $key) : string {
if ($key == "storyID") {
return "id";
}
if (str_starts_with($key, "story") && strlen($key) > 5) {
return lcfirst(substr($key, 5));
}
return $key;
}
}
diff --git a/app/Phabricator/ProjectsMap.php b/app/Phabricator/ProjectsMap.php
index 2af59d0..81acede 100644
--- a/app/Phabricator/ProjectsMap.php
+++ b/app/Phabricator/ProjectsMap.php
@@ -1,285 +1,277 @@
<?php
namespace Nasqueron\Notifications\Phabricator;
use Nasqueron\Notifications\Contracts\APIClient as APIClient;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Cache;
class ProjectsMap implements \IteratorAggregate, \ArrayAccess {
///
/// Private properties and constants
///
/**
* The maximum number of projects to fetch
*/
const LIMIT = 1000;
/**
* The projects as an array with phid as keys, project names as $value
*
* @var string[]
*/
private $map = [];
/**
* The Phabricator instance name for this projects map
*
* @var string
*/
private $instanceName;
/**
*
* @var \Nasqueron\Notifications\Contracts\APIClient
*/
private $apiClient;
/**
* The source of the map
*
* @var string
*/
private $source = 'unloaded';
///
/// Constructor
///
/**
* Initializes a new instance of ProjectsMap.
*
* @param string $instanceName The Phabricator instance name
*/
public function __construct (string $instanceName) {
$this->instanceName = $instanceName;
}
///
/// IteratorAggregate interface implementation
///
/**
* Gets iterator.
- *
- * @return \Traversable
*/
- public function getIterator () {
+ public function getIterator () : \Traversable {
return new \ArrayIterator($this->map);
}
///
/// ArrayAccess interface implementation
///
/**
* Determines whether an offset exists.
*
* @param mixed $offset The offset
* @return bool
*/
- public function offsetExists (mixed $offset) {
+ public function offsetExists (mixed $offset) : bool {
return array_key_exists($offset, $this->map);
}
/**
* Gets the value at the specified offset.
*
* @param mixed $offset The offset.
* @return mixed The value
*/
- public function offsetGet (mixed $offset) {
+ public function offsetGet (mixed $offset) : mixed {
return $this->map[$offset];
}
/**
* Assigns a value to the specified offset.
*
* @param mixed $offset The offset
* @param mixed $value The value to assign
*/
public function offsetSet (mixed $offset, mixed $value) {
$this->map[$offset] = $value;
}
/**
* Unsets a value at the specified offset.
*
* @param mixed $offset The offset where to remove the value
*/
public function offsetUnset (mixed $offset) {
unset($this->map[$offset]);
}
///
/// Static constructors
///
/**
* Gets a new ProjectsMap instance from cache or API when not cached.
*
* @param string $phabricatorInstanceName The Phabricator instance name
* @return ProjectsMap
*/
- public static function load (string $phabricatorInstanceName) {
+ public static function load (string $phabricatorInstanceName) : ProjectsMap {
$instance = new self($phabricatorInstanceName);
if ($instance->isCached()) {
$instance->loadFromCache();
} else {
$instance->fetchFromAPI();
}
return $instance;
}
/**
* Gets a new ProjectsMap instance and queries Phabricator API to fill it.
*/
public static function fetch (
string $phabricatorInstanceName,
?APIClient $apiClient = null
- ) {
+ ) : ProjectsMap {
$instance = new self($phabricatorInstanceName);
$instance->setAPIClient($apiClient);
$instance->fetchFromAPI();
return $instance;
}
///
/// API
///
- /**
- * @return \Nasqueron\Notifications\Contracts\APIClient
- */
- public function getAPIClient () {
+ public function getAPIClient () : APIClient {
if ($this->apiClient === null) {
$factory = App::make('phabricator-api');
$this->apiClient = $factory->getForProject($this->instanceName);
}
return $this->apiClient;
}
- /**
- * @param \Nasqueron\Notifications\Contracts\APIClient|null $apiClient
- */
- public function setAPIClient (?APIClient $apiClient = null) {
+ public function setAPIClient (?APIClient $apiClient = null) : void {
$this->apiClient = $apiClient;
}
/**
* Fetches the projects' map from the Phabricator API.
*
* @throws \Exception when API reply is empty or invalid.
*/
private function fetchFromAPI () {
$reply = $this->getAPIClient()->call(
'project.query',
[ 'limit' => self::LIMIT ]
);
if (!$reply) {
throw new \Exception(<<<MSG
Empty reply calling project.query at $this->instanceName Conduit API.
MSG
);
}
if (!property_exists($reply, 'data')) {
throw new \Exception(<<<MSG
Invalid reply calling project.query at $this->instanceName Conduit API.
MSG
);
}
foreach ($reply->data as $phid => $projectInfo) {
$this->offsetSet($phid, $projectInfo->name);
}
$this->source = 'api';
}
///
/// Cache
///
/**
* Gets cache key.
*
* @return string The cache key for the current projects map
*/
- private function getCacheKey () {
+ private function getCacheKey () : string {
return class_basename(get_class($this))
. '-'
. md5($this->instanceName);
}
/**
* Determines if the instance is cached
*
* @return bool true if cached; otherwise, false.
*/
- public function isCached () {
+ public function isCached () : bool {
return Cache::has($this->getCacheKey());
}
/**
* Saves data to cache
*/
public function saveToCache () {
Cache::forever($this->getCacheKey(), $this->map);
}
/**
* Loads data from cache
*
* Populates 'map' and 'source' properties
*/
public function loadFromCache () {
$cachedMap = Cache::get($this->getCacheKey());
if ($cachedMap !== null) {
$this->map = $cachedMap;
$this->source = 'cache';
}
}
///
/// Output
///
/**
* Gets project name, refreshing the cache if needed.
*
* @param string $projectPHID the PHID of the project to query the name
* @return string The name of the poject, or an empty string if not found
*/
- public function getProjectName (string $projectPHID) {
+ public function getProjectName (string $projectPHID) : string {
if ($this->offsetExists($projectPHID)) {
return $this->offsetGet($projectPHID);
}
if ($this->source !== 'api') {
$this->fetchFromAPI();
return $this->getProjectName($projectPHID);
}
return "";
}
/**
* Returns the projects map as an array.
*
- * @return array An array, each row containing ['PHID', 'project name']
+ * @return array[] An array, each row containing ['PHID', 'project name']
*/
- public function toArray () {
+ public function toArray () : array {
$array = [];
foreach ($this->map as $phid => $projectName) {
$array[] = [$phid, $projectName];
}
return $array;
}
}
diff --git a/app/Phabricator/ProjectsMapFactory.php b/app/Phabricator/ProjectsMapFactory.php
index e19a9f6..75c2c8a 100644
--- a/app/Phabricator/ProjectsMapFactory.php
+++ b/app/Phabricator/ProjectsMapFactory.php
@@ -1,27 +1,27 @@
<?php
namespace Nasqueron\Notifications\Phabricator;
class ProjectsMapFactory {
/**
* Loads projects map from cache or fetches it from API if not cached.
*
* @param string $instanceName The Phabricator instance name
* @return ProjectsMap
*/
- public function load (string $instanceName) {
+ public function load (string $instanceName) : ProjectsMap {
return ProjectsMap::load($instanceName);
}
/**
* Fetches projects map from API.
*
* @param string $instanceName The Phabricator instance name
* @return ProjectsMap
*/
- public function fetch (string $instanceName) {
+ public function fetch (string $instanceName) : ProjectsMap {
return ProjectsMap::fetch($instanceName);
}
}
diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
index 4f9bb51..24bd0d1 100644
--- a/app/Providers/AppServiceProvider.php
+++ b/app/Providers/AppServiceProvider.php
@@ -1,26 +1,22 @@
<?php
namespace Nasqueron\Notifications\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider {
/**
* Bootstrap any application services.
- *
- * @return void
*/
- public function boot() {
+ public function boot() : void {
//
}
/**
* Register any application services.
- *
- * @return void
*/
- public function register() {
+ public function register() : void {
//
}
}
diff --git a/app/Providers/BrokerServiceProvider.php b/app/Providers/BrokerServiceProvider.php
index 6c719a6..c38e65c 100644
--- a/app/Providers/BrokerServiceProvider.php
+++ b/app/Providers/BrokerServiceProvider.php
@@ -1,34 +1,30 @@
<?php
namespace Nasqueron\Notifications\Providers;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\ServiceProvider;
use Keruald\Broker\BrokerFactory;
class BrokerServiceProvider extends ServiceProvider {
/**
* Bootstraps the application services.
- *
- * @return void
*/
- public function boot() {
+ public function boot() : void {
}
/**
* Registers the application services.
- *
- * @return void
*/
- public function register() {
+ public function register() : void {
$this->app->singleton('broker', function (Application $app) {
$config = $app->make('config');
$driver = $config->get('broker.driver');
$params = $config->get('broker.connections.' . $driver);
return BrokerFactory::make($params);
});
}
}
diff --git a/app/Providers/DockerHubServiceProvider.php b/app/Providers/DockerHubServiceProvider.php
index 7cb52f4..b404134 100644
--- a/app/Providers/DockerHubServiceProvider.php
+++ b/app/Providers/DockerHubServiceProvider.php
@@ -1,50 +1,46 @@
<?php
namespace Nasqueron\Notifications\Providers;
use Keruald\DockerHub\Build\TriggerBuildFactory;
use GuzzleHttp\Client;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\ServiceProvider;
class DockerHubServiceProvider extends ServiceProvider {
/**
* Bootstraps the application services.
- *
- * @return void
*/
- public function boot() {
+ public function boot() : void {
}
/**
* Gets the tokens to trigger build for the Docker Hub images.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return array
*/
- public static function getTokens (Application $app) {
+ public static function getTokens (Application $app) : array {
$file = $app->make('config')->get('services.dockerhub.tokens');
$fs = $app->make('filesystem')->disk('local');
if ($fs->exists($file)) {
$content = $fs->get($file);
return json_decode($content, true);
}
return [];
}
/**
* Registers the application services.
- *
- * @return void
*/
- public function register() {
+ public function register() : void {
$this->app->singleton('dockerhub', function (Application $app) {
$tokens = DockerHubServiceProvider::getTokens($app);
return new TriggerBuildFactory(new Client, $tokens);
});
}
}
diff --git a/app/Providers/MailgunServiceProvider.php b/app/Providers/MailgunServiceProvider.php
index f275ea1..887ca52 100644
--- a/app/Providers/MailgunServiceProvider.php
+++ b/app/Providers/MailgunServiceProvider.php
@@ -1,33 +1,29 @@
<?php
namespace Nasqueron\Notifications\Providers;
use Keruald\Mailgun\MailgunMessageFactory;
use GuzzleHttp\Client;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\ServiceProvider;
class MailgunServiceProvider extends ServiceProvider {
/**
* Bootstraps the application services.
- *
- * @return void
*/
- public function boot() {
+ public function boot() : void {
}
/**
* Registers the application services.
- *
- * @return void
*/
- public function register() {
+ public function register() : void {
$this->app->singleton('mailgun', function (Application $app) {
$config = $app->make('config');
$key = $config->get('services.mailgun.secret');
return new MailgunMessageFactory(new Client, $key);
});
}
}
diff --git a/app/Providers/PhabricatorAPIServiceProvider.php b/app/Providers/PhabricatorAPIServiceProvider.php
index 13678ea..16cb87e 100644
--- a/app/Providers/PhabricatorAPIServiceProvider.php
+++ b/app/Providers/PhabricatorAPIServiceProvider.php
@@ -1,29 +1,25 @@
<?php
namespace Nasqueron\Notifications\Providers;
use Illuminate\Support\ServiceProvider;
use Nasqueron\Notifications\Phabricator\PhabricatorAPIFactory;
class PhabricatorAPIServiceProvider extends ServiceProvider {
/**
* Bootstraps the application services.
- *
- * @return void
*/
- public function boot() {
+ public function boot() : void {
}
/**
* Registers the application services.
- *
- * @return void
*/
- public function register() {
+ public function register() : void {
$this->app->singleton('phabricator-api', static function () {
return new PhabricatorAPIFactory;
});
}
}
diff --git a/app/Providers/PhabricatorProjectsMapServiceProvider.php b/app/Providers/PhabricatorProjectsMapServiceProvider.php
index 9610d00..785fc67 100644
--- a/app/Providers/PhabricatorProjectsMapServiceProvider.php
+++ b/app/Providers/PhabricatorProjectsMapServiceProvider.php
@@ -1,29 +1,25 @@
<?php
namespace Nasqueron\Notifications\Providers;
use Illuminate\Support\ServiceProvider;
use Nasqueron\Notifications\Phabricator\ProjectsMapFactory;
class PhabricatorProjectsMapServiceProvider extends ServiceProvider {
/**
* Bootstraps the application services.
- *
- * @return void
*/
- public function boot() {
+ public function boot() : void {
}
/**
* Registers the application services.
- *
- * @return void
*/
- public function register() {
+ public function register() : void {
$this->app->singleton('phabricator-projectsmap', static function () {
return new ProjectsMapFactory;
});
}
}
diff --git a/app/Providers/ReportServiceProvider.php b/app/Providers/ReportServiceProvider.php
index 47d8a48..4c2ad93 100644
--- a/app/Providers/ReportServiceProvider.php
+++ b/app/Providers/ReportServiceProvider.php
@@ -1,40 +1,38 @@
<?php
namespace Nasqueron\Notifications\Providers;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\ServiceProvider;
use Nasqueron\Notifications\Actions\ActionsReport;
use Nasqueron\Notifications\Events\ReportEvent;
class ReportServiceProvider extends ServiceProvider {
/**
* Registers the application services.
- *
- * @return void
*/
- public function register() {
+ public function register() : void {
$this->app->singleton('report', function (Application $app) {
$report = new ActionsReport();
static::listenToActionsForReport(
$report,
$app->make('events')
);
return $report;
});
}
public static function listenToActionsForReport (
ActionsReport $report,
Dispatcher $events
- ) {
+ ) : void {
$events->listen(
'Nasqueron\Notifications\Events\ReportEvent',
static function (ReportEvent $event) use ($report) {
$report->addAction($event->action);
}
);
}
}
diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php
index 04507b6..ebff488 100644
--- a/app/Providers/RouteServiceProvider.php
+++ b/app/Providers/RouteServiceProvider.php
@@ -1,41 +1,39 @@
<?php
namespace Nasqueron\Notifications\Providers;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Routing\Router;
class RouteServiceProvider extends ServiceProvider {
/**
* This namespace is applied to the controller routes in your routes file.
*
* In addition, it is set as the URL generator's root namespace.
*
* @var string
*/
protected $namespace = 'Nasqueron\Notifications\Http\Controllers';
/**
* Define your route model bindings, pattern filters, etc.
- *
- * @return void
*/
- public function boot() {
+ public function boot() : void {
//
parent::boot();
}
/**
* Define the routes for the application.
*
* @param \Illuminate\Routing\Router $router
* @return void
*/
- public function map(Router $router) {
+ public function map(Router $router) : void {
$router->group(['namespace' => $this->namespace], static function ($router) {
require app_path('Http/routes.php');
});
}
}
diff --git a/app/Providers/SentryServiceProvider.php b/app/Providers/SentryServiceProvider.php
index cd7d2e2..a67ef28 100644
--- a/app/Providers/SentryServiceProvider.php
+++ b/app/Providers/SentryServiceProvider.php
@@ -1,30 +1,26 @@
<?php
namespace Nasqueron\Notifications\Providers;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\ServiceProvider;
class SentryServiceProvider extends ServiceProvider {
/**
* Bootstraps the application services.
- *
- * @return void
*/
- public function boot() {
+ public function boot() : void {
}
/**
* Registers the application services.
- *
- * @return void
*/
- public function register() {
+ public function register() : void {
$this->app->singleton('raven', function (Application $app) {
$config = $app->make('config');
$dsn = $config->get('services.sentry.dsn');
return new \Raven_Client($dsn);
});
}
}
diff --git a/app/Providers/ServicesServiceProvider.php b/app/Providers/ServicesServiceProvider.php
index 00c911d..85c8e38 100644
--- a/app/Providers/ServicesServiceProvider.php
+++ b/app/Providers/ServicesServiceProvider.php
@@ -1,26 +1,24 @@
<?php
namespace Nasqueron\Notifications\Providers;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\ServiceProvider;
use Nasqueron\Notifications\Config\Services\Services;
class ServicesServiceProvider extends ServiceProvider {
/**
* Registers the application services.
- *
- * @return void
*/
- public function register() {
+ public function register() : void {
$this->app->singleton('services', function (Application $app) {
$path = config('services.gate.credentials');
if (strlen($path) > 0 && $app->make('filesystem')->has($path)) {
return Services::loadFromJson($path);
}
return new Services;
});
}
}
diff --git a/tests/Phabricator/ProjectsMapTest.php b/tests/Phabricator/ProjectsMapTest.php
index 6cdb170..972f63f 100644
--- a/tests/Phabricator/ProjectsMapTest.php
+++ b/tests/Phabricator/ProjectsMapTest.php
@@ -1,198 +1,198 @@
<?php
namespace Nasqueron\Notifications\Tests\Phabricator;
use Nasqueron\Notifications\Contracts\APIClient;
use Nasqueron\Notifications\Phabricator\ProjectsMap;
use Nasqueron\Notifications\Tests\TestCase;
use ErrorException;
use Exception;
class ProjectsMapTest extends TestCase {
/**
* @var \Nasqueron\Notifications\Phabricator\ProjectsMap
*/
private $map;
public function setUp (): void {
parent::setUp();
//
// We mock the API, so an imaginary instance of Phabricator
// will return 3 results: Accounts, Agora & architecture.
//
// Agora has the key "PHID-PROJ-cztcgpvqr6smnnekotq7".
//
$this->mockPhabricatorAPIForProjectsMap();
$this->map = ProjectsMap::fetch("http://phabricator.acme.tld");
}
public function testIteratorIsTraversable () {
$this->assertInstanceOf(
"Traversable",
$this->map->getIterator()
);
}
///
/// Tests for ArrayAccess
///
public function testOffsetExistsWhenItDoes () {
$this->assertTrue(
$this->map->offsetExists("PHID-PROJ-cztcgpvqr6smnnekotq7")
);
}
public function testOffsetExistsWhenItDoesNot () {
$this->assertFalse(
$this->map->offsetExists("non-existing-key")
);
}
public function testOffsetGetWhenItDoesExist () {
$this->assertSame(
"Agora",
$this->map->offsetGet("PHID-PROJ-cztcgpvqr6smnnekotq7")
);
}
public function testOffsetGetWhenItDoesNotExist () {
$this->expectException(ErrorException::class);
$this->map->offsetGet("non-existing-key");
}
/**
* @covers Nasqueron\Notifications\Phabricator\ProjectsMap::offsetSet
*/
public function testOffsetSet () {
$this->map->offsetSet("newkey", "quux");
$this->assertSame("quux", $this->map->offsetGet("newkey"));
}
/**
* @covers Nasqueron\Notifications\Phabricator\ProjectsMap::offsetUnset
*/
public function testOffsetUnset () {
unset($this->map["PHID-PROJ-cztcgpvqr6smnnekotq7"]);
$this->assertFalse(
$this->map->offsetExists("PHID-PROJ-cztcgpvqr6smnnekotq7")
);
}
///
/// Tests for cache
///
public function testCache () {
$this->assertFalse($this->map->isCached());
$this->map->saveToCache();
$this->assertTrue($this->map->isCached());
}
public function testLoadFromCache () {
$this->map->saveToCache();
$map = new ProjectsMap("http://phabricator.acme.tld");
$map->loadFromCache();
$this->assertTrue(
$map->offsetExists("PHID-PROJ-cztcgpvqr6smnnekotq7")
);
}
public function testLoadWhenInCache () {
$this->map->saveToCache();
$map = ProjectsMap::load("http://phabricator.acme.tld");
$this->assertTrue(
$map->offsetExists("PHID-PROJ-cztcgpvqr6smnnekotq7")
);
}
///
/// Tests for helper methods
///
public function testGetProjectName () {
$this->assertSame(
"Agora",
$this->map->getProjectName("PHID-PROJ-cztcgpvqr6smnnekotq7")
);
}
public function testGetProjectNameForNewInstance () {
$map = new ProjectsMap("http://phabricator.acme.tld");
$this->assertSame(
"Agora",
$map->getProjectName("PHID-PROJ-cztcgpvqr6smnnekotq7")
);
}
public function testGetProjectNameWhenItDoesNotExist () {
$this->assertSame(
"",
$this->map->getProjectName("non-existing-key")
);
}
public function testToArrayProducesArray () {
$array = $this->map->toArray();
$this->assertTrue(
is_array($array),
"Test if toArray return an array"
);
}
public function testThatArrayCount () {
$array = $this->map->toArray();
$this->assertSame(3, count($array));
}
public function testThatArrayContainsExpectedData () {
$this->assertSame(
[
["PHID-PROJ-6dg6ogx5pjmk24ur4tp4", "Accounts"],
["PHID-PROJ-cztcgpvqr6smnnekotq7", "Agora"],
["PHID-PROJ-3iew3cqf3htpazfyzb5a", "architecture"]
],
$this->map->toArray()
);
}
///
/// Tests API
///
private function mockPhabricatorAPIWithReply ($reply) : APIClient {
return (new class($reply) implements APIClient {
private $reply;
public function __construct ($reply) {
$this->reply = $reply;
}
public function setEndPoint ($url) : void { }
- public function call ($method, $arguments = []) {
+ public function call ($method, $arguments = []) : mixed {
return $this->reply;
}
});
}
public function testFetchFromAPIWithoutReply () {
$this->expectException(Exception::class);
$mock = $this->mockPhabricatorAPIWithReply(false);
ProjectsMap::fetch("http://phabricator.acme.tld", $mock);
}
public function testFetchFromAPIInvalidReply () {
$this->expectException(Exception::class);
$mock = $this->mockPhabricatorAPIWithReply(new \stdClass);
ProjectsMap::fetch("http://phabricator.acme.tld", $mock);
}
}

File Metadata

Mime Type
text/x-diff
Expires
Fri, Feb 28, 20:36 (21 h, 33 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2447800
Default Alt Text
(138 KB)

Event Timeline