LICENSE 0000666 00000002040 13436755521 0005564 0 ustar 00 Copyright (c) 2011 Igor Wiedler Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 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. README.md 0000666 00000003114 13436755521 0006041 0 ustar 00 # Événement Événement is a very simple event dispatching library for PHP. It has the same design goals as [Silex](http://silex-project.org) and [Pimple](http://pimple-project.org), to empower the user while staying concise and simple. It is very strongly inspired by the EventEmitter API found in [node.js](http://nodejs.org). [![Build Status](https://secure.travis-ci.org/igorw/evenement.png?branch=master)](http://travis-ci.org/igorw/evenement) ## Fetch The recommended way to install Événement is [through composer](http://getcomposer.org). Just create a composer.json file for your project: ```JSON { "require": { "evenement/evenement": "^3.0 || ^2.0" } } ``` **Note:** The `3.x` version of Événement requires PHP 7 and the `2.x` version requires PHP 5.4. If you are using PHP 5.3, please use the `1.x` version: ```JSON { "require": { "evenement/evenement": "^1.0" } } ``` And run these two commands to install it: $ curl -s http://getcomposer.org/installer | php $ php composer.phar install Now you can add the autoloader, and you will have access to the library: ```php on('user.created', function (User $user) use ($logger) { $logger->log(sprintf("User '%s' was created.", $user->getLogin())); }); ``` ### Emitting Events ```php emit('user.created', [$user]); ``` Tests ----- $ ./vendor/bin/phpunit License ------- MIT, see LICENSE. examples/benchmark-emit-one-argument.php 0000666 00000001224 13436755521 0014376 0 ustar 00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ const ITERATIONS = 10000000; use Evenement\EventEmitter; require __DIR__.'/../vendor/autoload.php'; $emitter = new EventEmitter(); $emitter->on('event', function ($a) {}); $start = microtime(true); for ($i = 0; $i < ITERATIONS; $i++) { $emitter->emit('event', [1]); } $time = microtime(true) - $start; echo 'Emitting ', number_format(ITERATIONS), ' events took: ', number_format($time, 2), 's', PHP_EOL; examples/benchmark-emit.php 0000666 00000001242 13436755521 0011777 0 ustar 00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ const ITERATIONS = 10000000; use Evenement\EventEmitter; require __DIR__.'/../vendor/autoload.php'; $emitter = new EventEmitter(); $emitter->on('event', function ($a, $b, $c) {}); $start = microtime(true); for ($i = 0; $i < ITERATIONS; $i++) { $emitter->emit('event', [1, 2, 3]); } $time = microtime(true) - $start; echo 'Emitting ', number_format(ITERATIONS), ' events took: ', number_format($time, 2), 's', PHP_EOL; examples/benchmark-remove-listener-once.php 0000666 00000001775 13436755521 0015116 0 ustar 00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ ini_set('memory_limit', '512M'); const ITERATIONS = 100000; use Evenement\EventEmitter; require __DIR__.'/../vendor/autoload.php'; $emitter = new EventEmitter(); $listeners = []; for ($i = 0; $i < ITERATIONS; $i++) { $listeners[] = function ($a, $b, $c) {}; } $start = microtime(true); foreach ($listeners as $listener) { $emitter->once('event', $listener); } $time = microtime(true) - $start; echo 'Adding ', number_format(ITERATIONS), ' once listeners took: ', number_format($time, 2), 's', PHP_EOL; $start = microtime(true); foreach ($listeners as $listener) { $emitter->removeListener('event', $listener); } $time = microtime(true) - $start; echo 'Removing ', number_format(ITERATIONS), ' once listeners took: ', number_format($time, 2), 's', PHP_EOL; examples/benchmark-emit-once.php 0000666 00000001331 13436755521 0012720 0 ustar 00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ ini_set('memory_limit', '512M'); const ITERATIONS = 100000; use Evenement\EventEmitter; require __DIR__.'/../vendor/autoload.php'; $emitter = new EventEmitter(); for ($i = 0; $i < ITERATIONS; $i++) { $emitter->once('event', function ($a, $b, $c) {}); } $start = microtime(true); $emitter->emit('event', [1, 2, 3]); $time = microtime(true) - $start; echo 'Emitting one event to ', number_format(ITERATIONS), ' once listeners took: ', number_format($time, 2), 's', PHP_EOL; examples/benchmark-emit-no-arguments.php 0000666 00000001215 13436755521 0014414 0 ustar 00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ const ITERATIONS = 10000000; use Evenement\EventEmitter; require __DIR__.'/../vendor/autoload.php'; $emitter = new EventEmitter(); $emitter->on('event', function () {}); $start = microtime(true); for ($i = 0; $i < ITERATIONS; $i++) { $emitter->emit('event'); } $time = microtime(true) - $start; echo 'Emitting ', number_format(ITERATIONS), ' events took: ', number_format($time, 2), 's', PHP_EOL; src/Evenement/EventEmitter.php 0000666 00000000537 13436755521 0012431 0 ustar 00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Evenement; class EventEmitter implements EventEmitterInterface { use EventEmitterTrait; } src/Evenement/EventEmitterTrait.php 0000666 00000007521 13436755521 0013435 0 ustar 00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Evenement; use InvalidArgumentException; trait EventEmitterTrait { protected $listeners = []; protected $onceListeners = []; public function on($event, callable $listener) { if ($event === null) { throw new InvalidArgumentException('event name must not be null'); } if (!isset($this->listeners[$event])) { $this->listeners[$event] = []; } $this->listeners[$event][] = $listener; return $this; } public function once($event, callable $listener) { if ($event === null) { throw new InvalidArgumentException('event name must not be null'); } if (!isset($this->onceListeners[$event])) { $this->onceListeners[$event] = []; } $this->onceListeners[$event][] = $listener; return $this; } public function removeListener($event, callable $listener) { if ($event === null) { throw new InvalidArgumentException('event name must not be null'); } if (isset($this->listeners[$event])) { $index = \array_search($listener, $this->listeners[$event], true); if (false !== $index) { unset($this->listeners[$event][$index]); if (\count($this->listeners[$event]) === 0) { unset($this->listeners[$event]); } } } if (isset($this->onceListeners[$event])) { $index = \array_search($listener, $this->onceListeners[$event], true); if (false !== $index) { unset($this->onceListeners[$event][$index]); if (\count($this->onceListeners[$event]) === 0) { unset($this->onceListeners[$event]); } } } } public function removeAllListeners($event = null) { if ($event !== null) { unset($this->listeners[$event]); } else { $this->listeners = []; } if ($event !== null) { unset($this->onceListeners[$event]); } else { $this->onceListeners = []; } } public function listeners($event = null): array { if ($event === null) { $events = []; $eventNames = \array_unique( \array_merge(\array_keys($this->listeners), \array_keys($this->onceListeners)) ); foreach ($eventNames as $eventName) { $events[$eventName] = \array_merge( isset($this->listeners[$eventName]) ? $this->listeners[$eventName] : [], isset($this->onceListeners[$eventName]) ? $this->onceListeners[$eventName] : [] ); } return $events; } return \array_merge( isset($this->listeners[$event]) ? $this->listeners[$event] : [], isset($this->onceListeners[$event]) ? $this->onceListeners[$event] : [] ); } public function emit($event, array $arguments = []) { if ($event === null) { throw new InvalidArgumentException('event name must not be null'); } if (isset($this->listeners[$event])) { foreach ($this->listeners[$event] as $listener) { $listener(...$arguments); } } if (isset($this->onceListeners[$event])) { $listeners = $this->onceListeners[$event]; unset($this->onceListeners[$event]); foreach ($listeners as $listener) { $listener(...$arguments); } } } } src/Evenement/EventEmitterInterface.php 0000666 00000001170 13436755521 0014244 0 ustar 00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Evenement; interface EventEmitterInterface { public function on($event, callable $listener); public function once($event, callable $listener); public function removeListener($event, callable $listener); public function removeAllListeners($event = null); public function listeners($event = null); public function emit($event, array $arguments = []); } doc/00-intro.md 0000666 00000002412 13436755521 0007221 0 ustar 00 # Introduction Événement is is French and means "event". The événement library aims to provide a simple way of subscribing to events and notifying those subscribers whenever an event occurs. The API that it exposes is almost a direct port of the EventEmitter API found in node.js. It also includes an "EventEmitter". There are some minor differences however. The EventEmitter is an implementation of the publish-subscribe pattern, which is a generalized version of the observer pattern. The observer pattern specifies an observable subject, which observers can register themselves to. Once something interesting happens, the subject notifies its observers. Pub/sub takes the same idea but encapsulates the observation logic inside a separate object which manages all of its subscribers or listeners. Subscribers are bound to an event name, and will only receive notifications of the events they subscribed to. **TLDR: What does evenement do, in short? It provides a mapping from event names to a list of listener functions and triggers each listener for a given event when it is emitted.** Why do we do this, you ask? To achieve decoupling. It allows you to design a system where the core will emit events, and modules are able to subscribe to these events. And respond to them. doc/01-api.md 0000666 00000004273 13436755521 0006647 0 ustar 00 # API The API that événement exposes is defined by the `Evenement\EventEmitterInterface`. The interface is useful if you want to define an interface that extends the emitter and implicitly defines certain events to be emitted, or if you want to type hint an `EventEmitter` to be passed to a method without coupling to the specific implementation. ## on($event, callable $listener) Allows you to subscribe to an event. Example: ```php $emitter->on('user.created', function (User $user) use ($logger) { $logger->log(sprintf("User '%s' was created.", $user->getLogin())); }); ``` Since the listener can be any callable, you could also use an instance method instead of the anonymous function: ```php $loggerSubscriber = new LoggerSubscriber($logger); $emitter->on('user.created', array($loggerSubscriber, 'onUserCreated')); ``` This has the benefit that listener does not even need to know that the emitter exists. You can also accept more than one parameter for the listener: ```php $emitter->on('numbers_added', function ($result, $a, $b) {}); ``` ## once($event, callable $listener) Convenience method that adds a listener which is guaranteed to only be called once. Example: ```php $conn->once('connected', function () use ($conn, $data) { $conn->send($data); }); ``` ## emit($event, array $arguments = []) Emit an event, which will call all listeners. Example: ```php $conn->emit('data', [$data]); ``` The second argument to emit is an array of listener arguments. This is how you specify more args: ```php $result = $a + $b; $emitter->emit('numbers_added', [$result, $a, $b]); ``` ## listeners($event) Allows you to inspect the listeners attached to an event. Particularly useful to check if there are any listeners at all. Example: ```php $e = new \RuntimeException('Everything is broken!'); if (0 === count($emitter->listeners('error'))) { throw $e; } ``` ## removeListener($event, callable $listener) Remove a specific listener for a specific event. ## removeAllListeners($event = null) Remove all listeners for a specific event or all listeners all together. This is useful for long-running processes, where you want to remove listeners in order to allow them to get garbage collected. doc/02-plugin-system.md 0000666 00000010714 13436755521 0010714 0 ustar 00 # Example: Plugin system In this example I will show you how to create a generic plugin system with événement where plugins can alter the behaviour of the app. The app is a blog. Boring, I know. By using the EventEmitter it will be easy to extend this blog with additional functionality without modifying the core system. The blog is quite basic. Users are able to create blog posts when they log in. The users are stored in a static config file, so there is no sign up process. Once logged in they get a "new post" link which gives them a form where they can create a new blog post with plain HTML. That will store the post in a document database. The index lists all blog post titles by date descending. Clicking on the post title will take you to the full post. ## Plugin structure The goal of the plugin system is to allow features to be added to the blog without modifying any core files of the blog. The plugins are managed through a config file, `plugins.json`. This JSON file contains a JSON-encoded list of class-names for plugin classes. This allows you to enable and disable plugins in a central location. The initial `plugins.json` is just an empty array: ```json [] ``` A plugin class must implement the `PluginInterface`: ```php interface PluginInterface { function attachEvents(EventEmitterInterface $emitter); } ``` The `attachEvents` method allows the plugin to attach any events to the emitter. For example: ```php class FooPlugin implements PluginInterface { public function attachEvents(EventEmitterInterface $emitter) { $emitter->on('foo', function () { echo 'bar!'; }); } } ``` The blog system creates an emitter instance and loads the plugins: ```php $emitter = new EventEmitter(); $pluginClasses = json_decode(file_get_contents('plugins.json'), true); foreach ($pluginClasses as $pluginClass) { $plugin = new $pluginClass(); $pluginClass->attachEvents($emitter); } ``` This is the base system. There are no plugins yet, and there are no events yet either. That's because I don't know which extension points will be needed. I will add them on demand. ## Feature: Markdown Writing blog posts in HTML sucks! Wouldn't it be great if I could write them in a nice format such as markdown, and have that be converted to HTML for me? This feature will need two extension points. I need to be able to mark posts as markdown, and I need to be able to hook into the rendering of the post body and convert it from markdown to HTML. So the blog needs two new events: `post.create` and `post.render`. In the code that creates the post, I'll insert the `post.create` event: ```php class PostEvent { public $post; public function __construct(array $post) { $this->post = $post; } } $post = createPostFromRequest($_POST); $event = new PostEvent($post); $emitter->emit('post.create', [$event]); $post = $event->post; $db->save('post', $post); ``` This shows that you can wrap a value in an event object to make it mutable, allowing listeners to change it. The same thing for the `post.render` event: ```php public function renderPostBody(array $post) { $emitter = $this->emitter; $event = new PostEvent($post); $emitter->emit('post.render', [$event]); $post = $event->post; return $post['body']; }
= renderPostBody($post) %>
``` Ok, the events are in place. It's time to create the first plugin, woohoo! I will call this the `MarkdownPlugin`, so here's `plugins.json`: ```json [ "MarkdownPlugin" ] ``` The `MarkdownPlugin` class will be autoloaded, so I don't have to worry about including any files. I just have to worry about implementing the plugin class. The `markdown` function represents a markdown to HTML converter. ```php class MarkdownPlugin implements PluginInterface { public function attachEvents(EventEmitterInterface $emitter) { $emitter->on('post.create', function (PostEvent $event) { $event->post['format'] = 'markdown'; }); $emitter->on('post.render', function (PostEvent $event) { if (isset($event->post['format']) && 'markdown' === $event->post['format']) { $event->post['body'] = markdown($event->post['body']); } }); } } ``` There you go, the blog now renders posts as markdown. But all of the previous posts before the addition of the markdown plugin are still rendered correctly as raw HTML. ## Feature: Comments TODO ## Feature: Comment spam control TODO composer.json 0000666 00000001226 13436755521 0007306 0 ustar 00 { "name": "evenement/evenement", "description": "Événement is a very simple event dispatching library for PHP", "keywords": ["event-dispatcher", "event-emitter"], "license": "MIT", "authors": [ { "name": "Igor Wiedler", "email": "igor@wiedler.ch" } ], "require": { "php": ">=7.0" }, "require-dev": { "phpunit/phpunit": "^6.0" }, "autoload": { "psr-0": { "Evenement": "src" } }, "autoload-dev": { "psr-0": { "Evenement": "tests" }, "files": ["tests/Evenement/Tests/functions.php"] } } .travis.yml 0000666 00000001001 13436755521 0006664 0 ustar 00 language: php php: - 7.0 - 7.1 - hhvm - nightly matrix: allow_failures: - php: hhvm - php: nightly before_script: - wget http://getcomposer.org/composer.phar - php composer.phar install script: - ./vendor/bin/phpunit --coverage-text - php -n examples/benchmark-emit-no-arguments.php - php -n examples/benchmark-emit-one-argument.php - php -n examples/benchmark-emit.php - php -n examples/benchmark-emit-once.php - php -n examples/benchmark-remove-listener-once.php phpunit.xml.dist 0000666 00000001177 13436755521 0007744 0 ustar 00