mirror of
https://github.com/Icinga/icingaweb2.git
synced 2025-04-08 17:15:08 +02:00
parent
2b15d35dec
commit
290fe9eeb5
@ -29,9 +29,9 @@
|
||||
|
||||
namespace Icinga\Web;
|
||||
|
||||
use Exception;
|
||||
use Icinga\Logger\Logger;
|
||||
use Icinga\Exception\ProgrammingError;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Icinga Web Hook registry
|
||||
@ -79,9 +79,9 @@ class Hook
|
||||
/**
|
||||
* Whether someone registered itself for the given hook name
|
||||
*
|
||||
* @param string $name One of the predefined hook names
|
||||
* @param string $name One of the predefined hook names
|
||||
*
|
||||
* @return bool
|
||||
* @return bool
|
||||
*/
|
||||
public static function has($name)
|
||||
{
|
||||
@ -89,72 +89,29 @@ class Hook
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first registered instance for the given hook name
|
||||
* Create or return an instance of a given hook
|
||||
*
|
||||
* TODO: Multiple instances are not handled yet
|
||||
* TODO: Should return some kind of a hook interface
|
||||
*
|
||||
* @param string $name One of the predefined hook names
|
||||
*
|
||||
* @return mixed
|
||||
* @throws ProgrammingError
|
||||
*/
|
||||
public static function get($name)
|
||||
{
|
||||
if (! self::has($name)) {
|
||||
return null;
|
||||
}
|
||||
if (! array_key_exists($name, self::$instances)) {
|
||||
$class = self::$hooks[$name];
|
||||
try {
|
||||
$obj = new $class();
|
||||
} catch (\Exception $e) {
|
||||
// TODO: Persist unloading for "some time" or "current session"
|
||||
Logger::debug(
|
||||
'Hook "%s" (%s) failed, will be unloaded: %s',
|
||||
$name,
|
||||
$class,
|
||||
$e->getMessage()
|
||||
);
|
||||
unset(self::$hooks[$name]);
|
||||
return null;
|
||||
}
|
||||
$base_class = 'Icinga\\Web\\Hook\\' . ucfirst($name);
|
||||
if (! $obj instanceof $base_class) {
|
||||
throw new ProgrammingError(
|
||||
sprintf(
|
||||
'%s is not an instance of %s',
|
||||
get_class($obj),
|
||||
$base_class
|
||||
)
|
||||
);
|
||||
}
|
||||
self::$instances[$name] = $obj;
|
||||
|
||||
}
|
||||
return self::$instances[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or return an instance of the hook
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $key
|
||||
* @return stdClass
|
||||
* @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)
|
||||
{
|
||||
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) {
|
||||
} catch (Exception $e) {
|
||||
Logger::debug(
|
||||
'Hook "%s" (%s) (%s) failed, will be unloaded: %s',
|
||||
$name,
|
||||
@ -162,9 +119,11 @@ class Hook
|
||||
$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;
|
||||
@ -173,11 +132,12 @@ class Hook
|
||||
/**
|
||||
* Test for a valid class name
|
||||
*
|
||||
* @param stdClass $instance
|
||||
* @param string $name
|
||||
* @throws ProgrammingError
|
||||
* @param mixed $instance
|
||||
* @param string $name
|
||||
*
|
||||
* @throws ProgrammingError
|
||||
*/
|
||||
private static function assertValidHook(&$instance, $name)
|
||||
private static function assertValidHook($instance, $name)
|
||||
{
|
||||
$base_class = self::$BASE_NS . ucfirst($name);
|
||||
if (!$instance instanceof $base_class) {
|
||||
@ -194,43 +154,45 @@ class Hook
|
||||
/**
|
||||
* Return all instances of a specific name
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $name One of the predefined hook names
|
||||
*
|
||||
* @return array
|
||||
* @return array
|
||||
*/
|
||||
public static function all($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
|
||||
* @param string $name One of the predefined hook names
|
||||
*
|
||||
* @return stdClass
|
||||
* @return null|mixed
|
||||
*/
|
||||
public static function first($name)
|
||||
{
|
||||
return self::createInstance($name, key(self::$hooks[$name]));
|
||||
if (self::has($name)) {
|
||||
return self::createInstance($name, key(self::$hooks[$name]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register your hook
|
||||
*
|
||||
* @param string $name One of the predefined hook names
|
||||
* @param string $key
|
||||
* @param string $class Your class name, must inherit one of the classes
|
||||
* in the Icinga/Web/Hook folder
|
||||
* Alias for Hook::registerClass()
|
||||
*
|
||||
* @see Hook::registerClass()
|
||||
*/
|
||||
public static function register($name, $key, $class)
|
||||
{
|
||||
@ -240,12 +202,17 @@ class Hook
|
||||
/**
|
||||
* Register a class
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $key
|
||||
* @param string $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/Web/Hook folder
|
||||
*/
|
||||
public static function registerClass($name, $key, $class)
|
||||
{
|
||||
if (!class_exists($class)) {
|
||||
throw new ProgrammingError('"' . $class . '" is not an existing class');
|
||||
}
|
||||
|
||||
if (!isset(self::$hooks[$name])) {
|
||||
self::$hooks[$name] = array();
|
||||
}
|
||||
@ -256,23 +223,23 @@ class Hook
|
||||
/**
|
||||
* Register an object
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $key
|
||||
* @param object $object
|
||||
* @param string $name One of the predefined hook names
|
||||
* @param string $key The identifier of a specific subtype
|
||||
* @param object $object The instantiated hook to register
|
||||
*
|
||||
* @throws ProgrammingError
|
||||
* @throws ProgrammingError
|
||||
*/
|
||||
public static function registerObject($name, $key, $object)
|
||||
{
|
||||
if (!is_object($object)) {
|
||||
throw new ProgrammingError('object is not an instantiated class');
|
||||
throw new ProgrammingError('"' . $object . '" is not an instantiated class');
|
||||
}
|
||||
|
||||
if (!isset(self::$instances[$name])) {
|
||||
self::$instances[$name] = array();
|
||||
}
|
||||
|
||||
self::$instances[$name][$key] =& $object;
|
||||
self::$instances[$name][$key] = $object;
|
||||
self::registerClass($name, $key, get_class($object));
|
||||
}
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ class ConfigurationTabBuilder
|
||||
{
|
||||
/** @var ConfigurationTab $configTab */
|
||||
$configTab = null;
|
||||
foreach (Hook::get(self::HOOK_NAMESPACE) as $configTab) {
|
||||
foreach (Hook::all(self::HOOK_NAMESPACE) as $configTab) {
|
||||
if (!$configTab instanceof ConfigurationTabInterface) {
|
||||
throw new ProgrammingError('tab not instance of ConfigTabInterface');
|
||||
}
|
||||
|
@ -110,7 +110,6 @@ EOT;
|
||||
$query = $form->getElement('query')->setDecorators(array('ViewHelper'));
|
||||
|
||||
$badges = new FilterBadgeRenderer($this->initialFilter);
|
||||
$form->setIgnoreChangeDiscarding(true);
|
||||
return '<div class="pull-right">' . $badges->render($view) . '</div>' . $form;
|
||||
$html = str_replace('{{FORM}}', $form->render($view), self::$TPL);
|
||||
$html = '<div class="input-append">' . $html . '</div>';
|
||||
|
@ -74,7 +74,7 @@ class Monitoring_ListController extends Controller
|
||||
public function init()
|
||||
{
|
||||
$this->backend = Backend::createBackend($this->_getParam('backend'));
|
||||
$this->view->grapher = Hook::get('grapher');
|
||||
$this->view->grapher = Hook::first('grapher');
|
||||
$this->createTabs();
|
||||
$this->view->activeRowHref = $this->getParam('detail');
|
||||
$this->view->compact = ($this->_request->getParam('view') === 'compact');
|
||||
|
@ -56,7 +56,6 @@ class Zend_View_Helper_CommandForm extends Zend_View_Helper_Abstract
|
||||
{
|
||||
$form = new Form();
|
||||
$form->setAttrib('class', 'inline');
|
||||
$form->setIgnoreChangeDiscarding(true);
|
||||
$form->setRequest(Zend_Controller_Front::getInstance()->getRequest());
|
||||
|
||||
// Filter work only from get parts. Put important
|
||||
|
@ -37,12 +37,12 @@ class Environment
|
||||
|
||||
public static function grapher($env = null)
|
||||
{
|
||||
return Hook::get('grapher', null, self::config($env, 'grapher'));
|
||||
return Hook::createInstance('grapher', null, self::config($env, 'grapher'));
|
||||
}
|
||||
|
||||
public static function configBackend($env = null)
|
||||
{
|
||||
return Hook::get(
|
||||
return Hook::createInstance(
|
||||
'configBackend',
|
||||
null,
|
||||
self::config($env, 'configBackend')
|
||||
|
@ -4,27 +4,16 @@
|
||||
|
||||
namespace Tests\Icinga\Web;
|
||||
|
||||
use \Mockery;
|
||||
use Mockery;
|
||||
use Exception;
|
||||
use Icinga\Web\Hook;
|
||||
use Icinga\Test\BaseTestCase;
|
||||
|
||||
class Base
|
||||
{
|
||||
}
|
||||
|
||||
class TestHookImplementation extends Base
|
||||
{
|
||||
}
|
||||
|
||||
class TestBadHookImplementation
|
||||
{
|
||||
}
|
||||
|
||||
class ErrorProneHookImplementation
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
throw new \Exception("HOOK ERROR");
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,103 +31,182 @@ class HookTest extends BaseTestCase
|
||||
Hook::clean();
|
||||
}
|
||||
|
||||
public function testHas()
|
||||
public function testWhetherHasReturnsTrueIfGivenAKnownHook()
|
||||
{
|
||||
$this->assertFalse(Hook::has("a"));
|
||||
$this->assertFalse(Hook::has("a","b"));
|
||||
Hook::registerClass('TestHook', __FUNCTION__, get_class(Mockery::mock(Hook::$BASE_NS . 'TestHook')));
|
||||
|
||||
Hook::registerClass("a","b","c");
|
||||
$this->assertTrue(Hook::has("a"));
|
||||
$this->assertTrue(Hook::has("a","b"));
|
||||
$this->assertTrue(Hook::has('TestHook'), 'Hook::has does not return true if given a known hook');
|
||||
}
|
||||
|
||||
public function testCreateInstance()
|
||||
public function testWhetherHasReturnsFalseIfGivenAnUnknownHook()
|
||||
{
|
||||
Hook::$BASE_NS = "Tests\\Icinga\\Web\\";
|
||||
Hook::registerClass("Base","b","Tests\\Icinga\\Web\\TestHookImplementation");
|
||||
$this->assertInstanceOf("Tests\\Icinga\\Web\\TestHookImplementation",Hook::createInstance("Base","b"));
|
||||
Hook::clean();
|
||||
$this->assertFalse(Hook::has('not_existing'), 'Hook::has does not return false if given an unknown hook');
|
||||
}
|
||||
|
||||
public function testCreateInvalidInstance1()
|
||||
public function testWhetherHooksCanBeRegisteredWithRegisterClass()
|
||||
{
|
||||
$this->setExpectedException('\Icinga\Exception\ProgrammingError');
|
||||
Hook::$BASE_NS = "Tests\\Icinga\\Web\\";
|
||||
Hook::registerClass("Base","b","Tests\\Icinga\\Web\\TestBadHookImplementation");
|
||||
Hook::createInstance("Base","b");
|
||||
Hook::clean();
|
||||
Hook::registerClass('TestHook', __FUNCTION__, get_class(Mockery::mock(Hook::$BASE_NS . 'TestHook')));
|
||||
|
||||
$this->assertTrue(Hook::has('TestHook'), 'Hook::registerClass does not properly register a given hook');
|
||||
}
|
||||
|
||||
public function testCreateInvalidInstance2()
|
||||
/**
|
||||
* @depends testWhetherHooksCanBeRegisteredWithRegisterClass
|
||||
*/
|
||||
public function testWhetherMultipleHooksOfTheSameTypeCanBeRegisteredWithRegisterClass()
|
||||
{
|
||||
Hook::$BASE_NS = "Tests\\Icinga\\Web\\";
|
||||
$test = Hook::createInstance("Base","NOTEXIST");
|
||||
$this->assertNull($test);
|
||||
}
|
||||
$firstHook = Mockery::mock(Hook::$BASE_NS . 'TestHook');
|
||||
$secondHook = Mockery::mock('overload:' . get_class($firstHook));
|
||||
|
||||
public function testCreateInvalidInstance3()
|
||||
{
|
||||
Hook::$BASE_NS = "Tests\\Icinga\\Web\\";
|
||||
Hook::register("Base","ErrorProne","Tests\\Icinga\\Web\\ErrorProneHookImplementation");
|
||||
$test = Hook::createInstance("Base","ErrorProne");
|
||||
$this->assertNull($test);
|
||||
}
|
||||
|
||||
public function testAll()
|
||||
{
|
||||
Hook::$BASE_NS = "Tests\\Icinga\\Web\\";
|
||||
Hook::registerClass("Base","a","Tests\\Icinga\\Web\\TestHookImplementation");
|
||||
Hook::registerClass("Base","b","Tests\\Icinga\\Web\\TestHookImplementation");
|
||||
Hook::registerClass("Base","c","Tests\\Icinga\\Web\\TestHookImplementation");
|
||||
$this->assertCount(3,Hook::all("Base"));
|
||||
foreach(Hook::all("Base") as $instance) {
|
||||
$this->assertInstanceOf("Tests\\Icinga\\Web\\TestHookImplementation",$instance);
|
||||
}
|
||||
}
|
||||
|
||||
public function testFirst()
|
||||
{
|
||||
Hook::$BASE_NS = "Tests\\Icinga\\Web\\";
|
||||
Hook::registerClass("Base","a","Tests\\Icinga\\Web\\TestHookImplementation");
|
||||
Hook::registerClass("Base","b","Tests\\Icinga\\Web\\TestHookImplementation");
|
||||
Hook::registerClass("Base","c","Tests\\Icinga\\Web\\TestHookImplementation");
|
||||
|
||||
$this->assertInstanceOf("Tests\\Icinga\\Web\\TestHookImplementation",Hook::first("Base"));
|
||||
}
|
||||
|
||||
public function testRegisterObject()
|
||||
{
|
||||
$o1 = Mockery::mock('Some\\Name\\Space\\ObjectHook');
|
||||
$o1->test = '$123123';
|
||||
$o2 = Mockery::mock('Some\\Name\\Space\\ObjectHook');
|
||||
$o2->test = '#456456';
|
||||
|
||||
Hook::registerObject('Test', 'o1', $o1);
|
||||
Hook::registerObject('Test', 'o2', $o2);
|
||||
|
||||
$this->assertInstanceOf('Some\\Name\\Space\\ObjectHook', Hook::createInstance('Test', 'o1'));
|
||||
$this->assertInstanceOf('Some\\Name\\Space\\ObjectHook', Hook::createInstance('Test', 'o2'));
|
||||
|
||||
$string = "";
|
||||
foreach (Hook::all('Test') as $hook) {
|
||||
$string .= $hook->test;
|
||||
}
|
||||
$this->assertEquals('$123123#456456', $string);
|
||||
Hook::registerClass('TestHook', 'one', get_class($firstHook));
|
||||
Hook::registerClass('TestHook', 'two', get_class($secondHook));
|
||||
$this->assertInstanceOf(
|
||||
get_class($secondHook),
|
||||
Hook::createInstance('TestHook', 'two'),
|
||||
'Hook::registerClass is not able to register different hooks of the same type'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Icinga\Exception\ProgrammingError
|
||||
* @expectedExceptionMessage object is not an instantiated class
|
||||
*/
|
||||
public function testErrorObjectRegistration()
|
||||
public function testWhetherOnlyClassesCanBeRegisteredAsHookWithRegisterClass()
|
||||
{
|
||||
Hook::registerObject('Test', 'e1', 'STRING');
|
||||
Hook::registerClass('TestHook', __FUNCTION__, 'nope');
|
||||
}
|
||||
|
||||
public function testGetZeroHooks()
|
||||
public function testWhetherHooksCanBeRegisteredWithRegisterObject()
|
||||
{
|
||||
$nh = Hook::all('DOES_NOT_EXIST');
|
||||
$this->assertInternalType('array', $nh);
|
||||
$this->assertCount(0, $nh);
|
||||
Hook::registerObject('TestHook', __FUNCTION__, Mockery::mock(Hook::$BASE_NS . 'TestHook'));
|
||||
|
||||
$this->assertTrue(Hook::has('TestHook'), 'Hook::registerObject does not properly register a given hook');
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testWhetherHooksCanBeRegisteredWithRegisterObject
|
||||
*/
|
||||
public function testWhetherMultipleHooksOfTheSameTypeCanBeRegisteredWithRegisterObject()
|
||||
{
|
||||
$firstHook = Mockery::mock(Hook::$BASE_NS . 'TestHook');
|
||||
$secondHook = Mockery::mock('overload:' . get_class($firstHook));
|
||||
|
||||
Hook::registerObject('TestHook', 'one', $firstHook);
|
||||
Hook::registerObject('TestHook', 'two', $secondHook);
|
||||
$this->assertInstanceOf(
|
||||
get_class($secondHook),
|
||||
Hook::createInstance('TestHook', 'two'),
|
||||
'Hook::registerObject is not able to register different hooks of the same type'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Icinga\Exception\ProgrammingError
|
||||
*/
|
||||
public function testWhetherOnlyObjectsCanBeRegisteredAsHookWithRegisterObject()
|
||||
{
|
||||
Hook::registerObject('TestHook', __FUNCTION__, 'nope');
|
||||
}
|
||||
|
||||
public function testWhetherCreateInstanceReturnsNullIfGivenAnUnknownHookName()
|
||||
{
|
||||
$this->assertNull(
|
||||
Hook::createInstance('not_existing', __FUNCTION__),
|
||||
'Hook::createInstance does not return null if given an unknown hook'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testWhetherHooksCanBeRegisteredWithRegisterClass
|
||||
*/
|
||||
public function testWhetherCreateInstanceInitializesHooksInheritingFromAPredefinedAbstractHook()
|
||||
{
|
||||
$baseHook = Mockery::mock(Hook::$BASE_NS . 'TestHook');
|
||||
Hook::registerClass(
|
||||
'TestHook',
|
||||
__FUNCTION__,
|
||||
get_class(Mockery::mock('overload:' . get_class($baseHook)))
|
||||
);
|
||||
|
||||
$this->assertInstanceOf(
|
||||
get_class($baseHook),
|
||||
Hook::createInstance('TestHook', __FUNCTION__),
|
||||
'Hook::createInstance does not initialize hooks inheriting from a predefined abstract hook'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testWhetherHooksCanBeRegisteredWithRegisterClass
|
||||
*/
|
||||
public function testWhetherCreateInstanceDoesNotInitializeMultipleHooksForASpecificIdentifier()
|
||||
{
|
||||
Hook::registerClass('TestHook', __FUNCTION__, get_class(Mockery::mock(Hook::$BASE_NS . 'TestHook')));
|
||||
$secondHook = Hook::createInstance('TestHook', __FUNCTION__);
|
||||
$thirdHook = Hook::createInstance('TestHook', __FUNCTION__);
|
||||
|
||||
$this->assertSame(
|
||||
$secondHook,
|
||||
$thirdHook,
|
||||
'Hook::createInstance initializes multiple hooks for a specific identifier'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testWhetherHooksCanBeRegisteredWithRegisterClass
|
||||
*/
|
||||
public function testWhetherCreateInstanceReturnsNullIfHookCannotBeInitialized()
|
||||
{
|
||||
Hook::registerClass('TestHook', __FUNCTION__, 'Tests\Icinga\Web\ErrorProneHookImplementation');
|
||||
|
||||
$this->assertNull(Hook::createInstance('TestHook', __FUNCTION__));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Icinga\Exception\ProgrammingError
|
||||
* @depends testWhetherHooksCanBeRegisteredWithRegisterClass
|
||||
*/
|
||||
public function testWhetherCreateInstanceThrowsAnErrorIfGivenAHookNotInheritingFromAPredefinedAbstractHook()
|
||||
{
|
||||
Mockery::mock(Hook::$BASE_NS . 'TestHook');
|
||||
Hook::registerClass('TestHook', __FUNCTION__, get_class(Mockery::mock('TestHook')));
|
||||
|
||||
Hook::createInstance('TestHook', __FUNCTION__);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testWhetherHooksCanBeRegisteredWithRegisterObject
|
||||
*/
|
||||
public function testWhetherAllReturnsAllRegisteredHooks()
|
||||
{
|
||||
$hook = Mockery::mock(Hook::$BASE_NS . 'TestHook');
|
||||
Hook::registerObject('TestHook', 'one', $hook);
|
||||
Hook::registerObject('TestHook', 'two', $hook);
|
||||
Hook::registerObject('TestHook', 'three', $hook);
|
||||
|
||||
$this->assertCount(3, Hook::all('TestHook'), 'Hook::all does not return all registered hooks');
|
||||
}
|
||||
|
||||
public function testWhetherAllReturnsNothingIfGivenAnUnknownHookName()
|
||||
{
|
||||
$this->assertEmpty(
|
||||
Hook::all('not_existing'),
|
||||
'Hook::all does not return an empty array if given an unknown hook'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testWhetherHooksCanBeRegisteredWithRegisterObject
|
||||
*/
|
||||
public function testWhetherFirstReturnsTheFirstRegisteredHook()
|
||||
{
|
||||
$firstHook = Mockery::mock(Hook::$BASE_NS . 'TestHook');
|
||||
$secondHook = Mockery::mock(Hook::$BASE_NS . 'TestHook');
|
||||
Hook::registerObject('TestHook', 'first', $firstHook);
|
||||
Hook::registerObject('TestHook', 'second', $secondHook);
|
||||
|
||||
$this->assertSame(
|
||||
$firstHook,
|
||||
Hook::first('TestHook'),
|
||||
'Hook::first does not return the first registered hook'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user