Skip to content

Commit

Permalink
Merge branch 'feature/hook-api-10613'
Browse files Browse the repository at this point in the history
refs #10613
  • Loading branch information
Thomas-Gelf committed Nov 13, 2015
2 parents 8f8d251 + fd71653 commit 30f8faf
Show file tree
Hide file tree
Showing 20 changed files with 806 additions and 539 deletions.
256 changes: 256 additions & 0 deletions library/Icinga/Application/Hook.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */

namespace Icinga\Application;

use Exception;
use Icinga\Application\Logger;
use Icinga\Exception\ProgrammingError;

/**
* Icinga Hook registry
*
* Modules making use of predefined hooks have to use this registry
*
* Usage:
* <code>
* Hook::register('grapher', 'My\\Grapher\\Class');
* </code>
*/
class Hook
{
/**
* Our hook name registry
*
* @var array
*/
protected static $hooks = array();

/**
* Hooks that have already been instantiated
*
* @var array
*/
protected static $instances = array();

/**
* Namespace prefix
*
* @var string
*/
public static $BASE_NS = 'Icinga\\Application\\Hook\\';

/**
* Append this string to base class
*
* All base classes renamed to *Hook
*
* @var string
*/
public static $classSuffix = 'Hook';

/**
* Reset object state
*/
public static function clean()
{
self::$hooks = array();
self::$instances = array();
self::$BASE_NS = 'Icinga\\Application\\Hook\\';
}

/**
* Whether someone registered itself for the given hook name
*
* @param string $name One of the predefined hook names
*
* @return bool
*/
public static function has($name)
{
$name = self::normalizeHookName($name);
return array_key_exists($name, self::$hooks);
}

protected static function normalizeHookName($name)
{
if (strpos($name, '\\') === false) {
$parts = explode('/', $name);
foreach ($parts as & $part) {
$part = ucfirst($part);
}

return implode('\\', $parts);
}

return $name;
}

/**
* Create or return an instance of a given hook
*
* TODO: Should return some kind of a hook interface
*
* @param string $name One of the predefined hook names
* @param string $key The identifier of a specific subtype
*
* @return mixed
*/
public static function createInstance($name, $key)
{
$name = self::normalizeHookName($name);

if (!self::has($name, $key)) {
return null;
}

if (isset(self::$instances[$name][$key])) {
return self::$instances[$name][$key];
}

$class = self::$hooks[$name][$key];
try {
$instance = new $class();
} catch (Exception $e) {
Logger::debug(
'Hook "%s" (%s) (%s) failed, will be unloaded: %s',
$name,
$key,
$class,
$e->getMessage()
);
// TODO: Persist unloading for "some time" or "current session"
unset(self::$hooks[$name][$key]);
return null;
}

self::assertValidHook($instance, $name);
self::$instances[$name][$key] = $instance;
return $instance;
}

protected static function splitHookName($name)
{
$sep = '\\';
if (false === $module = strpos($name, $sep)) {
return array(null, $name);
}
return array(
substr($name, 0, $module),
substr($name, $module + 1)
);
}

/**
* Test for a valid class name
*
* @param mixed $instance
* @param string $name
*
* @throws ProgrammingError
*/
private static function assertValidHook($instance, $name)
{
$name = self::normalizeHookName($name);

$suffix = self::$classSuffix; // 'Hook'
$base = self::$BASE_NS; // 'Icinga\\Web\\Hook\\'

list($module, $name) = self::splitHookName($name);

if ($module === null) {
$base_class = $base . ucfirst($name) . 'Hook';

// I'm unsure whether this makes sense. Unused and Wrong.
if (strpos($base_class, $suffix) === false) {
$base_class .= $suffix;
}
} else {
$base_class = 'Icinga\\Module\\'
. ucfirst($module)
. '\\Hook\\'
. ucfirst($name)
. $suffix;
}

if (!$instance instanceof $base_class) {

// This is a compatibility check. Should be removed one far day:
if ($module !== null) {
$compat_class = 'Icinga\\Module\\'
. ucfirst($module)
. '\\Web\\Hook\\'
. ucfirst($name)
. $suffix;

if ($instance instanceof $compat_class) {
return;
}
}

throw new ProgrammingError(
'%s is not an instance of %s',
get_class($instance),
$base_class
);
}
}

/**
* Return all instances of a specific name
*
* @param string $name One of the predefined hook names
*
* @return array
*/
public static function all($name)
{
$name = self::normalizeHookName($name);
if (!self::has($name)) {
return array();
}

foreach (self::$hooks[$name] as $key => $hook) {
if (self::createInstance($name, $key) === null) {
return array();
}
}

return self::$instances[$name];
}

/**
* Get the first hook
*
* @param string $name One of the predefined hook names
*
* @return null|mixed
*/
public static function first($name)
{
$name = self::normalizeHookName($name);

if (self::has($name)) {
return self::createInstance($name, key(self::$hooks[$name]));
}
}

/**
* Register a class
*
* @param string $name One of the predefined hook names
* @param string $key The identifier of a specific subtype
* @param string $class Your class name, must inherit one of the
* classes in the Icinga/Application/Hook folder
*/
public static function register($name, $key, $class)
{
$name = self::normalizeHookName($name);

if (!isset(self::$hooks[$name])) {
self::$hooks[$name] = array();
}

self::$hooks[$name][$key] = $class;
}
}
111 changes: 111 additions & 0 deletions library/Icinga/Application/Hook/GrapherHook.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */

namespace Icinga\Application\Hook;

use Icinga\Exception\ProgrammingError;
use Icinga\Module\Monitoring\Object\MonitoredObject;

/**
* Icinga Web Grapher Hook base class
*
* Extend this class if you want to integrate your graphing solution nicely into
* Icinga Web.
*/
abstract class GrapherHook extends WebBaseHook
{
/**
* Whether this grapher provides previews
*
* @var bool
*/
protected $hasPreviews = false;

/**
* Whether this grapher provides tiny previews
*
* @var bool
*/
protected $hasTinyPreviews = false;

/**
* Constructor must live without arguments right now
*
* Therefore the constructor is final, we might change our opinion about
* this one far day
*/
final public function __construct()
{
$this->init();
}

/**
* Overwrite this function if you want to do some initialization stuff
*
* @return void
*/
protected function init()
{
}

/**
* Whether this grapher provides previews
*
* @return bool
*/
public function hasPreviews()
{
return $this->hasPreviews;
}

/**
* Whether this grapher provides tiny previews
*
* @return bool
*/
public function hasTinyPreviews()
{
return $this->hasTinyPreviews;
}

/**
* Whether a graph for the monitoring object exist
*
* @param MonitoredObject $object
*
* @return bool
*/
abstract public function has(MonitoredObject $object);

/**
* Get a preview for the given object
*
* This function must return an empty string if no graph exists.
*
* @param MonitoredObject $object
*
* @return string
* @throws ProgrammingError
*
*/
public function getPreviewHtml(MonitoredObject $object)
{
throw new ProgrammingError('This hook provide previews but it is not implemented');
}


/**
* Get a tiny preview for the given object
*
* This function must return an empty string if no graph exists.
*
* @param MonitoredObject $object
*
* @return string
* @throws ProgrammingError
*/
public function getTinyPreviewHtml(MonitoredObject $object)
{
throw new ProgrammingError('This hook provide tiny previews but it is not implemented');
}
}
Loading

0 comments on commit 30f8faf

Please sign in to comment.