diff --git a/.codeclimate.yml b/.codeclimate.yml
new file mode 100644
index 0000000..fdd1488
--- /dev/null
+++ b/.codeclimate.yml
@@ -0,0 +1,5 @@
+languages:
+ PHP: true
+
+exclude_paths:
+ - tests/*
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..fcadb2c
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+* text eol=lf
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..39f165b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+build
+vendor
+resources
+phpunit.xml
+phpunit-*.xml
+composer.lock
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..50d7801
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,17 @@
+language: php
+
+php:
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+
+matrix:
+ fast_finish: true
+
+script:
+ - composer update-all
+ - composer test
+
+after_script:
+ - composer coveralls
diff --git a/LICENSE b/LICENSE
index 24069e5..fcd68cb 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2016 JBZoo Content Construction Kit (CCK)
+Copyright (c) 2015 JBZoo Content Construction Kit (CCK)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -19,3 +19,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+
diff --git a/README.md b/README.md
index d571907..ddfeef8 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,101 @@
-# Event
-Library for lightweight event-based programming
+# JBZoo Event [![Build Status](https://travis-ci.org/JBZoo/Event.svg?branch=master)](https://travis-ci.org/JBZoo/Event) [![Coverage Status](https://coveralls.io/repos/JBZoo/Event/badge.svg?branch=master&service=github)](https://coveralls.io/github/JBZoo/Event?branch=master)
+
+#### PHP Library for event-based development (fork from [sabre/event](https://github.com/fruux/sabre-event))
+
+[![License](https://poser.pugx.org/JBZoo/Event/license)](https://packagist.org/packages/JBZoo/Event) [![Latest Stable Version](https://poser.pugx.org/JBZoo/Event/v/stable)](https://packagist.org/packages/JBZoo/Event) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/JBZoo/Event/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/JBZoo/Event/?branch=master)
+
+The EventEmitter is a simple pattern that allows you to create an object that emits events, and allow you to listen to those events.
+
+### Simple example
+
+```php
+require_once './vendor/autoload.php'; // composer autoload.php
+
+// Get needed classes
+use JBZoo\Event\EventManager;
+
+$eManager = new EventManager();
+
+// Simple
+$eManager->on('create', function () {
+ echo "Something action";
+});
+
+// JUST DO IT
+$eManager->trigger('create');
+```
+
+
+### Set priority
+By supplying a priority, you are ensured that subscribers are handled in a specific order. The default priority is EventManager::MID.
+Anything below that will be triggered earlier, anything higher later.
+If there's two subscribers with the same priority, they will execute in an undefined, but deterministic order.
+```php
+// Run it first
+$eManager->on('create', function () {
+ echo "Something high priority action";
+}, EventManager::HIGH);
+
+// Run it latest
+$eManager->on('create', function () {
+ echo "Something another action";
+}, EventManager::LOW);
+
+// Custom index
+$eManager->on('create', function () {
+ echo "Something action";
+}, 42);
+
+// don't care...
+$eManager->on('create', function () {
+ echo "Something action";
+});
+```
+
+### Types of Callback
+All default PHP callbacks are supported, so closures are not required.
+```php
+$eManager->on('create', function(){ /* ... */ }); // Custom function
+$eManager->on('create', 'myFunction'); // Custom function name
+$eManager->on('create', ['myClass', 'myMethod']); // Static function
+$eManager->on('create', [$object, 'Method']); // Method of instance
+```
+
+
+### Cancel queue of events
+```php
+use JBZoo\Event\Exception;
+
+$eManager->on('create', function () {
+ throw new Exception(); // Special exception for JBZoo/Event
+});
+```
+
+
+### Passing arguments
+Arguments can be passed as an array.
+```php
+$eManager->on('create', function ($entityId) {
+ echo "An entity with id ", $entityId, " just got created.\n";
+});
+$entityId = 5;
+$eManager->trigger('create', [$entityId]);
+```
+
+Because you cannot really do anything with the return value of a listener, you can pass arguments by reference to communicate between listeners and back to the emitter.
+```php
+$eManager->on('create', function ($entityId, &$warnings) {
+ echo "An entity with id ", $entityId, " just got created.\n";
+ $warnings[] = "Something bad may or may not have happened.\n";
+});
+$warnings = [];
+$eventEmitter->trigger('create', [$entityId, &$warnings]);
+```
+
+### Namespaces
+... coming soon :)
+
+
+### License
+
+MIT
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..ccb1b31
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,66 @@
+{
+ "name" : "jbzoo/event",
+ "type" : "library",
+ "description" : "Library for event-based development",
+ "license" : "MIT",
+ "keywords" : ["JBZoo", "Events", "EventManager", "Listener", "Hook", "Signal"],
+ "minimum-stability" : "stable",
+ "authors" : [
+ {
+ "name" : "SmetDenis",
+ "email" : "denis@jbzoo.com",
+ "role" : "lead"
+ },
+ {
+ "name" : "Evert Pot",
+ "email" : "me@evertpot.com",
+ "homepage" : "http://sabre.io/event/"
+ }
+ ],
+ "require" : {
+ "php" : ">=5.4"
+ },
+ "require-dev" : {
+ "jbzoo/phpunit" : "^1.0"
+ },
+ "autoload" : {
+ "psr-4" : {
+ "JBZoo\\Event\\" : "src"
+ }
+ },
+ "extra" : {
+ "branch-alias" : {
+ "dev-master" : "1.x-dev"
+ }
+ },
+ "config" : {
+ "optimize-autoloader" : true
+ },
+ "scripts" : {
+ "test" : [
+ "@manifest",
+ "@autoload",
+ "@phpunit",
+ "@phpmd",
+ "@phpcs",
+ "@phpcpd",
+ "@phploc"
+ ],
+ "update-all" : [
+ "composer self-update --no-interaction",
+ "composer update --no-interaction --optimize-autoloader"
+ ],
+ "git-reset" : "git reset --hard",
+ "manifest" : "composer validate --no-interaction",
+ "autoload" : "composer dump-autoload --optimize --no-interaction",
+ "phpunit" : "php ./vendor/phpunit/phpunit/phpunit --configuration ./phpunit.xml.dist",
+ "phpmd" : "php ./vendor/phpmd/phpmd/src/bin/phpmd ./src text ./vendor/jbzoo/misc/phpmd/jbzoo.xml --verbose",
+ "phpcs" : "php ./vendor/squizlabs/php_codesniffer/scripts/phpcs ./src --standard=./vendor/jbzoo/misc/phpcs/JBZoo/ruleset.xml --report=full",
+ "phpcpd" : "php ./vendor/sebastian/phpcpd/phpcpd ./src --verbose",
+ "phploc" : "php ./vendor/phploc/phploc/phploc ./src --verbose",
+ "coveralls" : "php ./vendor/satooshi/php-coveralls/bin/coveralls --verbose"
+ },
+ "support" : {
+ "issues" : "https://github.com/JBZoo/Event/issues"
+ }
+}
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 100644
index 0000000..a2bbc24
--- /dev/null
+++ b/phpunit.xml.dist
@@ -0,0 +1,34 @@
+
+
+
+
+
+ ./tests
+
+
+
+
+
+ src
+
+
+
+
+
+
+
+
+
diff --git a/src/EventManager.php b/src/EventManager.php
new file mode 100644
index 0000000..69707ec
--- /dev/null
+++ b/src/EventManager.php
@@ -0,0 +1,220 @@
+
+ */
+
+namespace JBZoo\Event;
+
+/**
+ * Class Event
+ * @package JBZoo\Event
+ */
+class EventManager
+{
+ const LOWEST = 0;
+ const LOW = 50;
+ const MID = 100;
+ const HIGH = 500;
+ const HIGHEST = 1000;
+
+ /**
+ * The list of listeners
+ * @var array
+ */
+ protected $_listeners = [];
+
+ /**
+ * Subscribe to an event.
+ *
+ * @param string $eventName
+ * @param callable $callBack
+ * @param int $priority
+ * @return void
+ *
+ * @SuppressWarnings(PHPMD.ShortMethodName)
+ */
+ public function on($eventName, callable $callBack, $priority = self::MID)
+ {
+ if (!isset($this->_listeners[$eventName])) {
+ $this->_listeners[$eventName] = [
+ true, // If there's only one item, it's sorted
+ [$priority],
+ [$callBack],
+ ];
+
+ } else {
+ $this->_listeners[$eventName][0] = false; // marked as unsorted
+ $this->_listeners[$eventName][1][] = $priority;
+ $this->_listeners[$eventName][2][] = $callBack;
+ }
+ }
+
+ /**
+ * Subscribe to an event exactly once.
+ *
+ * @param string $eventName
+ * @param callable $callBack
+ * @param int $priority
+ * @return void
+ */
+ public function once($eventName, callable $callBack, $priority = 100)
+ {
+ $wrapper = null;
+ $wrapper = function () use ($eventName, $callBack, &$wrapper) {
+
+ $this->removeListener($eventName, $wrapper);
+ return call_user_func_array($callBack, func_get_args());
+
+ };
+
+ $this->on($eventName, $wrapper, $priority);
+
+ }
+
+ /**
+ * Emits an event.
+ *
+ * This method will return true if 0 or more listeners were succesfully
+ * handled. false is returned if one of the events broke the event chain.
+ *
+ * If the continueCallBack is specified, this callback will be called every
+ * time before the next event handler is called.
+ *
+ * If the continueCallback returns false, event propagation stops. This
+ * allows you to use the eventEmitter as a means for listeners to implement
+ * functionality in your application, and break the event loop as soon as
+ * some condition is fulfilled.
+ *
+ * Note that returning false from an event subscriber breaks propagation
+ * and returns false, but if the continue-callback stops propagation, this
+ * is still considered a 'successful' operation and returns true.
+ *
+ * Lastly, if there are 5 event handlers for an event. The continueCallback
+ * will be called at most 4 times.
+ *
+ * @param string $eventName
+ * @param array $arguments
+ * @param callback $continueCallBack
+ * @return bool
+ */
+ public function trigger($eventName, array $arguments = [], callable $continueCallBack = null)
+ {
+ if (is_null($continueCallBack)) {
+ foreach ($this->listeners($eventName) as $listener) {
+ $result = call_user_func_array($listener, $arguments);
+
+ if ($result === false) {
+ return false;
+ }
+ }
+
+ } else {
+
+ $listeners = $this->listeners($eventName);
+ $counter = count($listeners);
+
+ foreach ($listeners as $listener) {
+ $counter--;
+
+ $result = call_user_func_array($listener, $arguments);
+ if ($result === false) {
+ return false;
+ }
+
+ if ($counter > 0) {
+ if (!$continueCallBack()) {
+ break;
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the list of listeners for an event.
+ *
+ * The list is returned as an array, and the list of events are sorted by
+ * their priority.
+ *
+ * @param string $eventName
+ * @return callable[]
+ */
+ public function listeners($eventName)
+ {
+ if (!isset($this->_listeners[$eventName])) {
+ return [];
+ }
+
+ // The list is not sorted
+ if (!$this->_listeners[$eventName][0]) {
+ // Sorting
+ array_multisort($this->_listeners[$eventName][1], SORT_NUMERIC, $this->_listeners[$eventName][2]);
+
+ // Marking the listeners as sorted
+ $this->_listeners[$eventName][0] = true;
+ }
+
+ return $this->_listeners[$eventName][2];
+
+ }
+
+ /**
+ * Removes a specific listener from an event.
+ *
+ * If the listener could not be found, this method will return false. If it
+ * was removed it will return true.
+ *
+ * @param string $eventName
+ * @param callable $listener
+ * @return bool
+ */
+ public function removeListener($eventName, callable $listener)
+ {
+ if (!isset($this->_listeners[$eventName])) {
+ return false;
+ }
+
+ foreach ($this->_listeners[$eventName][2] as $index => $check) {
+
+ if ($check === $listener) {
+ unset($this->_listeners[$eventName][1][$index]);
+ unset($this->_listeners[$eventName][2][$index]);
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Removes all listeners.
+ *
+ * If the eventName argument is specified, all listeners for that event are
+ * removed. If it is not specified, every listener for every event is
+ * removed.
+ *
+ * @param string $eventName
+ * @return void
+ */
+ public function removeAllListeners($eventName = null)
+ {
+ if (!is_null($eventName)) {
+ unset($this->_listeners[$eventName]);
+ } else {
+ $this->_listeners = [];
+ }
+ }
+}
diff --git a/src/Exception.php b/src/Exception.php
new file mode 100644
index 0000000..62d4c29
--- /dev/null
+++ b/src/Exception.php
@@ -0,0 +1,25 @@
+
+ */
+
+namespace JBZoo\Event;
+
+/**
+ * Class Exception
+ * @package JBZoo\Event
+ */
+class Exception extends \Exception
+{
+
+}
diff --git a/tests/CodestyleTest.php b/tests/CodestyleTest.php
new file mode 100644
index 0000000..ba63de5
--- /dev/null
+++ b/tests/CodestyleTest.php
@@ -0,0 +1,26 @@
+
+ */
+
+namespace JBZoo\PHPUnit;
+
+/**
+ * Class CodeStyleTest
+ * @package JBZoo\PHPUnit
+ */
+class CodestyleTest extends Codestyle
+{
+ protected $_packageName = "Event";
+ protected $_packageAuthor = "Denis Smetannikov ";
+}
diff --git a/tests/EventsTest.php b/tests/EventsTest.php
new file mode 100644
index 0000000..1084755
--- /dev/null
+++ b/tests/EventsTest.php
@@ -0,0 +1,275 @@
+
+ */
+
+namespace JBZoo\PHPUnit;
+
+use JBZoo\Event\EventManager;
+
+/**
+ * Class ManagerTest
+ * @package JBZoo\PHPUnit
+ */
+class EventsTest extends PHPUnit
+{
+ function testInit()
+ {
+ $events = new EventManager();
+ $this->assertInstanceOf('JBZoo\\Event\\EventManager', $events);
+ }
+
+ function testListeners()
+ {
+ $events = new EventManager();
+
+ $callback1 = function () {
+ };
+
+ $callback2 = function () {
+ };
+
+ $events->on('foo', $callback1, 200);
+ $events->on('foo', $callback2, 100);
+
+ is([$callback2, $callback1], $events->listeners('foo'));
+ }
+
+ /**
+ * @depends testInit
+ */
+ function testHandleEvent()
+ {
+ $argResult = null;
+
+ $events = new EventManager();
+ $events->on('foo', function ($arg) use (&$argResult) {
+ $argResult = $arg;
+ });
+
+ isTrue($events->trigger('foo', ['bar']));
+ is('bar', $argResult);
+ }
+
+ /**
+ * @depends testHandleEvent
+ */
+ function testCancelEvent()
+ {
+ $argResult = 0;
+
+ $events = new EventManager();
+ $events->on('foo', function () use (&$argResult) {
+ $argResult = 1;
+ return false;
+ });
+
+ $events->on('foo', function () use (&$argResult) {
+ $argResult = 2;
+ });
+
+ isFalse($events->trigger('foo', ['bar']));
+ is(1, $argResult);
+ }
+
+ /**
+ * @depends testCancelEvent
+ */
+ function testPriority()
+ {
+ $argResult = 0;
+
+ $events = new EventManager();
+ $events->on('foo', function () use (&$argResult) {
+ $argResult = 1;
+ return false;
+ });
+
+ $events->on('foo', function () use (&$argResult) {
+ $argResult = 2;
+ return false;
+ }, 1);
+
+ isFalse($events->trigger('foo', ['bar']));
+ is(2, $argResult);
+ }
+
+ /**
+ * @depends testPriority
+ */
+ function testPriority2()
+ {
+ $result = [];
+ $events = new EventManager();
+
+ $events->on('foo', function () use (&$result) {
+ $result[] = 'a';
+ }, 200);
+
+ $events->on('foo', function () use (&$result) {
+ $result[] = 'b';
+ }, 50);
+
+ $events->on('foo', function () use (&$result) {
+ $result[] = 'c';
+ }, 300);
+
+ $events->on('foo', function () use (&$result) {
+ $result[] = 'd';
+ });
+
+ $events->trigger('foo');
+
+ is(['b', 'd', 'a', 'c'], $result);
+ }
+
+ function testRemoveListener()
+ {
+ $result = false;
+ $events = new EventManager();
+
+ $callBack = function () use (&$result) {
+ $result = true;
+ };
+
+ $events->on('foo', $callBack);
+
+ $events->trigger('foo');
+ isTrue($result);
+
+ $result = false;
+ isTrue($events->removeListener('foo', $callBack));
+
+ $events->trigger('foo');
+ isFalse($result);
+ }
+
+ function testRemoveUnknownListener()
+ {
+ $result = false;
+
+ $callBack = function () use (&$result) {
+ $result = true;
+ };
+
+ $events = new EventManager();
+ $events->on('foo', $callBack);
+ $events->trigger('foo');
+ isTrue($result);
+
+ $result = false;
+ isFalse($events->removeListener('bar', $callBack));
+ $events->trigger('foo');
+ isTrue($result);
+ }
+
+ function testRemoveListenerTwice()
+ {
+ $result = false;
+
+ $callBack = function () use (&$result) {
+ $result = true;
+ };
+
+ $events = new EventManager();
+ $events->on('foo', $callBack);
+ $events->trigger('foo');
+ isTrue($result);
+
+ $result = false;
+ isTrue($events->removeListener('foo', $callBack));
+ isFalse($events->removeListener('foo', $callBack));
+
+ $events->trigger('foo');
+ isFalse($result);
+ }
+
+ function testRemoveAllListeners()
+ {
+ $result = false;
+ $callBack = function () use (&$result) {
+ $result = true;
+ };
+
+ $events = new EventManager();
+ $events->on('foo', $callBack);
+ $events->trigger('foo');
+ isTrue($result);
+ $result = false;
+
+ $events->removeAllListeners('foo');
+
+ $events->trigger('foo');
+ isFalse($result);
+ }
+
+ function testRemoveAllListenersNoArg()
+ {
+ $result = false;
+ $callBack = function () use (&$result) {
+ $result = true;
+ };
+
+ $events = new EventManager();
+ $events->on('foo', $callBack);
+
+ $events->trigger('foo');
+ isTrue($result);
+ $result = false;
+
+ $events->removeAllListeners();
+
+ $events->trigger('foo');
+ isFalse($result);
+ }
+
+ function testOnce()
+ {
+ $result = 0;
+
+ $callBack = function () use (&$result) {
+ $result++;
+ };
+
+ $events = new EventManager();
+ $events->once('foo', $callBack);
+
+ $events->trigger('foo');
+ $events->trigger('foo');
+
+ is(1, $result);
+ }
+
+ /**
+ * @depends testCancelEvent
+ */
+ function testPriorityOnce()
+ {
+ $argResult = 0;
+
+ $events = new EventManager();
+ $events->once('foo', function () use (&$argResult) {
+ $argResult = 1;
+ return false;
+ });
+
+ $events->once('foo', function () use (&$argResult) {
+ $argResult = 2;
+ return false;
+
+ }, 1);
+
+ isFalse($events->trigger('foo', ['bar']));
+ is(2, $argResult);
+ }
+}
\ No newline at end of file
diff --git a/tests/PerformanceTest.php b/tests/PerformanceTest.php
new file mode 100644
index 0000000..20d3492
--- /dev/null
+++ b/tests/PerformanceTest.php
@@ -0,0 +1,27 @@
+
+ */
+
+namespace JBZoo\PHPUnit;
+
+/**
+ * Class PerformanceTest
+ * @package JBZoo\Event
+ */
+class PerformanceTest extends PHPUnit
+{
+ public function testLeakMemoryCreate()
+ {
+ }
+}
diff --git a/tests/autoload.php b/tests/autoload.php
new file mode 100644
index 0000000..6b80c13
--- /dev/null
+++ b/tests/autoload.php
@@ -0,0 +1,27 @@
+
+ */
+
+
+if (!defined('ROOT_PATH')) { // for PHPUnit process isolation
+ define('ROOT_PATH', realpath('.'));
+}
+
+// main autoload
+if ($autoload = realpath('./vendor/autoload.php')) {
+ require_once $autoload;
+} else {
+ echo 'Please execute "composer update" !' . PHP_EOL;
+ exit(1);
+}