diff --git a/app/Actions/AMQPAction.php b/app/Actions/AMQPAction.php index ef22f04..747f353 100644 --- a/app/Actions/AMQPAction.php +++ b/app/Actions/AMQPAction.php @@ -1,41 +1,45 @@ <?php namespace Nasqueron\Notifications\Actions; class AMQPAction extends Action { /** * The action done on the broker ('publish', 'consume') * * @var string */ public $method; /** * The queue or exchange target on the broker * * @var string */ public $target; /** * The routing key * * @var string */ public $routingKey; /** * Initializes a new instance of a AMQP action to report * * @param string $method The action done on the broker (e.g. 'publish') * @param string $target The queue or exchange target on the broker * @param string $routingKey The routing key for this exchange or queue */ - public function __construct (string $method, string $target, string $routingKey = '') { + public function __construct ( + string $method, + string $target, + string $routingKey = '' + ) { parent::__construct(); $this->method = $method; $this->target = $target; $this->routingKey = $routingKey; } } diff --git a/app/Actions/ActionError.php b/app/Actions/ActionError.php index ab090f9..54e60eb 100644 --- a/app/Actions/ActionError.php +++ b/app/Actions/ActionError.php @@ -1,30 +1,25 @@ <?php namespace Nasqueron\Notifications\Actions; class ActionError { /** * Exception type * * @var string */ public $type; /** * Exception message * * @var string */ public $message; - /** - * Initializes a new action error report instance from the specified Exception - * - * @param \Exception $ex The exception to build an ActionError instance from - */ public function __construct (\Exception $ex) { $this->type = class_basename(get_class($ex)); $this->message = $ex->getMessage(); } } diff --git a/app/Analyzers/BasePayloadAnalyzer.php b/app/Analyzers/BasePayloadAnalyzer.php index 9bd45d6..bebd917 100644 --- a/app/Analyzers/BasePayloadAnalyzer.php +++ b/app/Analyzers/BasePayloadAnalyzer.php @@ -1,173 +1,181 @@ <?php namespace Nasqueron\Notifications\Analyzers; use Config; use Storage; use BadMethodCallException; 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'); + $dir = Config::get( + 'services.' + . strtolower(static::SERVICE_NAME) + . '.analyzer.configDir' + ); $filename = $dir . '/' . $this->project . '.json'; if (!Storage::has($filename)) { return $dir . '/' . static::CONFIG_DEFAULT_FILE; } return $filename; } /** * Gets full qualified class name for configuration. * * @return string */ private function getCandidateConfigurationClassName() : string { - $namespace = 'Nasqueron\Notifications\Analyzers\\' . static::SERVICE_NAME; - return $namespace . "\\" . static::SERVICE_NAME . 'PayloadAnalyzerConfiguration'; + 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("The getItemName method must be implemented in the analyzer class if used."); + 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/GitHub/Events/Event.php b/app/Analyzers/GitHub/Events/Event.php index 60bc989..044a111 100644 --- a/app/Analyzers/GitHub/Events/Event.php +++ b/app/Analyzers/GitHub/Events/Event.php @@ -1,78 +1,80 @@ <?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 ($eventName) { return __NAMESPACE__ . '\\' . studly_case($eventName) . 'Event'; } /** * 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 ($eventName, $payload) { $class = self::getClass($eventName); if (!class_exists($class)) { - throw new \InvalidArgumentException("Class doesn't exist: $class (for $eventName)"); + 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 ($text, $strLen = 114, $symbol = '…') { $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/WithRepoAndBranch.php b/app/Analyzers/GitHub/Events/WithRepoAndBranch.php index b48de5b..09537bd 100644 --- a/app/Analyzers/GitHub/Events/WithRepoAndBranch.php +++ b/app/Analyzers/GitHub/Events/WithRepoAndBranch.php @@ -1,51 +1,45 @@ <?php namespace Nasqueron\Notifications\Analyzers\GitHub\Events; /** * Helper methods for events with a need to specify the repo and the branch * (e.g. push) * * @link https://developer.github.com/v3/activity/events/types/#pushevent */ trait WithRepoAndBranch { /** * Gets repository and branch information - * - * @return string */ - public function getWhere () { + public function getWhere () : string { $repo = $this->payload->repository->name; $branch = $this->payload->ref; return static::getRepositoryAndBranch($repo, $branch); } - /** - * Gets a repository and branch information string - * - * @param string $repo The repository - * @param string $branch The branch - * @return string "<repo>" or "<repo> (branch <branch>)" when branch isn't master - */ - public static function getRepositoryAndBranch ($repo = "", $branch = "") { + public static function getRepositoryAndBranch ( + $repo = "", + $branch = "" + ) : string { if ($repo === "") { return ""; } if (starts_with($branch, "refs/heads/")) { $branch = substr($branch, 11); } if ($branch === "" || $branch === "master") { return $repo; } return trans('GitHub.RepoAndBranch', [ 'repo' => $repo, 'branch' => $branch, ]); } } diff --git a/app/Analyzers/GitHub/GitHubPayloadAnalyzer.php b/app/Analyzers/GitHub/GitHubPayloadAnalyzer.php index 3a2904d..3148f6d 100644 --- a/app/Analyzers/GitHub/GitHubPayloadAnalyzer.php +++ b/app/Analyzers/GitHub/GitHubPayloadAnalyzer.php @@ -1,112 +1,116 @@ <?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) { + public function __construct( + string $project, + string $event, + \stdClass $payload + ) { parent::__construct($project, $payload); $this->event = $event; try { $this->analyzerEvent = Event::forPayload($event, $payload); } catch (\InvalidArgumentException $ex) { $this->analyzerEvent = new UnknownEvent($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 931f1aa..ad056bf 100644 --- a/app/Analyzers/ItemGroupMapping.php +++ b/app/Analyzers/ItemGroupMapping.php @@ -1,60 +1,63 @@ <?php namespace Nasqueron\Notifications\Analyzers; /** * 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 { + 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/Phabricator/PhabricatorPayloadAnalyzerConfiguration.php b/app/Analyzers/Phabricator/PhabricatorPayloadAnalyzerConfiguration.php index 0b4a802..f613952 100644 --- a/app/Analyzers/Phabricator/PhabricatorPayloadAnalyzerConfiguration.php +++ b/app/Analyzers/Phabricator/PhabricatorPayloadAnalyzerConfiguration.php @@ -1,16 +1,17 @@ <?php namespace Nasqueron\Notifications\Analyzers\Phabricator; use Nasqueron\Notifications\Analyzers\PayloadAnalyzerConfiguration; -class PhabricatorPayloadAnalyzerConfiguration extends PayloadAnalyzerConfiguration { +class PhabricatorPayloadAnalyzerConfiguration + extends PayloadAnalyzerConfiguration { /** * An array of RepositoryGroupMapping objects to match repositories & groups * * @var PhabricatorGroupMapping[] */ public $map; } diff --git a/app/Config/Features.php b/app/Config/Features.php index 0bb9fb0..09c0a70 100644 --- a/app/Config/Features.php +++ b/app/Config/Features.php @@ -1,94 +1,92 @@ <?php namespace Nasqueron\Notifications\Config; use Config; /** * The features class offers a sugar syntax to check if a feature is enabled * in the Config repository, at app.features. * * Features could be added to config/app.php to the features array. */ class Features { /// /// Feature information /// /** * Gets the configuration key for the specified feature name. * * @param string $feature The feature to get the config key * @return string The config key */ private static function getFeatureConfigKey (string $feature) : string { return 'app.features.' . $feature; } /** * Determines if the specified feature is enabled. * * @param string $feature The feature to check in the config * @return bool */ public static function isEnabled (string $feature) : bool { $key = self::getFeatureConfigKey($feature); return Config::has($key) && (bool)Config::get($key); } /** * Enables a feature in our current configuration instance. * * @param string $feature The feature */ public static function enable (string $feature) : void { $key = self::getFeatureConfigKey($feature); Config::set($key, true); } /** * Disables a feature in our current configuration instance. * * @param string $feature The feature */ public static function disable (string $feature) : void { $key = self::getFeatureConfigKey($feature); Config::set($key, false); } /// /// Features lists /// /** * Gets all the features, with the toggle status. - * - * @return array An array with features as keys, bool as values (true if enabled) */ public static function getAll () : array { return Config::get('app.features'); } /** * Lists all the features. * * @return string[] a list of all features */ public static function getAvailable () : array { $features = self::getAll(); return array_keys($features); } /** * Lists the enabled features. * * @return string[] a list of enabled features */ public static function getEnabled () : array { $features = self::getAll(); $enabledFeatures = array_filter($features); return array_keys($enabledFeatures); } } diff --git a/app/Config/Reporting/BaseReportEntry.php b/app/Config/Reporting/BaseReportEntry.php index e4afb2d..c8a3c04 100644 --- a/app/Config/Reporting/BaseReportEntry.php +++ b/app/Config/Reporting/BaseReportEntry.php @@ -1,49 +1,58 @@ <?php namespace Nasqueron\Notifications\Config\Reporting; abstract class BaseReportEntry { /// /// Format /// public abstract function toArray () : array; public abstract function toFancyArray () : array; /// /// Format helper methods /// /** * Returns a fancy string for reports. * * @param string $string The source string * @param string $emptyStringGlyph The glyph to use if the string is empty * @return string */ - public static function fancyString (string $string, string $emptyStringGlyph) : string { + public static function fancyString ( + string $string, + string $emptyStringGlyph + ) : string { if ($string === "") { return $emptyStringGlyph; } return $string; } /** * Returns a fancy representation from a boolean for reports. * * @param bool $value The source value * @param string $truthyStringGlyph The glyph to use if the value is true - * @param string $falsyStringGlyph The glyph to use if the value is false [facultative, by default an empty string] + * @param string $falsyStringGlyph The glyph to use if the value is false + * [facultative, by default an empty string] + * * @return string The relevant glyph */ - public static function fancyBool (bool $value, string $truthyStringGlyph, string $falsyStringGlyph = '') : string { + public static function fancyBool ( + bool $value, + string $truthyStringGlyph, + string $falsyStringGlyph = '' + ) : string { if ($value) { return $truthyStringGlyph; } return $falsyStringGlyph; } } diff --git a/app/Config/Reporting/ServiceReportEntry.php b/app/Config/Reporting/ServiceReportEntry.php index 4dde243..b8d7517 100644 --- a/app/Config/Reporting/ServiceReportEntry.php +++ b/app/Config/Reporting/ServiceReportEntry.php @@ -1,134 +1,129 @@ <?php namespace Nasqueron\Notifications\Config\Reporting; use Nasqueron\Notifications\Config\Services\Service; use 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 /// - /** - * Initializes a new instance of the ServiceReportEntry class. - * - * @param \Nasqueron\Notifications\Config\Services\Service $service The service - */ 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 47716f0..b59b6a7 100644 --- a/app/Config/Services/Services.php +++ b/app/Config/Services/Services.php @@ -1,104 +1,107 @@ <?php namespace Nasqueron\Notifications\Config\Services; use Storage; class Services { /// /// Properties /// /** * @var Service[] */ public $services = []; /// /// Constructors /// /** - * Initializes a new instance of the Services class deserializing a JSON file. - * * @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 () { 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') + * @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, $value) : ?Service { + public function findServiceByProperty ( + string $gate, + string $property, + $value + ) : ?Service { foreach ($this->services as $service) { if ($service->gate === $gate && $service->$property === $value) { return $service; } } return null; } } diff --git a/app/Console/Commands/NotificationsPayload.php b/app/Console/Commands/NotificationsPayload.php index 15316b7..77d6f5a 100644 --- a/app/Console/Commands/NotificationsPayload.php +++ b/app/Console/Commands/NotificationsPayload.php @@ -1,214 +1,225 @@ <?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 = 'Gets a notification payload from a service payload'; + 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. + * @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"); + 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 when too many or too few arguments have been given. + * @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 */ private function formatPayload() { 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 { + public static function argumentsArrayCombine ( + array $keys, array $values + ) : array { $countKeys = count($keys); $countValues = count($values); if ($countKeys != $countValues) { - throw new InvalidArgumentException("Number of arguments mismatch: got $countValues but expected $countKeys."); + 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 */ 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 */ 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/Console/Commands/PhabricatorProjectsMap.php b/app/Console/Commands/PhabricatorProjectsMap.php index 70724a2..364873b 100644 --- a/app/Console/Commands/PhabricatorProjectsMap.php +++ b/app/Console/Commands/PhabricatorProjectsMap.php @@ -1,39 +1,41 @@ <?php namespace Nasqueron\Notifications\Console\Commands; use Illuminate\Console\Command; use ProjectsMap; use Services; class PhabricatorProjectsMap extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'phabricator:projectsmap'; /** * The console command description. * * @var string */ - protected $description = 'Regenerate the projects map for each Phabricator instances'; + protected $description = <<<'TXT' +Regenerate the projects map for each Phabricator instances +TXT; /** * Executes the console command. */ public function handle() : void { foreach (Services::getForGate('Phabricator') as $service) { $this->info("Querying projects map for " . $service->instance); $map = ProjectsMap::fetch($service->door); $map->saveToCache(); $this->table( ['PHID', 'Project name'], $map->toArray() ); } } } diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 06c43f5..1562247 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -1,70 +1,68 @@ <?php namespace Nasqueron\Notifications\Console; use Illuminate\Console\Command; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; class Kernel extends ConsoleKernel { /** * The Artisan commands provided by your application. * * @var string[] */ protected $commands = [ \Nasqueron\Notifications\Console\Commands\ConfigShow::class, \Nasqueron\Notifications\Console\Commands\ConfigValidate::class, \Nasqueron\Notifications\Console\Commands\Inspire::class, \Nasqueron\Notifications\Console\Commands\NotificationsPayload::class, \Nasqueron\Notifications\Console\Commands\PhabricatorProjectsMap::class, ]; /** * Define the application's command schedule. * * @param \Illuminate\Console\Scheduling\Schedule $schedule * @return void */ protected function schedule (Schedule $schedule) : void { $schedule->command('inspire') ->hourly(); } /** * Gets a command by name * - * @param string $name The command name (first word of the command signature) - * @return \Illuminate\Console\Command * @throws \RuntimeException when command doesn't exit */ public function get (string $name) : Command { $commands = $this->all(); if (array_key_exists($name, $commands)) { return $commands[$name]; } throw new \RuntimeException("Command $name doesn't exist."); } /** * Gets a command by class * * @param string $class The command class * @return \Illuminate\Console\Command * @throws \RuntimeException when command doesn't exit */ public function getByClass (string $class) : Command { $commands = $this->all(); foreach ($commands as $command) { if ($command instanceof $class) { return $command; } } throw new \RuntimeException("Command $class doesn't exist."); } } diff --git a/app/Http/Controllers/Gate/GateController.php b/app/Http/Controllers/Gate/GateController.php index 0077e19..36cdf38 100644 --- a/app/Http/Controllers/Gate/GateController.php +++ b/app/Http/Controllers/Gate/GateController.php @@ -1,120 +1,121 @@ <?php namespace Nasqueron\Notifications\Http\Controllers\Gate; use Nasqueron\Notifications\Config\Features; use Nasqueron\Notifications\Config\Services\Service; use Nasqueron\Notifications\Http\Controllers\Controller; use Symfony\Component\HttpFoundation\Response as BaseResponse; use Illuminate\View\View; use App; use Log; use Report; use Response; use Services; /** * 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 - * - * @return \Nasqueron\Notifications\Config\Services\Service|null The service information is found; otherwise, null. */ public function getService () : ?Service { - return Services::findServiceByDoor($this->getServiceName(), $this->door); + 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/Http/routes.php b/app/Http/routes.php index 19411c5..5f2d930 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -1,41 +1,42 @@ <?php use Nasqueron\Notifications\Config\Features; use Nasqueron\Notifications\Config\Reporting\ConfigReport; /* |-------------------------------------------------------------------------- | Application Routes |-------------------------------------------------------------------------- | | Here is where you can register all of the routes for an application. | It's a breeze. Simply tell Laravel the URIs it should respond to | and give it the controller to call when that URI is requested. | */ Route::get('/', function () { return view('welcome'); }); // Allows to external tool to ping your instalation and know if the site is up. Route::get('/status', function() { return "ALIVE"; }); // Allows to external tool to check the current configuration. if (Features::isEnabled('GetConfig')) { Route::get('/config', function() { $report = new ConfigReport(); return Response::json($report); }); } // Gate controllers if (Features::isEnabled('Gate')) { foreach (Config::get('gate.controllers') as $controller) { $controllerRoute = '/gate/' . $controller . '/'; - Route::get($controllerRoute . '{door?}', "Gate\\${controller}GateController@onGet"); - Route::post($controllerRoute . '{door}', "Gate\\${controller}GateController@onPost"); + $controllerClass = "Gate\\${controller}GateController"; + Route::get($controllerRoute . '{door?}', "$controllerClass@onGet"); + Route::post($controllerRoute . '{door}', "$controllerClass@onPost"); } } diff --git a/app/Jobs/FireDockerHubNotification.php b/app/Jobs/FireDockerHubNotification.php index 39029fd..5f246a5 100644 --- a/app/Jobs/FireDockerHubNotification.php +++ b/app/Jobs/FireDockerHubNotification.php @@ -1,52 +1,47 @@ <?php namespace Nasqueron\Notifications\Jobs; use Nasqueron\Notifications\Notifications\DockerHubNotification; use Nasqueron\Notifications\Events\DockerHubPayloadEvent; use Nasqueron\Notifications\Events\NotificationEvent; use Nasqueron\Notifications\Jobs\Job; use Event; class FireDockerHubNotification extends Job { /** * @var DockerHubPayloadEvent; */ private $event; /** * Initializes a new instance of FireDockerHubNotification * * @param DockerHubPayloadEvent $event The event to notify */ public function __construct (DockerHubPayloadEvent $event) { $this->event = $event; } /// /// Task /// /** * Executes the job. */ public function handle() : void { $notification = $this->createNotification(); Event::fire(new NotificationEvent($notification)); } - /** - * Creates a DockerHub notification - * - * @return \Nasqueron\Notifications\Notifications\DockerHubNotification The notification - */ protected function createNotification() : DockerHubNotification { return new DockerHubNotification( $this->event->door, // project $this->event->event, // event type $this->event->payload // raw content ); } } diff --git a/app/Jobs/FireGitHubNotification.php b/app/Jobs/FireGitHubNotification.php index 002e5d8..4ce7e18 100644 --- a/app/Jobs/FireGitHubNotification.php +++ b/app/Jobs/FireGitHubNotification.php @@ -1,52 +1,48 @@ <?php namespace Nasqueron\Notifications\Jobs; use Nasqueron\Notifications\Notifications\GitHubNotification; use Nasqueron\Notifications\Events\GitHubPayloadEvent; use Nasqueron\Notifications\Events\NotificationEvent; use Nasqueron\Notifications\Jobs\Job; use Event; class FireGitHubNotification extends Job { /** * @var GitHubPayloadEvent; */ private $event; /** * Initializes a new instance of FireGitHubNotification * * @param GitHubPayloadEvent $event The event to notify */ public function __construct (GitHubPayloadEvent $event) { $this->event = $event; } /// /// Task /// /** * Executes the job. */ public function handle() : void { $notification = $this->createNotification(); Event::fire(new NotificationEvent($notification)); } - /** - * Creates a GitHub notification - * - * @return \Nasqueron\Notifications\Notifications\GitHubNotification The notification - */ + protected function createNotification() : GitHubNotification { return new GitHubNotification( $this->event->door, // project $this->event->event, // event type $this->event->payload // raw content ); } } diff --git a/app/Jobs/FireJenkinsNotification.php b/app/Jobs/FireJenkinsNotification.php index 5221e8d..8a2d14e 100644 --- a/app/Jobs/FireJenkinsNotification.php +++ b/app/Jobs/FireJenkinsNotification.php @@ -1,55 +1,50 @@ <?php namespace Nasqueron\Notifications\Jobs; use Nasqueron\Notifications\Notifications\JenkinsNotification; use Nasqueron\Notifications\Events\JenkinsPayloadEvent; use Nasqueron\Notifications\Events\NotificationEvent; use Nasqueron\Notifications\Jobs\Job; use 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::fire(new NotificationEvent($notification)); } } - /** - * Creates a Jenkins notification - * - * @return \Nasqueron\Notifications\Notifications\JenkinsNotification The notification - */ protected function createNotification() : JenkinsNotification { return new JenkinsNotification( $this->event->door, // project $this->event->payload // raw content ); } } diff --git a/app/Jobs/FirePhabricatorNotification.php b/app/Jobs/FirePhabricatorNotification.php index 742c218..d8cb8a7 100644 --- a/app/Jobs/FirePhabricatorNotification.php +++ b/app/Jobs/FirePhabricatorNotification.php @@ -1,51 +1,46 @@ <?php namespace Nasqueron\Notifications\Jobs; use Nasqueron\Notifications\Notifications\PhabricatorNotification; use Nasqueron\Notifications\Events\PhabricatorPayloadEvent; use Nasqueron\Notifications\Events\NotificationEvent; use Nasqueron\Notifications\Jobs\Job; use Event; class FirePhabricatorNotification extends Job { /** * @var PhabricatorPayloadEvent; */ private $event; /** * Initializes a new instance of FirePhabricatorNotification * * @param PhabricatorPayloadEvent $event The event to notify */ public function __construct (PhabricatorPayloadEvent $event) { $this->event = $event; } /// /// Task /// /** * Executes the job. */ public function handle() : void { $notification = $this->createNotification(); Event::fire(new NotificationEvent($notification)); } - /** - * Creates a Phabricator notification - * - * @return \Nasqueron\Notifications\Notifications\PhabricatorNotification The notification - */ protected function createNotification() : PhabricatorNotification { return new PhabricatorNotification( $this->event->door, // Project $this->event->story // Story ); } } diff --git a/app/Jobs/NotifyNewCommitsToDiffusion.php b/app/Jobs/NotifyNewCommitsToDiffusion.php index 45f9a8e..0b16d39 100644 --- a/app/Jobs/NotifyNewCommitsToDiffusion.php +++ b/app/Jobs/NotifyNewCommitsToDiffusion.php @@ -1,201 +1,201 @@ <?php namespace Nasqueron\Notifications\Jobs; use Nasqueron\Notifications\Actions\ActionError; use Nasqueron\Notifications\Actions\NotifyNewCommitsAction; use Nasqueron\Notifications\Events\ReportEvent; use Nasqueron\Notifications\Phabricator\PhabricatorAPI as API; use Nasqueron\Notifications\Phabricator\PhabricatorAPIException; use Event; use Log; use PhabricatorAPI; 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::fire($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 ; otherwise, false. + * @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 fetch ; otherwise, false. + * @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. + * Gets the call sign matching the repository URL (e.g. "OPS"). * - * @return string the repository call sign "OPS", or "" if not in Phabricator + * @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 9e739a9..c4b3676 100644 --- a/app/Jobs/SendMessageToBroker.php +++ b/app/Jobs/SendMessageToBroker.php @@ -1,109 +1,113 @@ <?php namespace Nasqueron\Notifications\Jobs; use Nasqueron\Notifications\Actions\ActionError; use Nasqueron\Notifications\Actions\AMQPAction; use Nasqueron\Notifications\Events\ReportEvent; use Nasqueron\Notifications\Jobs\Job; use Broker; use Event; use 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) { + 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::fire(new ReportEvent($actionToReport)); } } diff --git a/app/Listeners/AMQPEventListener.php b/app/Listeners/AMQPEventListener.php index af035dc..a2fa279 100644 --- a/app/Listeners/AMQPEventListener.php +++ b/app/Listeners/AMQPEventListener.php @@ -1,72 +1,72 @@ <?php namespace Nasqueron\Notifications\Listeners; use Nasqueron\Notifications\Events\NotificationEvent; use Nasqueron\Notifications\Jobs\SendMessageToBroker; use Nasqueron\Notifications\Notifications\Notification; use Illuminate\Events\Dispatcher; use Config; class AMQPEventListener { /// /// Notifications /// /** * Handles a notification event. * * @param NotificationEvent $event */ public function onNotification(NotificationEvent $event) : void { $this->sendNotification($event->notification); } - /** - * Gets routing key, to allow consumers to select the topic they subscribe to. - * - * @param Notification The $notification from where the keys must be extracted - */ - protected static function getNotificationRoutingKey (Notification $notification) : string { + protected static function getNotificationRoutingKey ( + Notification $notification + ) : string { $keyParts = [ $notification->project, $notification->group, $notification->service, $notification->type, ]; return strtolower(implode('.', $keyParts)); } /** * Sends the notification to the broker target for distilled notifications. * * @param Notification The notification to send */ protected function sendNotification(Notification $notification) : void { $target = Config::get('broker.targets.notifications'); $routingKey = static::getNotificationRoutingKey($notification); $message = json_encode($notification); $job = new SendMessageToBroker($target, $routingKey, $message); $job->handle(); } /// /// Events listening /// /** * Registers the listeners for the subscriber. * * @param Dispatcher $events */ public function subscribe (Dispatcher $events) : void { $class = AMQPEventListener::class; - $events->listen(NotificationEvent::class, "$class@onNotification"); + $events->listen( + NotificationEvent::class, + "$class@onNotification" + ); } } diff --git a/app/Listeners/NotificationListener.php b/app/Listeners/NotificationListener.php index d743766..991370d 100644 --- a/app/Listeners/NotificationListener.php +++ b/app/Listeners/NotificationListener.php @@ -1,94 +1,96 @@ <?php namespace Nasqueron\Notifications\Listeners; use Nasqueron\Notifications\Events\DockerHubPayloadEvent; use Nasqueron\Notifications\Events\GitHubPayloadEvent; use Nasqueron\Notifications\Events\JenkinsPayloadEvent; use Nasqueron\Notifications\Events\PhabricatorPayloadEvent; use Nasqueron\Notifications\Jobs\FireDockerHubNotification; use Nasqueron\Notifications\Jobs\FireGitHubNotification; use Nasqueron\Notifications\Jobs\FireJenkinsNotification; use Nasqueron\Notifications\Jobs\FirePhabricatorNotification; class NotificationListener { /// /// Distill services' payloads into notifications /// /** * Handles a Docker Hub payload event. * * @param DockerHubPayloadEvent $event * @return void */ public function onDockerHubPayload(DockerHubPayloadEvent $event) : void { $job = new FireDockerHubNotification($event); $job->handle(); } /** * Handles a GitHub payload event. * * @param GitHubPayloadEvent $event * @return void */ public function onGitHubPayload(GitHubPayloadEvent $event) : void { $job = new FireGitHubNotification($event); $job->handle(); } /** * Handles a Phabricator payload event. * * @param PhabricatorPayloadEvent $event * @return void */ - public function onPhabricatorPayload(PhabricatorPayloadEvent $event) : void { + public function onPhabricatorPayload( + PhabricatorPayloadEvent $event + ) : void { $job = new FirePhabricatorNotification($event); $job->handle(); } /** * Handles a Jenkins payload event. * * @param JenkinsPayloadEvent $event * @return void */ public function onJenkinsPayload (JenkinsPayloadEvent $event) : void { $job = new FireJenkinsNotification($event); $job->handle(); } /// /// Events listening /// /** * Register the listeners for the subscriber. * * @param \Illuminate\Events\Dispatcher $events */ public function subscribe (\Illuminate\Events\Dispatcher $events) : void { $class = 'Nasqueron\Notifications\Listeners\NotificationListener'; $events->listen( 'Nasqueron\Notifications\Events\DockerHubPayloadEvent', "$class@onDockerHubPayload" ); $events->listen( 'Nasqueron\Notifications\Events\GitHubPayloadEvent', "$class@onGitHubPayload" ); $events->listen( 'Nasqueron\Notifications\Events\JenkinsPayloadEvent', "$class@onJenkinsPayload" ); $events->listen( 'Nasqueron\Notifications\Events\PhabricatorPayloadEvent', "$class@onPhabricatorPayload" ); } } diff --git a/app/Notifications/DockerHubNotification.php b/app/Notifications/DockerHubNotification.php index e485677..960db47 100644 --- a/app/Notifications/DockerHubNotification.php +++ b/app/Notifications/DockerHubNotification.php @@ -1,87 +1,91 @@ <?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) { + 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 344d9b3..6ffa0b5 100644 --- a/app/Notifications/GitHubNotification.php +++ b/app/Notifications/GitHubNotification.php @@ -1,68 +1,73 @@ <?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) { + 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). + * 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 56fae37..1ce9d9e 100644 --- a/app/Notifications/JenkinsNotification.php +++ b/app/Notifications/JenkinsNotification.php @@ -1,111 +1,112 @@ <?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 ($project, $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). + * 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/Phabricator/PhabricatorAPI.php b/app/Phabricator/PhabricatorAPI.php index 3f98a98..e92ea0e 100644 --- a/app/Phabricator/PhabricatorAPI.php +++ b/app/Phabricator/PhabricatorAPI.php @@ -1,160 +1,164 @@ <?php namespace Nasqueron\Notifications\Phabricator; use Nasqueron\Notifications\Contracts\APIClient; use 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 ($endPoint, $apiToken) { $this->endPoint = $endPoint; $this->apiToken = $apiToken; } /** - * Gets an API instance for the specific instance - * - * @param string $instance The name of the instance (this matches that parameter in credentials.json) - * @return PhabricatorAPI A PhabricatorAPI instance for the project if found; otherwise, null. + * @throws \RuntimeException when the service isn't in credentials.json */ - public static function forInstance ($instance) { - $service = Services::findServiceByProperty('Phabricator', 'instance', $instance); + public static function forInstance ($instance) : PhabricatorAPI { + $service = Services::findServiceByProperty( + 'Phabricator', + 'instance', + $instance + ); if ($service === null) { - throw new \RuntimeException("No credentials for Phabricator instance $instance."); + throw new \RuntimeException( + "No credentials for Phabricator instance $instance." + ); } return new self($service->instance, $service->secret); } /** - * Gets an API instance for the specific project - * - * @param string $project The name of the project (this matches the door parameter in credentials.json) - * @return PhabricatorAPI A PhabricatorAPI instance for the project if found; otherwise, null. + * @throws \RuntimeException when the service isn't in credentials.json */ public static function forProject ($project) { $service = Services::findServiceByDoor('Phabricator', $project); if ($service === null) { - throw new \RuntimeException("No credentials for Phabricator project $project."); + 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 ($url) { $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 ($method, $arguments = []) { $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 ($reply) { if (is_object($reply) && property_exists($reply, 'data')) { $reply = $reply->data; } foreach ($reply as $value) { return $value; } } /// /// CURL session /// protected static function getPostFields ($arguments) { $items = []; foreach ($arguments as $key => $value) { $items[] = urlencode($key) . '=' . urlencode($value); } return implode('&', $items); } protected static function post ($url, $arguments) { $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"); + throw new \RuntimeException( + "Can't reach Phabricator API endpoint: $url" + ); } return $result; } } diff --git a/app/Phabricator/PhabricatorStory.php b/app/Phabricator/PhabricatorStory.php index 97d0c64..2464c8e 100644 --- a/app/Phabricator/PhabricatorStory.php +++ b/app/Phabricator/PhabricatorStory.php @@ -1,329 +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 ($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) { + public static function loadFromIterable ( + string $instanceName, iterable $payload + ) { $instance = new self($instanceName); foreach ($payload as $key => $value) { $property = self::mapPhabricatorFeedKey($key); $instance->$property = $value; } return $instance; } - /** - * Initializes a new instance of PhabricatorStory from a JSON payload. - * - * This is intended to parse files saved by LastPayloadSaver::savePayload. - * - * @param string $instanceName The Phabricator instance name - * @param string $payload The data submitted by Phabricator's JSON representation - * @return PhabricatorStory - */ - public static function loadFromJson ($instanceName, $payload) { + public static function loadFromJson ( + $instanceName, + $payload + ) { $array = json_decode($payload, true); if (!is_array($array)) { - throw new InvalidArgumentException("Payload should be deserializable as an 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 () { 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 () { 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 ($method) { $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 ($method, $objectPHID) { 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) { + protected function getItemProjectsPHIDsThroughApplicationSearch ( + $method, + $objectPHID + ) { 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 () { 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 () { $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 ($key) { if ($key == "storyID") { return "id"; } if (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 a2a24b6..5e093d2 100644 --- a/app/Phabricator/ProjectsMap.php +++ b/app/Phabricator/ProjectsMap.php @@ -1,278 +1,285 @@ <?php namespace Nasqueron\Notifications\Phabricator; use Nasqueron\Notifications\Contracts\APIClient as APIClient; use App; use 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 ($instanceName) { $this->instanceName = $instanceName; } /// /// IteratorAggregate interface implementation /// /** * Gets iterator. * * @return \Traversable */ public function getIterator () { return new \ArrayIterator($this->map); } /// /// ArrayAccess interface implementation /// /** * Determines whether an offset exists. * * @param mixed $offset The offset * @return bool */ public function offsetExists ($offset) { 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 ($offset) { 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 ($offset, $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 ($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 ($phabricatorInstanceName) { $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. - * - * @param string $phabricatorInstanceName The Phabricator instance name - * @param \Nasqueron\Notifications\Contracts\APIClient|null $apiClient The Phabricator API client - * @return ProjectsMap */ - public static function fetch ($phabricatorInstanceName, ?APIClient $apiClient = null) { + public static function fetch ( + string $phabricatorInstanceName, + ?APIClient $apiClient = null + ) { $instance = new self($phabricatorInstanceName); $instance->setAPIClient($apiClient); $instance->fetchFromAPI(); return $instance; } /// /// API /// /** * @return \Nasqueron\Notifications\Contracts\APIClient */ public function getAPIClient () { 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) { $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("Empty reply calling project.query at $this->instanceName Conduit API."); + throw new \Exception(<<<MSG +Empty reply calling project.query at $this->instanceName Conduit API. +MSG +); } if (!property_exists($reply, 'data')) { - throw new \Exception("Invalid reply calling project.query at $this->instanceName Conduit API."); + 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 () { - return class_basename(get_class($this)) . '-' . md5($this->instanceName); + 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 () { 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 ($projectPHID) { 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'] */ public function toArray () { $array = []; foreach ($this->map as $phid => $projectName) { $array[] = [$phid, $projectName]; } return $array; } } diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 77c55e7..782bb46 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -1,29 +1,31 @@ <?php namespace Nasqueron\Notifications\Providers; -use Illuminate\Contracts\Events\Dispatcher as DispatcherContract; -use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; +use Illuminate\{ + Contracts\Events\Dispatcher as DispatcherContract, + Foundation\Support\Providers\EventServiceProvider as ServiceProvider +}; use Config; class EventServiceProvider extends ServiceProvider { /** * Registers all our listeners as subscriber classes */ private function subscribeListeners () { $this->subscribe += Config::get('app.listeners'); } /** * Register any other events for your application. * * @param \Illuminate\Contracts\Events\Dispatcher $events * @return void */ public function boot(DispatcherContract $events) { $this->subscribeListeners(); parent::boot($events); } } diff --git a/app/Providers/ReportServiceProvider.php b/app/Providers/ReportServiceProvider.php index 42df71a..6a1666c 100644 --- a/app/Providers/ReportServiceProvider.php +++ b/app/Providers/ReportServiceProvider.php @@ -1,40 +1,40 @@ <?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() { $this->app->singleton('report', function (Application $app) { $report = new ActionsReport(); - static::listenToActionsForReport($report, $app->make('events')); + static::listenToActionsForReport( + $report, + $app->make('events') + ); return $report; }); } - /** - * Listen to actions fired by the application to add to the report - * - * @param \Nasqueron\Notifications\Actions\ActionsReport $report The report to add actions to - * @param \Illuminate\Contracts\Events\Dispatcher $events The application events dispatcher - */ - public static function listenToActionsForReport (ActionsReport $report, Dispatcher $events) { + public static function listenToActionsForReport ( + ActionsReport $report, + Dispatcher $events + ) { $events->listen( 'Nasqueron\Notifications\Events\ReportEvent', function (ReportEvent $event) use ($report) { $report->addAction($event->action); } ); } } diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 78624c6..bd2aca4 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -1,44 +1,46 @@ <?php namespace Nasqueron\Notifications\Providers; -use Illuminate\Routing\Router; -use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; +use Illuminate\{ + Routing\Router, + Foundation\Support\Providers\RouteServiceProvider as ServiceProvider +}; 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. * * @param \Illuminate\Routing\Router $router * @return void */ public function boot(Router $router) { // parent::boot($router); } /** * Define the routes for the application. * * @param \Illuminate\Routing\Router $router * @return void */ public function map(Router $router) { $router->group(['namespace' => $this->namespace], function ($router) { require app_path('Http/routes.php'); }); } } diff --git a/tests/Console/Commands/ConfigShowTest.php b/tests/Console/Commands/ConfigShowTest.php index 56e8522..d7e03c2 100644 --- a/tests/Console/Commands/ConfigShowTest.php +++ b/tests/Console/Commands/ConfigShowTest.php @@ -1,103 +1,105 @@ <?php namespace Nasqueron\Notifications\Tests\Console\Commands; use Nasqueron\Notifications\Config\Features; use Nasqueron\Notifications\Config\Services\Service; use Mockery; class ConfigShowTest extends TestCase { /** * @var string */ protected $class = 'Nasqueron\Notifications\Console\Commands\ConfigShow'; /** * Nasqueron\Notifications\Config\Services\Services */ private $servicesMock; public function setUp () { parent::setUp(); $this->servicesMock = $this->mockServices(); } public function testRegularExecute () { //Our command calls Services::get() $this->servicesMock->shouldReceive('get')->once()->andReturn([]); $this->tester->execute(['command' => $this->command->getName()]); $this->assertRegexpInDisplay('/Gates/'); $this->assertRegexpInDisplay('/Features/'); $this->assertRegexpInDisplay('/Services declared/'); } public function testRegularExecuteWithService () { $service = $this->mockService(); $this->servicesMock ->shouldReceive('get') ->once() ->andReturn([$service]); $this->tester->execute(['command' => $this->command->getName()]); $this->assertRegexpInDisplay('/Storm/'); } public function testRegularExecuteWithPhabricatorService () { $this->mockPhabricatorAPIForProjectsMap(); $service = $this->mockService('Phabricator'); $this->servicesMock ->shouldReceive('get') ->once() ->andReturn([$service]); $this->servicesMock ->shouldReceive('findServiceByProperty'); $this->tester->execute(['command' => $this->command->getName()]); $this->assertRegexpInDisplay( '/Phabricator.*Projects map not cached./' ); } protected function mockProjectsMap () { - $mock = Mockery::mock('Nasqueron\Notifications\Phabricator\ProjectsMap'); + $mock = Mockery::mock( + 'Nasqueron\Notifications\Phabricator\ProjectsMap' + ); $this->app->instance('phabricator-projectsmap', $mock); return $mock; } public function testRegularExecuteWithPhabricatorServiceWhenTheProjectsMapIsCached () { // The services list will return only one, for the Phabricator gate. $service = $this->mockService('Phabricator'); $this->servicesMock ->shouldReceive('get')->once()->andReturn([$service]); // The project map (built by the factory) will say it's cached. $this->mockProjectsMap() ->shouldReceive('fetch->isCached')->once()->andReturn(true); $this->tester->execute(['command' => $this->command->getName()]); $this->assertRegexpInDisplay('/Phabricator.*✓/'); } public function testExecuteWhenSomeFeatureIsDisabled () { Features::disable('ActionsReport'); $this->servicesMock->shouldReceive('get')->once()->andReturn([]); $this->tester->execute(['command' => $this->command->getName()]); $this->assertRegexpInDisplay( '/Gate *\| *✓ *\|/' ); $this->assertRegexpInDisplay( '/ActionsReport *\| *\|/' ); } } diff --git a/tests/Console/Commands/NotificationsPayloadTest.php b/tests/Console/Commands/NotificationsPayloadTest.php index 5c62855..d415fc7 100644 --- a/tests/Console/Commands/NotificationsPayloadTest.php +++ b/tests/Console/Commands/NotificationsPayloadTest.php @@ -1,86 +1,86 @@ <?php namespace Nasqueron\Notifications\Tests\Console\Commands; use Nasqueron\Notifications\Console\Commands\NotificationsPayload; class NotificationsPayloadTest extends TestCase { /** * @var string */ - protected $class = 'Nasqueron\Notifications\Console\Commands\NotificationsPayload'; + protected $class = NotificationsPayload::class; public function testRegularExecute () { $path = __DIR__ . '/../../data/payloads/DockerHubPushPayload.json'; $this->tester->execute([ 'command' => $this->command->getName(), 'service' => 'DockerHub', 'payload' => $path, 'args' => [ 'Acme', 'push' ], ]); $this->assertDisplayContains('"service": "DockerHub"'); $this->assertDisplayContains('"project": "Acme"'); $this->assertDisplayContains('svendowideit\/testhook'); } public function testPhabricatorPayload () { $path = __DIR__ . '/../../data/payloads/PhabricatorPastePayload.json'; $this->tester->execute([ 'command' => $this->command->getName(), 'service' => 'Phabricator', 'payload' => $path, 'args' => [ 'Acme', ], ]); $this->assertDisplayContains('"service": "Phabricator"'); $this->assertDisplayContains('"project": "Acme"'); $this->assertDisplayContains('"type": "PSTE"'); } /** * @expectedException InvalidArgumentException */ public function testArgumentsArrayCombine () { NotificationsPayload::argumentsArrayCombine(['foo'], []); } public function testFileNotFound () { $this->tester->execute([ 'command' => $this->command->getName(), 'service' => 'DockerHub', 'payload' => "/tmp/not.found", 'args' => [ 'Acme', 'push' ], ]); $this->assertDisplayContains('File not found: /tmp/not.found'); } public function testServiceNotFound () { $path = __DIR__ . '/../../data/payloads/DockerHubPushPayload.json'; $this->tester->execute([ 'command' => $this->command->getName(), 'service' => 'InterdimensionalTeleport', 'payload' => $path, 'args' => [ 'Acme', 'push' ], ]); $this->assertDisplayContains( 'Unknown service: InterdimensionalTeleport' ); } } diff --git a/tests/Console/Commands/PhabricatorProjectsMapTest.php b/tests/Console/Commands/PhabricatorProjectsMapTest.php index 713e250..2f10021 100644 --- a/tests/Console/Commands/PhabricatorProjectsMapTest.php +++ b/tests/Console/Commands/PhabricatorProjectsMapTest.php @@ -1,33 +1,34 @@ <?php namespace Nasqueron\Notifications\Tests\Console\Commands; use Nasqueron\Notifications\Config\Services\Service; +use Nasqueron\Notifications\Console\Commands\PhabricatorProjectsMap; class PhabricatorProjectsMapTest extends TestCase { /** * @var string */ - protected $class = 'Nasqueron\Notifications\Console\Commands\PhabricatorProjectsMap'; + protected $class = PhabricatorProjectsMap::class; public function setUp () { parent::setUp(); $service = $this->mockService('Phabricator'); $this->mockServices() ->shouldReceive('getForGate') ->once() ->andReturn([$service]); $this->mockPhabricatorAPIForProjectsMap(); } public function testRegularExecute () { $this->tester->execute(['command' => $this->command->getName()]); $this->assertRegexpInDisplay('/PHID.*Project name/'); $this->assertRegexpInDisplay( '/PHID-PROJ-cztcgpvqr6smnnekotq7.*Agora/' ); } } diff --git a/tests/Console/KernelTest.php b/tests/Console/KernelTest.php index cd129d2..af4c756 100644 --- a/tests/Console/KernelTest.php +++ b/tests/Console/KernelTest.php @@ -1,118 +1,125 @@ <?php namespace Nasqueron\Notifications\Tests\Console; use Nasqueron\Notifications\Tests\TestCase; use Nasqueron\Notifications\Console\Kernel; use Illuminate\Contracts\Console\Kernel as BaseKernel; use Artisan; use File; class KernelTest extends TestCase { /** * @var \Nasqueron\Notifications\Console\Kernel */ private $kernel; /** * The actual list of services providers * * @var string[] */ private $commands; /** * The service providers' namespace * * @var string */ private $namespace; public function setUp () { parent::setUp(); $this->kernel = $this->app->make(BaseKernel::class); $this->commands = $this->kernel->all(); $this->namespace = $this->app->getInstance()->getNamespace() . 'Console\\Commands\\'; } public function testOmittedFiles () { $files = File::allFiles(app_path('Console/Commands')); foreach ($files as $file) { $class = $this->namespace . $file->getBasename('.php'); $this->assertArrayContainsInstanceOf( $class, $this->commands, "The class $class should be added to app/Console/Kernel.php." ); } } public function testGet () { $this->assertInstanceOf( \Nasqueron\Notifications\Console\Commands\Inspire::class, $this->kernel->get('inspire') ); } /** * @expectedException \RuntimeException */ public function testGetWhenCommandDoesNotExist () { $this->kernel->get('notexisting'); } public function testGetByClass () { $class = \Nasqueron\Notifications\Console\Commands\Inspire::class; $this->assertInstanceOf($class, $this->kernel->getByClass($class)); } /** * @expectedException \RuntimeException */ public function testGetByClassWhenCommandDoesNotExist () { $this->kernel->getByClass('notexisting'); } /// /// Custom assertions /// /** * Asserts the specified array contains an element of an expected type. * * @param mixed $expectedType The type to find among the array elements * @param array $haystack The array where to find * @param string $message The test message */ - public static function assertArrayContainsInstanceOf ($expectedType, $haystack, $message = '') { + public static function assertArrayContainsInstanceOf ( + $expectedType, + $haystack, + $message = '' + ) { self::assertThat( self::arrayContainsInstanceOf($expectedType, $haystack), self::isTrue(), $message ); } /** * Determines if the specified array contains at least one instance of the * specified type. * * @param mixed $expectedType The type to find among the array elements * @param array $haystack The array where to find * @return bool */ - protected static function arrayContainsInstanceOf ($expectedType, $haystack) { + protected static function arrayContainsInstanceOf ( + $expectedType, + $haystack + ) { foreach ($haystack as $item) { if ($item instanceof $expectedType) { return true; } } return false; } }