mirror of
https://github.com/stan220/WildcardEventDispatcher.git
synced 2026-09-08 19:28:45 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc3257181c | ||
|
|
544fca2b55 | ||
|
|
f32bd8592f | ||
|
|
392eff7dc6 | ||
|
|
bcb29ff342 | ||
|
|
8733d7382b | ||
|
|
fe0da3a28b | ||
|
|
5ed48b24cb | ||
|
|
65d13be38c | ||
|
|
19dc115e94 | ||
|
|
8d53f365b2 | ||
|
|
f8099a63f8 | ||
|
|
fd3a84d57d | ||
|
|
8fcbe9f726 | ||
|
|
5472039e48 | ||
|
|
2d68309b14 | ||
|
|
83a285207b | ||
|
|
0300cbb1a5 | ||
|
|
392eb3b559 | ||
|
|
a688303be2 | ||
|
|
6af1e73df4 | ||
|
|
62eb20340a | ||
|
|
e6b272951d |
+25
-2
@@ -1,7 +1,30 @@
|
||||
language: php
|
||||
dist: trusty
|
||||
sudo: false
|
||||
|
||||
php:
|
||||
- 5.3
|
||||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
- 7.0
|
||||
- 7.1
|
||||
- 7.2
|
||||
- 7.3
|
||||
|
||||
before_script: composer install --dev
|
||||
cache:
|
||||
directories:
|
||||
- ${HOME}/.composer/cache/files
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- php: 5.3
|
||||
dist: precise
|
||||
env: COMPOSER_FLAGS="--prefer-lowest"
|
||||
|
||||
before_script:
|
||||
- phpenv config-rm xdebug.ini
|
||||
- composer update --no-interaction --no-progress --no-suggest ${COMPOSER_FLAGS}
|
||||
|
||||
script:
|
||||
- vendor/bin/phpunit -v
|
||||
|
||||
@@ -2,21 +2,36 @@
|
||||
|
||||
[](https://travis-ci.org/jmikola/WildcardEventDispatcher)
|
||||
|
||||
This library implements an event dispatcher, based on [Symfony2's interface][],
|
||||
This library implements an event dispatcher, based on [Symfony's interface][],
|
||||
with wildcard syntax inspired by AMQP topic exchanges. Listeners may be bound to
|
||||
a wildcard pattern and be notified if a dispatched event's name matches that
|
||||
pattern. Literal event name matching is still supported.
|
||||
|
||||
If you are interested in using this library in a Symfony2 project, you may also
|
||||
If you are interested in using this library in a Symfony project, you may also
|
||||
want to take a look at the corresponding [bundle][].
|
||||
|
||||
[Symfony2's interface]: https://github.com/symfony/EventDispatcher
|
||||
[Symfony's interface]: https://github.com/symfony/EventDispatcher
|
||||
[bundle]: https://github.com/jmikola/JmikolaWildcardEventDispatcherBundle
|
||||
|
||||
## Usage ##
|
||||
## Installation
|
||||
|
||||
The library is published as a [package][] and is installable via [Composer][]:
|
||||
|
||||
```
|
||||
$ composer require jmikola/wildcard-event-dispatcher=~1.0
|
||||
```
|
||||
|
||||
[package]: https://packagist.org/packages/jmikola/wildcard-event-dispatcher
|
||||
[Composer]: http://getcomposer.org/
|
||||
|
||||
### Compatibility
|
||||
|
||||
This library requires Symfony 2.3 or above.
|
||||
|
||||
## Usage
|
||||
|
||||
WildcardEventDispatcher implements EventDispatcherInterface and may be used as
|
||||
you would Symfony2's standard EventDispatcher:
|
||||
you would Symfony's standard EventDispatcher:
|
||||
|
||||
```php
|
||||
<?php
|
||||
@@ -50,9 +65,9 @@ $dispatcher = new WildcardEventDispatcher(new EventDispatcher());
|
||||
|
||||
[composes]: http://en.wikipedia.org/wiki/Object_composition
|
||||
|
||||
## Wildcard Syntax ##
|
||||
## Wildcard Syntax
|
||||
|
||||
### Single-word Wildcard ###
|
||||
### Single-word Wildcard
|
||||
|
||||
Consider the scenario where the same listener is defined for multiple events,
|
||||
all of which share a common prefix:
|
||||
@@ -90,7 +105,7 @@ and followed by another word. The matching of `core` alone may not make sense,
|
||||
but this is implemented in order to be consistent with AMQP. A trailing `*`
|
||||
after a non-empty sequence may match the preceding sequence sans `.*`.
|
||||
|
||||
### Multi-word Wildcard ###
|
||||
### Multi-word Wildcard
|
||||
|
||||
Suppose there was a `core` event in your application named `core.foo.bar`. The
|
||||
aforementioned `core.*` pattern would not catch this event. You could use:
|
||||
@@ -130,7 +145,7 @@ $dispatcher = new WildcardEventDispatcher();
|
||||
$dispatcher->addListener('#', $allListener);
|
||||
```
|
||||
|
||||
### Additional Wildcard Documentation ###
|
||||
### Additional Wildcard Documentation
|
||||
|
||||
When in doubt, the unit tests for `ListenerPattern` are a good resource for
|
||||
inferring how wildcards will be interpreted. This library aims to mimic the
|
||||
|
||||
+9
-2
@@ -8,12 +8,19 @@
|
||||
"authors": [
|
||||
{ "name": "Jeremy Mikola", "email": "jmikola@gmail.com" }
|
||||
],
|
||||
"minimum-stability": "dev",
|
||||
"require": {
|
||||
"php": ">=5.3.2",
|
||||
"symfony/event-dispatcher": ">=2.0.0,<2.3-dev"
|
||||
"symfony/event-dispatcher": "^2.3 || ^3.0 || ^4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4.8.36 || ^6.4"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": { "Jmikola": "src/" }
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.1.x-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ class LazyListenerPattern extends ListenerPattern
|
||||
* @param string $eventPattern
|
||||
* @param callback $listenerProvider
|
||||
* @param integer $priority
|
||||
* @throws InvalidArgumentException if the listener provider is not a callback
|
||||
* @throws \InvalidArgumentException if the listener provider is not a callback
|
||||
*/
|
||||
public function __construct($eventPattern, $listenerProvider, $priority = 0)
|
||||
{
|
||||
|
||||
@@ -12,18 +12,7 @@ class ListenerPattern
|
||||
protected $priority;
|
||||
protected $regex;
|
||||
|
||||
private static $replacements = array(
|
||||
// Trailing single-wildcard with separator prefix
|
||||
'/\\\\\.\\\\\*$/' => '(?:\.\w+)?',
|
||||
// Single-wildcard with separator prefix
|
||||
'/\\\\\.\\\\\*/' => '(?:\.\w+)',
|
||||
// Single-wildcard without separator prefix
|
||||
'/(?<!\\\\\.)\\\\\*/' => '(?:\w+)',
|
||||
// Multi-wildcard with separator prefix
|
||||
'/\\\\\.#/' => '(?:\.\w+)*',
|
||||
// Multi-wildcard without separator prefix
|
||||
'/(?<!\\\\\.)#/' => '(?:|\w+(?:\.\w+)*)',
|
||||
);
|
||||
private static $replacements;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@@ -110,10 +99,52 @@ class ListenerPattern
|
||||
*/
|
||||
private function createRegex($eventPattern)
|
||||
{
|
||||
$replacements = self::getReplacements();
|
||||
|
||||
return sprintf('/^%s$/', preg_replace(
|
||||
array_keys(self::$replacements),
|
||||
array_values(self::$replacements),
|
||||
array_keys($replacements),
|
||||
array_values($replacements),
|
||||
preg_quote($eventPattern, '/')
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns preg_replace() replacements for preparing the event pattern.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function getReplacements()
|
||||
{
|
||||
if (null !== self::$replacements) {
|
||||
return self::$replacements;
|
||||
}
|
||||
|
||||
self::$replacements = array(
|
||||
// Trailing single-wildcard with separator prefix
|
||||
'/\\\\\.\\\\\*$/' => '(?:\.\w+)?',
|
||||
// Single-wildcard with separator prefix
|
||||
'/\\\\\.\\\\\*/' => '(?:\.\w+)',
|
||||
// Single-wildcard without separator prefix
|
||||
'/(?<!\\\\\.)\\\\\*/' => '(?:\w+)',
|
||||
);
|
||||
|
||||
// preg_quote() escapes `#` in PHP 7.3+
|
||||
if (PHP_VERSION_ID >= 70300) {
|
||||
self::$replacements += array(
|
||||
// Multi-wildcard with separator prefix
|
||||
'/\\\\\.\\\\\#/' => '(?:\.\w+)*',
|
||||
// Multi-wildcard without separator prefix
|
||||
'/(?<!\\\\\.)\\\\\#/' => '(?:|\w+(?:\.\w+)*)',
|
||||
);
|
||||
} else {
|
||||
self::$replacements += array(
|
||||
// Multi-wildcard with separator prefix
|
||||
'/\\\\\.#/' => '(?:\.\w+)*',
|
||||
// Multi-wildcard without separator prefix
|
||||
'/(?<!\\\\\.)#/' => '(?:|\w+(?:\.\w+)*)',
|
||||
);
|
||||
}
|
||||
|
||||
return self::$replacements;
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ class WildcardEventDispatcher implements EventDispatcherInterface
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* If an EventDispatcherInterface is not provided , a new EventDispatcher
|
||||
* If an EventDispatcherInterface is not provided, a new EventDispatcher
|
||||
* will be composed.
|
||||
*
|
||||
* @param EventDispatcherInterface $dispatcher
|
||||
@@ -92,10 +92,14 @@ class WildcardEventDispatcher implements EventDispatcherInterface
|
||||
public function addSubscriber(EventSubscriberInterface $subscriber)
|
||||
{
|
||||
foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
|
||||
if (is_array($params)) {
|
||||
$this->addListener($eventName, array($subscriber, $params[0]), $params[1]);
|
||||
} else {
|
||||
if (is_string($params)) {
|
||||
$this->addListener($eventName, array($subscriber, $params));
|
||||
} elseif (is_string($params[0])) {
|
||||
$this->addListener($eventName, array($subscriber, $params[0]), isset($params[1]) ? $params[1] : 0);
|
||||
} else {
|
||||
foreach ($params as $listener) {
|
||||
$this->addListener($eventName, array($subscriber, $listener[0]), isset($listener[1]) ? $listener[1] : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,7 +110,13 @@ class WildcardEventDispatcher implements EventDispatcherInterface
|
||||
public function removeSubscriber(EventSubscriberInterface $subscriber)
|
||||
{
|
||||
foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
|
||||
$this->removeListener($eventName, array($subscriber, is_array($params) ? $params[0] : $params));
|
||||
if (is_array($params) && is_array($params[0])) {
|
||||
foreach ($params as $listener) {
|
||||
$this->removeListener($eventName, array($subscriber, $listener[0]));
|
||||
}
|
||||
} else {
|
||||
$this->removeListener($eventName, array($subscriber, is_string($params) ? $params : $params[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,4 +195,22 @@ class WildcardEventDispatcher implements EventDispatcherInterface
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see EventDispatcherInterface::getListenerPriority()
|
||||
* @throws \InvalidArgumentException if $eventName contains a wildcard pattern
|
||||
* @throws \BadMethodCallException if this method is not implemented on the composed EventDispatcher
|
||||
*/
|
||||
public function getListenerPriority($eventName, $listener)
|
||||
{
|
||||
if ($this->hasWildcards($eventName)) {
|
||||
throw new \InvalidArgumentException('Wildcard patterns are not supported');
|
||||
}
|
||||
|
||||
if ( ! method_exists($this->dispatcher, 'getListenerPriority')) {
|
||||
throw new \BadMethodCallException('getListenerPriority() is not implemented');
|
||||
}
|
||||
|
||||
return $this->dispatcher->getListenerPriority($eventName, $listener);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
namespace Jmikola\Tests\WildcardEventDispatcher;
|
||||
|
||||
use Jmikola\WildcardEventDispatcher\LazyListenerPattern;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class LazyListenerPatternTest extends \PHPUnit_Framework_TestCase
|
||||
class LazyListenerPatternTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
namespace Jmikola\Tests\WildcardEventDispatcher;
|
||||
|
||||
use Jmikola\WildcardEventDispatcher\ListenerPattern;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ListenerPatternTest extends \PHPUnit_Framework_TestCase
|
||||
class ListenerPatternTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider providePatternsAndMatches
|
||||
@@ -86,6 +87,6 @@ class ListenerPatternTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
private function getMockEventDispatcher()
|
||||
{
|
||||
return $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
|
||||
return $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock();
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -4,8 +4,9 @@ namespace Jmikola\Tests\WildcardEventDispatcher;
|
||||
|
||||
use Jmikola\WildcardEventDispatcher\WildcardEventDispatcher;
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class WildcardEventDispatcherFunctionalTest extends \PHPUnit_Framework_TestCase
|
||||
class WildcardEventDispatcherFunctionalTest extends TestCase
|
||||
{
|
||||
const coreRequest = 'core.request';
|
||||
const coreException = 'core.exception';
|
||||
@@ -127,7 +128,7 @@ class WildcardEventDispatcherFunctionalTest extends \PHPUnit_Framework_TestCase
|
||||
private function assertNumberListenersAdded($expected, $eventName = null)
|
||||
{
|
||||
return isset($eventName)
|
||||
? $this->assertEquals($expected, count($this->dispatcher->getListeners($eventName)))
|
||||
? $this->assertCount($expected, $this->dispatcher->getListeners($eventName))
|
||||
: $this->assertEquals($expected, array_sum(array_map('count', $this->dispatcher->getListeners())));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,9 @@ namespace Jmikola\Tests\WildcardEventDispatcher;
|
||||
|
||||
use Jmikola\WildcardEventDispatcher\WildcardEventDispatcher;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class EventDispatcherTest extends \PHPUnit_Framework_TestCase
|
||||
class EventDispatcherTest extends TestCase
|
||||
{
|
||||
private $dispatcher;
|
||||
private $innerDispatcher;
|
||||
@@ -115,32 +116,74 @@ class EventDispatcherTest extends \PHPUnit_Framework_TestCase
|
||||
*/
|
||||
$subscriber = new TestEventSubscriber();
|
||||
|
||||
$i = 0;
|
||||
$defaultPriority = 0;
|
||||
$numSubscribedEvents = count($subscriber->getSubscribedEvents());
|
||||
$this->innerDispatcher->expects($this->at(0))
|
||||
->method('addListener')
|
||||
->with('core.request', array($subscriber, 'onRequest'), 0);
|
||||
$this->innerDispatcher->expects($this->at(1))
|
||||
->method('addListener')
|
||||
->with('core.exception', array($subscriber, 'onException'), 10);
|
||||
$this->innerDispatcher->expects($this->at(2))
|
||||
->method('addListener')
|
||||
->with('core.multi', array($subscriber, 'onMulti1'), 10);
|
||||
$this->innerDispatcher->expects($this->at(3))
|
||||
->method('addListener')
|
||||
->with('core.multi', array($subscriber, 'onMulti2'), 20);
|
||||
|
||||
foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
|
||||
$method = is_array($params) ? $params[0] : $params;
|
||||
$priority = is_array($params) ? $params[1] : $defaultPriority;
|
||||
$this->innerDispatcher->expects($this->at(4))
|
||||
->method('removeListener')
|
||||
->with('core.request', array($subscriber, 'onRequest'));
|
||||
$this->innerDispatcher->expects($this->at(5))
|
||||
->method('removeListener')
|
||||
->with('core.exception', array($subscriber, 'onException'));
|
||||
$this->innerDispatcher->expects($this->at(6))
|
||||
->method('removeListener')
|
||||
->with('core.multi', array($subscriber, 'onMulti1'));
|
||||
$this->innerDispatcher->expects($this->at(7))
|
||||
->method('removeListener')
|
||||
->with('core.multi', array($subscriber, 'onMulti2'));
|
||||
|
||||
$this->innerDispatcher->expects($this->at($i))
|
||||
->method('addListener')
|
||||
->with($eventName, array($subscriber, $method), $priority);
|
||||
|
||||
$this->innerDispatcher->expects($this->at($numSubscribedEvents + $i))
|
||||
->method('removeListener')
|
||||
->with($eventName, array($subscriber, $method));
|
||||
$this->dispatcher->addSubscriber($subscriber);
|
||||
$this->dispatcher->removeSubscriber($subscriber);
|
||||
}
|
||||
|
||||
++$i;
|
||||
public function testGetListenerPriorityInvokesMethodOnInnerDispather()
|
||||
{
|
||||
if ( ! method_exists('Symfony\Component\EventDispatcher\EventDispatcherInterface', 'getListenerPriority')) {
|
||||
$this->markTestSkipped('getListenerPriority() does not exist on EventDispatcherInterface');
|
||||
}
|
||||
|
||||
$this->dispatcher->addSubscriber($subscriber, $priority);
|
||||
$this->dispatcher->removeSubscriber($subscriber, $priority);
|
||||
$this->innerDispatcher->expects($this->once())
|
||||
->method('getListenerPriority')
|
||||
->with('core.request', 'callback')
|
||||
->will($this->returnValue(1));
|
||||
|
||||
$this->assertSame(1, $this->dispatcher->getListenerPriority('core.request', 'callback'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testGetListenerPriorityRequiresEventNameWithoutWildcards()
|
||||
{
|
||||
$this->dispatcher->getListenerPriority('core.*', 'callback');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException BadMethodCallException
|
||||
*/
|
||||
public function testGetListenerPriorityRequiresMethodOnInnerDispather()
|
||||
{
|
||||
if (method_exists('Symfony\Component\EventDispatcher\EventDispatcherInterface', 'getListenerPriority')) {
|
||||
$this->markTestSkipped('getListenerPriority() exists on EventDispatcherInterface');
|
||||
}
|
||||
|
||||
$this->dispatcher->getListenerPriority('core.request', 'callback');
|
||||
}
|
||||
|
||||
private function getMockEventDispatcher()
|
||||
{
|
||||
return $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
|
||||
return $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,6 +194,7 @@ class TestEventSubscriber implements EventSubscriberInterface
|
||||
return array(
|
||||
'core.request' => 'onRequest',
|
||||
'core.exception' => array('onException', 10),
|
||||
'core.multi' => array(array('onMulti1', 10), array('onMulti2', 20)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user