Skip to content

Commit

Permalink
Add Hook mechanism in order to trigger contents. #1120
Browse files Browse the repository at this point in the history
  • Loading branch information
nadar committed Dec 21, 2016
1 parent 55c90e1 commit a0fca92
Show file tree
Hide file tree
Showing 4 changed files with 319 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ The changelog contains informations about bug fixes, new features or bc breaking

### Added

- [#1120](https://github.com/luyadev/luya/issues/1120) Add Hook mechanism in order to trigger contents.
- [#1115](https://github.com/luyadev/luya/issues/1115) ActiveDataProvider default sorting configuration for news article overview.
- [#1018](https://github.com/luyadev/luya/issues/1018) NgRest SelectModel Plugins where conditions, labelFields and labelTemplate properties added.
- [#1010](https://github.com/luyadev/luya/issues/1010) Ability to soft delete admin languages if its not the system default language.
Expand Down
132 changes: 132 additions & 0 deletions core/Hook.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<?php

namespace luya;

use yii\base\Object;
use luya\base\HookEvent;

/**
* Simple Hooking mechanism.
*
* Attaching callables or object methods to the Hook mechnism in your controller, block or elsewhere.
*
* A simple string output behavior:
*
* ```php
* Hook::on('fooBar', function() {
* return 'Hello World';
* });
* ```
*
* In order to trigger the hook output somewher in your layout file use:
*
* ```php
* echo Hook::string('fooBar');
* ```
*
* In order to generate iteration output.
*
* ```php
* Hook::on('fooBarArray', function($hook) {
* $hook[] = 'Hello';
* $hook[] = 'World';
* });
* ```
*
* The array would print:
*
* ```php
* $array = Hook::iterate('fooBarArray'); // Array ( 0 => 'Hello', 1 => 'World')
* ```
*
* @since 1.0.0
* @author Basil Suter <basil@nadar.io>
*/
class Hook extends Object
{
private static $_hooks = [];

/**
* Register a hook listener.
*
* Define the name of the type of listener.
*
* @param string $name The name of the hook.
* @param callable|array $value An array with `[$object, 'method']` or a callable function `function($hook) {}`.
* @param boolean $prepend Whether to prepend the item to the start or not, defaults to false.
*/
public static function on($name, $value, $prepend = false)
{
/// @TODO implement prepend
static::$_hooks[$name][] = new HookEvent(['handler' => $value]);
}

/**
* Trigger the hooks and returns the {{luya\base\HookEvent}} objects.
*
* @param string $name The hook name.
* @return array An array with {{luya\base\HookEvent}} objects.
*/
protected static function trigger($name)
{
if (isset(static::$_hooks[$name])) {

$events = [];

foreach (static::$_hooks[$name] as $hookEvent) {

if ($hookEvent->isHandled) {
continue;
}

if (is_callable($hookEvent->handler)) {
$hookEvent->output = call_user_func($hookEvent->handler, $hookEvent);
} elseif (is_array($hookValue)) {
$hookEvent->output = call_user_func_array($hookEvent->handler, [$hookEvent]);
}

$hookEvent->isHandled = true;

if ($hookEvent->isValid) {
$events[] = $hookEvent;
}
}

return $events;
}

return [];
}

/**
* Get the string output of the hooks.
*
* @param string $name The name of the hook to trigger.
* @return string
*/
public static function string($name)
{
$buffer = [];
foreach (self::trigger($name) as $hook) {
$buffer[] = $hook->output;
}

return implode("", $buffer);
}

/**
* Get the array output of iteration hooks.
*
* @param string $name The name of the hook to trigger.
* @return array
*/
public static function iterate($name)
{
$buffer = [];
foreach (self::trigger($name) as $hook) {
$buffer = array_merge($buffer, $hook->getIterations());
}

return $buffer;
}
}
82 changes: 82 additions & 0 deletions core/base/HookEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

namespace luya\base;

use yii\base\Object;

/**
* HookEvent Object.
*
* Each Hook Event is represented and evaluated by this Object. See {{luya\Hook}}.
*
* @since 1.0.0
* @author Basil Suter <basil@nadar.io>
*/
final class HookEvent extends Object implements \ArrayAccess
{
/**
* @var array|callable The executable handler to use when calling the hook event in order to generate the $output.
*/
public $handler;

/**
* @var string The output which is generated when the $handler is called, this is used in order to return string outputs.
*/
public $output;

/**
* @var boolean Whether the hook event is handled already or not.
*/
public $isHandled = false;

/**
* @var boolean Whether the current event is valid or not, if set to false, this event will not be triggered on output.
*/
public $isValid = true;

private $_iterations = [];

/**
* Getter method for iteration.
*
* @return array
*/
public function getIterations()
{
return $this->_iterations;
}

/**
* Add Iteration
* @param mixed $value The value of the key
* @param string $key The key of the array
*/
public function iteration($value, $key = null)
{
if (is_null($key)) {
$this->_iterations[] = $value;
} else {
$this->_iterations[$key] = $value;
}
}

public function offsetSet($offset, $value)
{
$this->iteration($value, $offset);
}

public function offsetExists($offset)
{
return isset($this->_iterations[$offset]);
}

public function offsetUnset($offset)
{
unset($this->_iterations[$offset]);
}

public function offsetGet($offset)
{
return isset($this->_iterations[$offset]) ? $this->_iterations[$offset] : null;
}
}
104 changes: 104 additions & 0 deletions tests/core/HookTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<?php

namespace luyatests\core;

use luyatests\LuyaWebTestCase;
use luya\Hook;

class TestClass
{
public function helloHook($hook)
{
$hook->iteration('hello');
}

public function worldHook($hook)
{
$hook->iteration('world');
}
}

class HookTest extends LuyaWebTestCase
{
public function testStringHooks()
{
Hook::on('fooBar', function($hook) {
return 'Hello World';
});

$this->assertSame('Hello World', Hook::string('fooBar'));

Hook::on('fooBar', function($hook) {
return 'Another';
});
Hook::on('fooBar', function($hook) {
return 'Test';
});

$this->assertSame('AnotherTest', Hook::string('fooBar'));
}

public function testIterationHooks()
{
Hook::on('fooBar', function($hook) {
$hook->iteration('hello');
$hook->iteration('world');
});

$this->assertSame(['hello', 'world'], Hook::iterate('fooBar'));


Hook::on('testIterator', function($hook) {
$hook->iteration('hello');
$hook->iteration('world');
});

Hook::on('testIterator', function($hook) {
$hook->iteration('another');
$hook->iteration('world');
});

$this->assertSame(['hello', 'world', 'another', 'world'], Hook::iterate('testIterator'));
}

public function testIterationWithObjectHooks()
{
$obj = new TestClass();
Hook::on('fooBar', [$obj, 'helloHook']);
Hook::on('fooBar', [$obj, 'worldHook']);

$this->assertSame(['hello', 'world'], Hook::iterate('fooBar'));
}

public function testIterationArrayAccessHook()
{
Hook::on('fooBar', function($hook) {
$hook[] = 'Hello';
$hook[] = 'World';

$hook['foo'] = 'Bar';
$hook['unfoo'] = 'Unfoo';

if ($hook['unfoo']) {
unset($hook['unfoo']);
}
});

$this->assertSame(['Hello', 'World', 'foo' => 'Bar'], Hook::iterate('fooBar'));
}

public function testIterationOverrideKey()
{
Hook::on('fooBar', function($hook) {
$hook['test'] = 'value1';
$hook['test'] = 'value2';
});

Hook::on('fooBar', function($hook) {
$hook['test'] = 'value3';
$hook['test'] = 'value4';
});

$this->assertSame(['test' => 'value4'], Hook::iterate('fooBar'));
}
}

0 comments on commit a0fca92

Please sign in to comment.