aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRobin Appelman <icewind@owncloud.com>2013-05-07 16:34:09 +0200
committerRobin Appelman <icewind@owncloud.com>2013-05-07 16:34:09 +0200
commitb8a421a86db09c7ed106c2f9aee15aa337761e4a (patch)
treed972ba893a35c004f5454924899d8db95f585659
parent2208313e02f0c1d5ae728115f81e040eaedaa0dc (diff)
downloadnextcloud-server-b8a421a86db09c7ed106c2f9aee15aa337761e4a.tar.gz
nextcloud-server-b8a421a86db09c7ed106c2f9aee15aa337761e4a.zip
New hook system
-rw-r--r--lib/hooks/basicemitter.php89
-rw-r--r--lib/hooks/emitter.php32
-rw-r--r--lib/hooks/legacyemitter.php16
-rw-r--r--tests/lib/hooks/basicemitter.php261
-rw-r--r--tests/lib/hooks/legacyemitter.php55
5 files changed, 453 insertions, 0 deletions
diff --git a/lib/hooks/basicemitter.php b/lib/hooks/basicemitter.php
new file mode 100644
index 00000000000..bd24539a40b
--- /dev/null
+++ b/lib/hooks/basicemitter.php
@@ -0,0 +1,89 @@
+<?php
+/**
+ * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace OC\Hooks;
+
+abstract class BasicEmitter implements Emitter {
+
+ /**
+ * @var (callable[])[] $listeners
+ */
+ private $listeners = array();
+
+ /**
+ * @param string $scope
+ * @param string $method
+ * @param callable $callback
+ */
+ public function listen($scope, $method, $callback) {
+ $eventName = $scope . '::' . $method;
+ if (!isset($this->listeners[$eventName])) {
+ $this->listeners[$eventName] = array();
+ }
+ if (array_search($callback, $this->listeners[$eventName]) === false) {
+ $this->listeners[$eventName][] = $callback;
+ }
+ }
+
+ /**
+ * @param string $scope optional
+ * @param string $method optional
+ * @param callable $callback optional
+ */
+ public function remoteListener($scope = null, $method = null, $callback = null) {
+ $names = array();
+ $allNames = array_keys($this->listeners);
+ if ($scope and $method) {
+ $name = $scope . '::' . $method;
+ if (isset($this->listeners[$name])) {
+ $names[] = $name;
+ }
+ } elseif ($scope) {
+ foreach ($allNames as $name) {
+ $parts = explode('::', $name, 2);
+ if ($parts[0] == $scope) {
+ $names[] = $name;
+ }
+ }
+ } elseif ($method) {
+ foreach ($allNames as $name) {
+ $parts = explode('::', $name, 2);
+ if ($parts[1] == $method) {
+ $names[] = $name;
+ }
+ }
+ } else {
+ $names = $allNames;
+ }
+
+ foreach ($names as $name) {
+ if ($callback) {
+ $index = array_search($callback, $this->listeners[$name]);
+ if ($index !== false) {
+ unset($this->listeners[$name][$index]);
+ }
+ } else {
+ $this->listeners[$name] = array();
+ }
+ }
+ }
+
+ /**
+ * @param string $scope
+ * @param string $method
+ * @param array $arguments optional
+ */
+ protected function emit($scope, $method, $arguments = array()) {
+ $eventName = $scope . '::' . $method;
+ if (isset($this->listeners[$eventName])) {
+ foreach ($this->listeners[$eventName] as $callback) {
+ call_user_func_array($callback, $arguments);
+ }
+ }
+ }
+}
diff --git a/lib/hooks/emitter.php b/lib/hooks/emitter.php
new file mode 100644
index 00000000000..4219b6f3545
--- /dev/null
+++ b/lib/hooks/emitter.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace OC\Hooks;
+
+/**
+ * Class Emitter
+ *
+ * interface for all classes that are able to emit events
+ *
+ * @package OC\Hooks
+ */
+interface Emitter {
+ /**
+ * @param string $scope
+ * @param string $method
+ * @param callable $callback
+ */
+ public function listen($scope, $method, $callback);
+
+ /**
+ * @param string $scope optional
+ * @param string $method optional
+ * @param callable $callback optional
+ */
+ public function remoteListener($scope = null, $method = null, $callback = null);
+}
diff --git a/lib/hooks/legacyemitter.php b/lib/hooks/legacyemitter.php
new file mode 100644
index 00000000000..a2d16ace9a7
--- /dev/null
+++ b/lib/hooks/legacyemitter.php
@@ -0,0 +1,16 @@
+<?php
+/**
+ * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace OC\Hooks;
+
+abstract class LegacyEmitter extends BasicEmitter {
+ protected function emit($scope, $method, $arguments = array()) {
+ \OC_Hook::emit($scope, $method, $arguments);
+ parent::emit($scope, $method, $arguments);
+ }
+}
diff --git a/tests/lib/hooks/basicemitter.php b/tests/lib/hooks/basicemitter.php
new file mode 100644
index 00000000000..53de996c5c9
--- /dev/null
+++ b/tests/lib/hooks/basicemitter.php
@@ -0,0 +1,261 @@
+<?php
+/**
+ * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace Test\Hooks;
+
+/**
+ * Class DummyEmitter
+ *
+ * class to make BasicEmitter::emit publicly available
+ *
+ * @package Test\Hooks
+ */
+class DummyEmitter extends \OC\Hooks\BasicEmitter {
+ public function emitEvent($scope, $method, $arguments = array()) {
+ $this->emit($scope, $method, $arguments);
+ }
+}
+
+/**
+ * Class EmittedException
+ *
+ * a dummy exception so we can check if an event is emitted
+ *
+ * @package Test\Hooks
+ */
+class EmittedException extends \Exception {
+}
+
+class BasicEmitter extends \PHPUnit_Framework_TestCase {
+ /**
+ * @var \OC\Hooks\Emitter $emitter
+ */
+ protected $emitter;
+
+ public function setUp() {
+ $this->emitter = new DummyEmitter();
+ }
+
+ public function nonStaticCallBack() {
+ throw new EmittedException;
+ }
+
+ public static function staticCallBack() {
+ throw new EmittedException;
+ }
+
+ /**
+ * @expectedException \Test\Hooks\EmittedException
+ */
+ public function testAnonymousFunction() {
+ $this->emitter->listen('Test', 'test', function () {
+ throw new EmittedException;
+ });
+ $this->emitter->emitEvent('Test', 'test');
+ }
+
+ /**
+ * @expectedException \Test\Hooks\EmittedException
+ */
+ public function testStaticCallback() {
+ $this->emitter->listen('Test', 'test', array('\Test\Hooks\BasicEmitter', 'staticCallBack'));
+ $this->emitter->emitEvent('Test', 'test');
+ }
+
+ /**
+ * @expectedException \Test\Hooks\EmittedException
+ */
+ public function testNonStaticCallback() {
+ $this->emitter->listen('Test', 'test', array($this, 'nonStaticCallBack'));
+ $this->emitter->emitEvent('Test', 'test');
+ }
+
+ public function testOnlyCallOnce() {
+ $count = 0;
+ $listener = function () use (&$count) {
+ $count++;
+ };
+ $this->emitter->listen('Test', 'test', $listener);
+ $this->emitter->listen('Test', 'test', $listener);
+ $this->emitter->emitEvent('Test', 'test');
+ $this->assertEquals(1, $count, 'Listener called an invalid number of times (' . $count . ') expected 1');
+ }
+
+ public function testDifferentMethods() {
+ $count = 0;
+ $listener = function () use (&$count) {
+ $count++;
+ };
+ $this->emitter->listen('Test', 'test', $listener);
+ $this->emitter->listen('Test', 'foo', $listener);
+ $this->emitter->emitEvent('Test', 'test');
+ $this->emitter->emitEvent('Test', 'foo');
+ $this->assertEquals(2, $count, 'Listener called an invalid number of times (' . $count . ') expected 2');
+ }
+
+ public function testDifferentScopes() {
+ $count = 0;
+ $listener = function () use (&$count) {
+ $count++;
+ };
+ $this->emitter->listen('Test', 'test', $listener);
+ $this->emitter->listen('Bar', 'test', $listener);
+ $this->emitter->emitEvent('Test', 'test');
+ $this->emitter->emitEvent('Bar', 'test');
+ $this->assertEquals(2, $count, 'Listener called an invalid number of times (' . $count . ') expected 2');
+ }
+
+ public function testDifferentCallbacks() {
+ $count = 0;
+ $listener1 = function () use (&$count) {
+ $count++;
+ };
+ $listener2 = function () use (&$count) {
+ $count++;
+ };
+ $this->emitter->listen('Test', 'test', $listener1);
+ $this->emitter->listen('Test', 'test', $listener2);
+ $this->emitter->emitEvent('Test', 'test');
+ $this->assertEquals(2, $count, 'Listener called an invalid number of times (' . $count . ') expected 2');
+ }
+
+ /**
+ * @expectedException \Test\Hooks\EmittedException
+ */
+ public function testArguments() {
+ $this->emitter->listen('Test', 'test', function ($foo, $bar) {
+ if ($foo == 'foo' and $bar == 'bar') {
+ throw new EmittedException;
+ }
+ });
+ $this->emitter->emitEvent('Test', 'test', array('foo', 'bar'));
+ }
+
+ /**
+ * @expectedException \Test\Hooks\EmittedException
+ */
+ public function testNamedArguments() {
+ $this->emitter->listen('Test', 'test', function ($foo, $bar) {
+ if ($foo == 'foo' and $bar == 'bar') {
+ throw new EmittedException;
+ }
+ });
+ $this->emitter->emitEvent('Test', 'test', array('foo' => 'foo', 'bar' => 'bar'));
+ }
+
+ public function testRemoveAllSpecified() {
+ $listener = function () {
+ throw new EmittedException;
+ };
+ $this->emitter->listen('Test', 'test', $listener);
+ $this->emitter->remoteListener('Test', 'test', $listener);
+ $this->emitter->emitEvent('Test', 'test');
+ }
+
+ public function testRemoveWildcardListener() {
+ $listener1 = function () {
+ throw new EmittedException;
+ };
+ $listener2 = function () {
+ throw new EmittedException;
+ };
+ $this->emitter->listen('Test', 'test', $listener1);
+ $this->emitter->listen('Test', 'test', $listener2);
+ $this->emitter->remoteListener('Test', 'test');
+ $this->emitter->emitEvent('Test', 'test');
+ }
+
+ public function testRemoveWildcardMethod() {
+ $listener = function () {
+ throw new EmittedException;
+ };
+ $this->emitter->listen('Test', 'test', $listener);
+ $this->emitter->listen('Test', 'foo', $listener);
+ $this->emitter->remoteListener('Test', null, $listener);
+ $this->emitter->emitEvent('Test', 'test');
+ $this->emitter->emitEvent('Test', 'foo');
+ }
+
+ public function testRemoveWildcardScope() {
+ $listener = function () {
+ throw new EmittedException;
+ };
+ $this->emitter->listen('Test', 'test', $listener);
+ $this->emitter->listen('Bar', 'test', $listener);
+ $this->emitter->remoteListener(null, 'test', $listener);
+ $this->emitter->emitEvent('Test', 'test');
+ $this->emitter->emitEvent('Bar', 'test');
+ }
+
+ public function testRemoveWildcardScopeAndMethod() {
+ $listener = function () {
+ throw new EmittedException;
+ };
+ $this->emitter->listen('Test', 'test', $listener);
+ $this->emitter->listen('Test', 'foo', $listener);
+ $this->emitter->listen('Bar', 'foo', $listener);
+ $this->emitter->remoteListener(null, null, $listener);
+ $this->emitter->emitEvent('Test', 'test');
+ $this->emitter->emitEvent('Test', 'foo');
+ $this->emitter->emitEvent('Bar', 'foo');
+ }
+
+ /**
+ * @expectedException \Test\Hooks\EmittedException
+ */
+ public function testRemoveKeepOtherCallback() {
+ $listener1 = function () {
+ throw new EmittedException;
+ };
+ $listener2 = function () {
+ throw new EmittedException;
+ };
+ $this->emitter->listen('Test', 'test', $listener1);
+ $this->emitter->listen('Test', 'test', $listener2);
+ $this->emitter->remoteListener('Test', 'test', $listener1);
+ $this->emitter->emitEvent('Test', 'test');
+ }
+
+ /**
+ * @expectedException \Test\Hooks\EmittedException
+ */
+ public function testRemoveKeepOtherMethod() {
+ $listener = function () {
+ throw new EmittedException;
+ };
+ $this->emitter->listen('Test', 'test', $listener);
+ $this->emitter->listen('Test', 'foo', $listener);
+ $this->emitter->remoteListener('Test', 'foo', $listener);
+ $this->emitter->emitEvent('Test', 'test');
+ }
+
+ /**
+ * @expectedException \Test\Hooks\EmittedException
+ */
+ public function testRemoveKeepOtherScope() {
+ $listener = function () {
+ throw new EmittedException;
+ };
+ $this->emitter->listen('Test', 'test', $listener);
+ $this->emitter->listen('Bar', 'test', $listener);
+ $this->emitter->remoteListener('Bar', 'test', $listener);
+ $this->emitter->emitEvent('Test', 'test');
+ }
+
+ /**
+ * @expectedException \Test\Hooks\EmittedException
+ */
+ public function testRemoveNonExistingName() {
+ $listener = function () {
+ throw new EmittedException;
+ };
+ $this->emitter->listen('Test', 'test', $listener);
+ $this->emitter->remoteListener('Bar', 'test', $listener);
+ $this->emitter->emitEvent('Test', 'test');
+ }
+}
diff --git a/tests/lib/hooks/legacyemitter.php b/tests/lib/hooks/legacyemitter.php
new file mode 100644
index 00000000000..a7bed879a72
--- /dev/null
+++ b/tests/lib/hooks/legacyemitter.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace Test\Hooks;
+
+/**
+ * Class DummyLegacyEmitter
+ *
+ * class to make LegacyEmitter::emit publicly available
+ *
+ * @package Test\Hooks
+ */
+class DummyLegacyEmitter extends \OC\Hooks\LegacyEmitter {
+ public function emitEvent($scope, $method, $arguments = array()) {
+ $this->emit($scope, $method, $arguments);
+ }
+}
+
+class LegacyEmitter extends BasicEmitter {
+
+ //we can't use exceptions here since OC_Hooks catches all exceptions
+ private static $emitted = false;
+
+ public function setUp() {
+ $this->emitter = new DummyLegacyEmitter();
+ self::$emitted = false;
+ \OC_Hook::clear('Test','test');
+ }
+
+ public static function staticLegacyCallBack() {
+ self::$emitted = true;
+ }
+
+ public static function staticLegacyArgumentsCallBack($arguments) {
+ if ($arguments['foo'] == 'foo' and $arguments['bar'] == 'bar')
+ self::$emitted = true;
+ }
+
+ public function testLegacyHook() {
+ \OC_Hook::connect('Test', 'test', '\Test\Hooks\LegacyEmitter', 'staticLegacyCallBack');
+ $this->emitter->emitEvent('Test', 'test');
+ $this->assertEquals(true, self::$emitted);
+ }
+
+ public function testLegacyArguments() {
+ \OC_Hook::connect('Test', 'test', '\Test\Hooks\LegacyEmitter', 'staticLegacyArgumentsCallBack');
+ $this->emitter->emitEvent('Test', 'test', array('foo' => 'foo', 'bar' => 'bar'));
+ $this->assertEquals(true, self::$emitted);
+ }
+}