├── tests ├── _bootstrap.php ├── unit │ ├── _bootstrap.php │ ├── FacadeTest.php │ ├── QueueTest.php │ ├── EventTest.php │ └── UnitTester.php ├── unit.suite.yml ├── _support │ ├── UnitHelper.php │ ├── AcceptanceHelper.php │ └── FunctionalHelper.php └── resources │ └── EventableObject.php ├── .scrutinizer.yml ├── changelog.md ├── codeception.yml ├── .gitignore ├── composer.json ├── src ├── Facades │ ├── Event.php │ ├── Queue.php │ └── Base.php ├── Providers │ └── FuelServiceProvider.php ├── EventTrait.php ├── Queue.php ├── Container.php └── Listener.php ├── .travis.yml ├── facades.md ├── queue.md └── readme.md /tests/_bootstrap.php: -------------------------------------------------------------------------------- 1 | _eventPrependSelf = $prepend; 12 | $this->_eventBindSelf = $bind; 13 | } 14 | 15 | public function increment($event, $by = 1) 16 | { 17 | $this->num += $by; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fuelphp/event", 3 | "type": "library", 4 | "description": "Framework independent Event library.", 5 | "keywords": ["event"], 6 | "homepage": "https://github.com/fuelphp/event", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "FuelPHP Development Team", 11 | "email": "team@fuelphp.com" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=5.4" 16 | }, 17 | "require-dev": { 18 | "codeception/codeception" : "~2.0" 19 | }, 20 | "autoload": { 21 | "psr-4": { "Fuel\\Event\\": "src/" } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Facades/Event.php: -------------------------------------------------------------------------------- 1 | register('event', function ($dic) 36 | { 37 | return $dic->resolve('Fuel\Event\Container'); 38 | }); 39 | 40 | // \Fuel\Event\Queue 41 | $this->register('queue', function ($dic) 42 | { 43 | return $dic->resolve('Fuel\Event\Queue'); 44 | }); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/unit/FacadeTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf('Fuel\\Event\\Container', $container); 21 | } 22 | 23 | public function testForgeQueue() 24 | { 25 | $container = Queue::forge(); 26 | 27 | $this->assertInstanceOf('Fuel\\Event\\Queue', $container); 28 | } 29 | 30 | public function testEventCallForewarding() 31 | { 32 | $result = Event::on('event', function(){}); 33 | 34 | $this->assertInstanceOf('Fuel\\Event\\Container', $result); 35 | } 36 | 37 | public function testQueueCallForewarding() 38 | { 39 | $result = Queue::queue('event'); 40 | 41 | $this->assertInstanceOf('Fuel\\Event\\Queue', $result); 42 | } 43 | 44 | public function testEventInstance() 45 | { 46 | $result = Event::instance('my_instance'); 47 | 48 | $this->assertInstanceOf('Fuel\\Event\\Container', $result); 49 | } 50 | 51 | public function testDeleteQueueInstance() 52 | { 53 | $instance = Queue::instance('my_instance'); 54 | $instance->queue('event', array(1, 2, 3)); 55 | 56 | Queue::delete('my_instance'); 57 | 58 | $instance = Queue::instance('my_instance'); 59 | $payload = $instance->getPayload('event'); 60 | 61 | $this->assertEquals($payload, array()); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/Facades/Base.php: -------------------------------------------------------------------------------- 1 | on('other_event', function()); 37 | ``` 38 | 39 | ## Queue 40 | 41 | ```php 42 | on('my_event', $handler) 64 | ->flush('my_event'); 65 | ``` 66 | 67 | ## Named instances 68 | 69 | Both Queues and Containers have a multiton implementation. This allows you "namespace" queues and events. This is done by using the `::instance` method: 70 | 71 | ```php 72 | 73 | use Fuel\Event\Facades\Event; 74 | use Fuel\Event\Facades\Queue; 75 | 76 | // It works for queues 77 | Queue::instance('my_name')->on('event', $handler); 78 | 79 | // As well as for events 80 | Event::instance('named')->on('this', $doThat); 81 | ``` 82 | -------------------------------------------------------------------------------- /queue.md: -------------------------------------------------------------------------------- 1 | ## Event Queue 2 | 3 | An event queue lets you fire events with multiple payloads at a delayed time. Using queue's is a great way to organize events. Where events are only fired with one payload, queue's can hold multiple payloads and listeners, and run through them in one go. 4 | 5 | ### Setting up a Queue 6 | 7 | ```php 8 | $queue = new Fuel\Event\Queue(); 9 | ``` 10 | 11 | ### Adding payloads to the queue 12 | 13 | ```php 14 | $queue->queue('name', array('param 1 - a', 'param 2 - a')); 15 | 16 | // Add another one. 17 | $queue->queue('name', array('param 1 - b', 'param 2 - b')); 18 | ``` 19 | 20 | You've now got 2 payloads in the queue, ready to get cracking. 21 | 22 | ### Registering Flushers (Listeners, Workers, ...) 23 | 24 | Where in events callbacks are often referred to as Listeners, in Queue they're commonly known as Flushers or Workers. Registering a Flusher works just like adding a Listener to a Container: 25 | 26 | ```php 27 | $queue->on('name', $callable_flusher, $context, $priority); 28 | ``` 29 | 30 | As you can see in the code above, the queue `on` method can also take a `$priority` param. Like in events emitted from a Container, this will sort the Flushers descending. 31 | 32 | ```php 33 | $queue->queue('name', array('Hello', 'World')); 34 | 35 | $queue->on('name', function($event, $one, $two){ 36 | echo $two.'!'; 37 | }, 1); 38 | 39 | $queue->on('name', function($event, $one, $two){ 40 | echo $one.' '; 41 | }, 2); 42 | ``` 43 | 44 | When flushed this will echo out `Hello World!`. 45 | 46 | ## Flushing the queue 47 | 48 | In order to run the queued payloads by the Flushers you'll have to `flush()`. 49 | 50 | ```php 51 | $result = $queue->flush('name'); 52 | ``` 53 | 54 | When only using one Flusher, you can also define it in the `flush` call. 55 | 56 | ```php 57 | $result = $queue->flush('name', function(){ 58 | // Flusher logic. 59 | }, $optional_context); 60 | ``` 61 | 62 | ## Flush results. 63 | 64 | The result of the flushing is a multidimentional array: 65 | 66 | ```php 67 | result_array( 68 | payload_one_array( 69 | flusher_one_payload_one_result(), 70 | flusher_two_payload_one_result(), 71 | ), 72 | payload_two_array( 73 | flusher_one_payload_two_result(), 74 | flusher_two_payload_two_result(), 75 | ) 76 | ) 77 | ``` 78 | -------------------------------------------------------------------------------- /src/EventTrait.php: -------------------------------------------------------------------------------- 1 | _eventBindSelf) 51 | { 52 | // Set the context to $this 53 | $context = $this; 54 | } 55 | 56 | // Ensure there is a Container 57 | $this->_eventContainer or $this->_eventContainer = new Container(); 58 | 59 | // Add the event 60 | $this->_eventContainer->on($event, $handler, $context, $priority); 61 | 62 | // Remain chainable 63 | return $this; 64 | } 65 | 66 | /** 67 | * Removes one or more events. 68 | * 69 | * @param string $event event name 70 | * @param mixed $handler event handler 71 | * @param mixed $context closure context 72 | * @return object $this 73 | */ 74 | public function off($event = null, $handler = null, $context = null) 75 | { 76 | // When a container is set 77 | if ($this->_eventContainer) 78 | { 79 | // When the object is self binding 80 | if ($context === null and $this->_eventBindSelf) 81 | { 82 | // Set the context to $this 83 | $context = $this; 84 | } 85 | 86 | // Add the event to the container 87 | $this->_eventContainer->on($event, $handler, $context); 88 | } 89 | 90 | // Remain chainable 91 | return $this; 92 | } 93 | 94 | /** 95 | * Trigger an event. 96 | * 97 | * @param string $event event to trigger 98 | * @return object $this 99 | */ 100 | public function trigger($event, $arguments = array()) 101 | { 102 | // Check for self prepend 103 | $this->_eventPrependSelf and array_unshift($arguments, $this); 104 | 105 | // Add the event type 106 | array_unshift($arguments, $event); 107 | 108 | // When a container is set 109 | if ($this->_eventContainer) 110 | { 111 | // Add the event to the container 112 | call_user_func_array(array($this->_eventContainer, 'trigger'), $arguments); 113 | } 114 | 115 | // Remain chainable 116 | return $this; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /tests/unit/QueueTest.php: -------------------------------------------------------------------------------- 1 | queue = new Queue(); 18 | } 19 | 20 | public function testPayloads() 21 | { 22 | $expected = array( 23 | array(1), 24 | array(2), 25 | array(3), 26 | ); 27 | 28 | $this->queue->queue('event', array(1)); 29 | $this->queue->queue('event', array(2)); 30 | $this->queue->queue('event', array(3)); 31 | 32 | $result = $this->queue->getPayload('event'); 33 | 34 | $this->assertEquals($expected, $result); 35 | } 36 | 37 | public function testFlush() 38 | { 39 | $expected = array(); 40 | 41 | $this->queue->queue('event'); 42 | 43 | $result = $this->queue->flush('event'); 44 | 45 | $this->assertEquals($expected, $result); 46 | } 47 | 48 | public function testFlusher() 49 | { 50 | $expected = array(array(1)); 51 | 52 | $this->queue->queue('event'); 53 | 54 | $result = $this->queue->flush('event', function(){ 55 | return 1; 56 | }); 57 | 58 | $this->assertEquals($expected, $result); 59 | } 60 | 61 | public function testFlusherWithgetPayload() 62 | { 63 | $expected = array(array(1)); 64 | 65 | $this->queue->queue('event', array(1)); 66 | 67 | $result = $this->queue->flush('event', function($event, $number){ 68 | return $number; 69 | }); 70 | 71 | $this->assertEquals($expected, $result); 72 | } 73 | 74 | public function testFlusherWithMultiplePayloads() 75 | { 76 | $expected = array(array(1), array(2)); 77 | 78 | $this->queue->queue('event', array(1)); 79 | $this->queue->queue('event', array(2)); 80 | 81 | $result = $this->queue->flush('event', function($event, $number){ 82 | return $number; 83 | }); 84 | 85 | $this->assertEquals($expected, $result); 86 | } 87 | 88 | public function testMultipleFlushesWithGetPayload() 89 | { 90 | $expected = array( 91 | array(2), 92 | ); 93 | 94 | $this->queue->queue('event', array(1)); 95 | 96 | $this->queue->flush('event', function($event, $number){ 97 | return $number; 98 | }); 99 | 100 | $result = $this->queue->flush('event', function($event, $number){ 101 | return $number + 1; 102 | }); 103 | 104 | $this->assertEquals($expected, $result); 105 | } 106 | 107 | public function testMultipleFlushesWithMultiplePayloads() 108 | { 109 | $expected = array( 110 | array(1, 2), 111 | array(3, 4), 112 | ); 113 | 114 | $this->queue->queue('event', array(1)); 115 | $this->queue->queue('event', array(3)); 116 | 117 | $this->queue->on('event', function($event, $number){ 118 | return $number; 119 | }); 120 | 121 | $this->queue->on('event', function($event, $number){ 122 | return $number + 1; 123 | }); 124 | 125 | $result = $this->queue->flush('event'); 126 | 127 | $this->assertEquals($expected, $result); 128 | } 129 | 130 | public function textMultipleFlushesWithPropagationStop() 131 | { 132 | $expected = array( 133 | array(1), 134 | array(1), 135 | ); 136 | 137 | $this->queue->queue('event'); 138 | $this->queue->queue('event'); 139 | 140 | $this->queue->on('event', function($e){ 141 | $e->stopPropagation(); 142 | return 1; 143 | }); 144 | 145 | $this->queue->on('event', function($e){ 146 | return 2; 147 | }); 148 | 149 | $result = $this->queue->flush('event'); 150 | 151 | $this->assertEquals($expected, $result); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/Queue.php: -------------------------------------------------------------------------------- 1 | queue[$queue])) 32 | { 33 | // Create one if it doesn't exist 34 | $this->queue = array(); 35 | } 36 | 37 | if ( ! is_array($payload)) 38 | { 39 | throw new \InvalidArgumentException('Queue payload must an array.'); 40 | } 41 | 42 | // Append the payload to the queue 43 | $this->queue[$queue][] = $payload; 44 | 45 | return $this; 46 | } 47 | 48 | /** 49 | * Retrieve the queue payload 50 | * 51 | * @param string $queue queue name 52 | * @return array queue payloads 53 | */ 54 | public function getPayload($queue) 55 | { 56 | return isset($this->queue[$queue]) ? $this->queue[$queue] : array(); 57 | } 58 | 59 | /** 60 | * Register a flusher. 61 | * 62 | * @param string $queue queue name 63 | * @param mixed $flusher flusher 64 | * @param mixed $context flusher context 65 | * @param int $priority priority 66 | */ 67 | public function on($queue, $flusher, $context = null, $priority = 0) 68 | { 69 | // Ensure there is an event container. 70 | $this->container or $this->container = new Container(); 71 | 72 | // Register the flusher to the event container 73 | $this->container->on($queue, $flusher, $context, $priority); 74 | 75 | return $this; 76 | } 77 | 78 | /** 79 | * Remove a flusher. 80 | * 81 | * @param string $queue queue name 82 | * @param mixed $flusher flusher 83 | * @param mixed $context flusher context 84 | */ 85 | public function off($queue = null, $flusher = null, $context = null) 86 | { 87 | // When there is no event container 88 | if ( ! $this->container) 89 | { 90 | // Skip execution 91 | return $this; 92 | } 93 | 94 | // Remove the flusher from the event container. 95 | $this->container->off($queue, $flusher, $context); 96 | 97 | return $this; 98 | } 99 | 100 | /** 101 | * Flushes a queue. Adds a flusher when supplied. 102 | * 103 | * @param string $queue queue name 104 | * @param mixed $flusher flusher 105 | * @param mixed $context flusher context 106 | * @param int $priority priority 107 | */ 108 | public function flush($queue, $flusher = null, $context = null, $priority = 0) 109 | { 110 | // Set the return array 111 | $return = array(); 112 | 113 | if ($flusher) 114 | { 115 | $this->on($queue, $flusher, $context, $priority); 116 | } 117 | 118 | // When there is no event container 119 | if ( ! $this->container) 120 | { 121 | // Skip execution 122 | return $return; 123 | } 124 | 125 | // Get the queue payload 126 | $queuePayload = $this->getPayload($queue); 127 | 128 | foreach ($queuePayload as $payload) 129 | { 130 | // Prepend the event 131 | array_unshift($payload, $queue); 132 | 133 | $return[] = call_user_func_array(array($this->container, 'trigger'), $payload); 134 | } 135 | 136 | if ($flusher) 137 | { 138 | $this->off($queue, $flusher, $context, $priority); 139 | } 140 | 141 | return $return; 142 | } 143 | 144 | /** 145 | * Clear an event queue, removes all payloads and flushers. 146 | * 147 | * @param string $queue queue name 148 | * @return object $this 149 | */ 150 | public function clear($queue) 151 | { 152 | if (isset($this->queue[$queue])) 153 | { 154 | unset($this->queue[$queue]); 155 | } 156 | 157 | // When there is no event container 158 | if ( ! $this->container) 159 | { 160 | // Skip execution 161 | return $this; 162 | } 163 | 164 | // Remove all flushers 165 | $this->container->off($queue); 166 | 167 | return $this; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/Container.php: -------------------------------------------------------------------------------- 1 | listeners[$event])) 33 | { 34 | $this->listeners[$event] = array(); 35 | } 36 | 37 | $this->listeners[$event][] = new Listener($event, $handler, $context, $priority); 38 | 39 | return $this; 40 | } 41 | 42 | /** 43 | * Removes one or more events. 44 | * 45 | * @param string $event event name 46 | * @param mixed $handler event handler 47 | * @param mixed $context closure context 48 | * @return object $this 49 | */ 50 | public function off($event = null, $handler = null, $context = null) 51 | { 52 | // When there are no events to fire 53 | if (($event and ! isset($this->listeners[$event])) or empty($this->listeners[$event])) 54 | { 55 | // Skip execution 56 | return $this; 57 | } 58 | 59 | // When an event name is given, only fetch that stack. 60 | $events = $event ? $this->listeners[$event] : $this->listeners; 61 | 62 | foreach ($events as $k => $e) 63 | { 64 | // If the event matches, delete it 65 | if ($e->is($event, $handler, $context)) 66 | { 67 | // Use the event param. 68 | if ($event) 69 | { 70 | // Saves a function call ;-) 71 | unset($this->listeners[$event][$k]); 72 | } 73 | else 74 | { 75 | // Otherwise, retrieve the event name from the Event object. 76 | unset($this->listeners[$e->event()][$k]); 77 | } 78 | } 79 | } 80 | 81 | return $this; 82 | } 83 | 84 | /** 85 | * Trigger an event. 86 | * 87 | * @param string $event event to trigger 88 | * @return array return values 89 | */ 90 | public function trigger($event) 91 | { 92 | // Get the handlers 93 | $listeners = $this->getListeners($event); 94 | 95 | // Set return array 96 | $return = array(); 97 | 98 | // When there are no handlers 99 | if (empty($listeners)) 100 | { 101 | // Skip execution 102 | return $return; 103 | } 104 | 105 | // Get the event arguments. 106 | $args = func_get_args(); 107 | 108 | // Shift the event name off the arguments array 109 | array_shift($args); 110 | 111 | // Sort the events. 112 | usort($listeners, [$this, 'listenerSort']); 113 | 114 | foreach ($listeners as $listener) 115 | { 116 | // Fire the event and fetch the result 117 | $return[] = $listener($event, $args); 118 | 119 | // When the bubbling is prevented. 120 | if($listener->propagationStopped()) 121 | { 122 | return $return; 123 | } 124 | } 125 | 126 | return $return; 127 | } 128 | 129 | /** 130 | * Listener sort function 131 | * 132 | * @param Listener $a 133 | * @param Listener $b 134 | * @return int sort result 135 | */ 136 | protected function listenerSort(Listener $a, Listener $b) 137 | { 138 | if ($a->priority >= $b->priority) 139 | { 140 | return 1; 141 | } 142 | 143 | return -1; 144 | } 145 | 146 | /** 147 | * Retrieve the handlers for a given type, including the all events. 148 | * 149 | * @param string $event event name 150 | * @return array array of event objects for a given type 151 | */ 152 | public function getListeners($event) 153 | { 154 | // Get the special all events 155 | $all_listeners = isset($this->listeners['all']) ? $this->listeners['all'] : array(); 156 | 157 | // Get the handlers 158 | $event_listeners = isset($this->listeners[$event]) ? $this->listeners[$event] : array(); 159 | 160 | // Return the merged handlers array 161 | return array_merge(array(), $all_listeners, $event_listeners); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/Listener.php: -------------------------------------------------------------------------------- 1 | event = $event; 67 | $this->handler = $handler; 68 | $this->context = $context; 69 | $this->priority = $priority; 70 | } 71 | 72 | /** 73 | * Prevents further events from being fired. 74 | * 75 | * @return object $this 76 | */ 77 | public function stopPropagation() 78 | { 79 | $this->propagate = false; 80 | 81 | return $this; 82 | } 83 | 84 | /** 85 | * Returns wether event propagation should continue. 86 | * 87 | * @return bool wether event propagation should continue. 88 | */ 89 | public function propagationStopped() 90 | { 91 | return ! $this->propagate; 92 | } 93 | 94 | /** 95 | * Retrieve the Event name 96 | * 97 | * @return string event name 98 | */ 99 | public function name() 100 | { 101 | return $this->event; 102 | } 103 | 104 | /** 105 | * Retrieve the Event handler 106 | * 107 | * @return mixed event handler 108 | */ 109 | public function handler() 110 | { 111 | return $this->handler; 112 | } 113 | 114 | /** 115 | * Retrieve the Event context 116 | * 117 | * @return mixed event context 118 | */ 119 | public function context() 120 | { 121 | return $this->context; 122 | } 123 | 124 | /** 125 | * Retrieve wether the event object matches a set of event params. 126 | * 127 | * @return bool wether the event object matches the params 128 | */ 129 | public function is($event, $handler, $context) 130 | { 131 | if (($event === null or $this->event === $event) and 132 | ($handler === null or $this->handler === $handler) and 133 | ($context === null or $this->context === $context)) 134 | { 135 | return true; 136 | } 137 | 138 | return false; 139 | } 140 | 141 | /** 142 | * Invoke handler forewarder. 143 | * 144 | * @param string $event triggered event 145 | * @param array $args handler arguments 146 | * @return mixed handler return value 147 | */ 148 | public function __invoke($event, $args) 149 | { 150 | $handler = $this->handler; 151 | 152 | // Store the original event 153 | $original_event = $this->event; 154 | 155 | // Set the triggered event 156 | // This is needed when 'all' events are fired 157 | $this->event = $event; 158 | 159 | if ($this->context) 160 | { 161 | if ( ! ($handler instanceof \Closure)) 162 | { 163 | throw new Exception('Handler must be a Closure in order to bind a contaxt to.'); 164 | } 165 | 166 | if ( ! ($handler = $handler->bindTo($this->context, $this->context))) 167 | { 168 | throw new Exception('Context could not be bound to handler.'); 169 | } 170 | } 171 | 172 | // Prepend self to the arguments array 173 | array_unshift($args, $this); 174 | 175 | // Fetch the handler result 176 | $result = call_user_func_array($handler, $args); 177 | 178 | // Restore the old event name 179 | $this->event = $original_event; 180 | 181 | return $result; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /tests/unit/EventTest.php: -------------------------------------------------------------------------------- 1 | container = new Container(); 14 | } 15 | 16 | public function testEventClosure() 17 | { 18 | $something = 1; 19 | 20 | $this->container->on('my_event', function($e) use(&$something) 21 | { 22 | $something++; 23 | }); 24 | 25 | $this->container->trigger('my_event'); 26 | 27 | $this->assertEquals($something, 2); 28 | } 29 | 30 | public function testPreventClosure() 31 | { 32 | $something = 1; 33 | 34 | $this->container->on('my_event', function($e){ 35 | $e->stopPropagation(); 36 | }); 37 | 38 | $this->container->on('my_event', function($e) use(&$something) 39 | { 40 | $something++; 41 | }); 42 | 43 | $this->container->trigger('my_event'); 44 | 45 | $this->assertEquals($something, 1); 46 | } 47 | 48 | /** 49 | * @expectedException \InvalidArgumentException 50 | */ 51 | public function testInvalidHandler() 52 | { 53 | $this->container->on('my_event', 'not callable'); 54 | } 55 | 56 | /** 57 | * @expectedException \InvalidArgumentException 58 | */ 59 | public function testInvalidContext() 60 | { 61 | $this->container->on('my_event', function(){}, 'not a context'); 62 | } 63 | 64 | public function testEventArguments() 65 | { 66 | $something = 1; 67 | 68 | $this->container->on('my_event', function($e, $amount) use(&$something) 69 | { 70 | $something += $amount; 71 | }); 72 | 73 | $this->container->trigger('my_event', 1); 74 | 75 | $this->assertEquals($something, 2); 76 | } 77 | 78 | public function testReturnValues() 79 | { 80 | $expected = array(1, 2, 3); 81 | 82 | $this->container->on('my_event', function(){ 83 | return 1; 84 | }); 85 | 86 | $this->container->on('my_event', function(){ 87 | return 2; 88 | }); 89 | 90 | $this->container->on('my_event', function(){ 91 | return 3; 92 | }); 93 | 94 | $result = $this->container->trigger('my_event'); 95 | 96 | $this->assertEquals($expected, $result); 97 | } 98 | 99 | public function testAllEvent() 100 | { 101 | $expected = array(1, 2, 3); 102 | 103 | $this->container->on('all', function(){ 104 | return 1; 105 | }); 106 | 107 | $this->container->on('my_event', function(){ 108 | return 3; 109 | }); 110 | 111 | $this->container->on('all', function(){ 112 | return 2; 113 | }); 114 | 115 | $result = $this->container->trigger('my_event'); 116 | 117 | $this->assertEquals($expected, $result); 118 | } 119 | 120 | public function testUnregisterEventByName() 121 | { 122 | $expected = array(2, 3); 123 | 124 | $this->container->on('event', function(){ 125 | return 1; 126 | }); 127 | 128 | $this->container->on('all', function(){ 129 | return 2; 130 | }); 131 | 132 | $this->container->off('event'); 133 | 134 | $this->container->on('event', function(){ 135 | return 3; 136 | }); 137 | 138 | $result = $this->container->trigger('event'); 139 | 140 | $this->assertEquals($expected, $result); 141 | } 142 | 143 | public function testArrayCallback() 144 | { 145 | $obj = new \EventableObject(); 146 | 147 | $this->container->on('my_event', array($obj, 'increment')); 148 | $this->container->trigger('my_event', 2); 149 | 150 | $this->assertEquals($obj->num, 3); 151 | } 152 | 153 | public function testEventContext() 154 | { 155 | $something = 1; 156 | $context = new \stdClass(); 157 | $context->val = 1; 158 | 159 | $this->container->on('my_event', function() use(&$something) 160 | { 161 | $something += $this->val; 162 | }, $context); 163 | 164 | $this->container->trigger('my_event'); 165 | 166 | $this->assertEquals($something, 2); 167 | } 168 | 169 | public function testTrait() 170 | { 171 | $obj = new \EventableObject(); 172 | $something = 1; 173 | 174 | $obj->on('my_event', function() use(&$something){ 175 | $something++; 176 | }); 177 | 178 | $obj->trigger('my_event'); 179 | 180 | $this->assertEquals($something, 2); 181 | } 182 | 183 | public function testTraitBinding() 184 | { 185 | $obj = new \EventableObject(true); 186 | 187 | $obj->on('my_event', function() use(&$something){ 188 | $this->num++; 189 | }); 190 | 191 | $obj->trigger('my_event'); 192 | 193 | $this->assertEquals($obj->num, 2); 194 | } 195 | 196 | public function testTraitBindingOverwrite() 197 | { 198 | $obj = new \EventableObject(true); 199 | $obj2 = new \EventableObject(); 200 | 201 | $obj->on('my_event', function() use(&$something){ 202 | $this->num++; 203 | }, $obj2); 204 | 205 | $obj->trigger('my_event'); 206 | 207 | $this->assertEquals($obj2->num, 2); 208 | $this->assertEquals($obj->num, 1); 209 | } 210 | 211 | public function testTraitPrepend() 212 | { 213 | $obj = new \EventableObject(true, true); 214 | 215 | $obj->on('my_event', function($e, $o) use($obj){ 216 | $o->increment($e); 217 | }); 218 | 219 | $obj->trigger('my_event'); 220 | 221 | $this->assertEquals($obj->num, 2); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # FuelPHP Event Package 2 | 3 | [![Build Status](https://secure.travis-ci.org/fuelphp/event.png)](http://travis-ci.org/fuelphp/event) 4 | [![Code Coverage](https://scrutinizer-ci.com/g/fuelphp/event/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/fuelphp/event/?branch=master) 5 | [![Code Quality](https://scrutinizer-ci.com/g/fuelphp/event/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/fuelphp/event/?branch=master) 6 | [![HHVM Status](http://hhvm.h4cc.de/badge/fuelphp/event.svg)](http://hhvm.h4cc.de/package/fuelphp/event) 7 | 8 | Swift and elegant event management in PHP. A simple interface with a lot of power. 9 | 10 | ## What's included 11 | 12 | * Creating event containers for easy management. 13 | * Registering and unregistering of events. 14 | * Event prioritizing. 15 | * Propagation of events can be prevented. 16 | * A trait for eventable objects. 17 | * Context binding to event handlers. 18 | * Queues ([with docs](https://github.com/fuelphp/event/blob/master/queue.md)). 19 | * Easy access through handy Facades ([docs](https://github.com/fuelphp/event/blob/master/facades.md)) 20 | 21 | ## A simple example 22 | 23 | ```php 24 | on('my_event', function($event){ 29 | // Act on the event 30 | }); 31 | 32 | $container->trigger('my_event'); 33 | ``` 34 | 35 | ### The special 'all' event 36 | 37 | The only reserved event for `Container`s and `Queue`s is the `'all'` event. Listeners for this event 38 | will be triggered on every event fired: 39 | 40 | ```php 41 | $container->on('all', function(){ 42 | echo 'This will also be fired!'; 43 | }); 44 | 45 | $container->trigger('event'); 46 | ``` 47 | 48 | ## Unregistering of event 49 | 50 | ```php 51 | // Unregister all handlers for a specific event 52 | $container->off('my_event'); 53 | 54 | // Unregister all handlers with a specific handler 55 | $container->off(null, $my_handler); 56 | 57 | // Unregister all handlers with a specific context 58 | $conainer->off(null, null, $context); 59 | ``` 60 | 61 | ## Adding context 62 | 63 | The value of `$this` inside the handler (Closure) can be set by providing an object as the callback context. 64 | 65 | ```php 66 | $container->on('my_event', function($event){ 67 | // $this is now $myObject 68 | }, $myObject); 69 | ``` 70 | 71 | ## Prioritizing events 72 | 73 | Events can be prioritized with the addition of a priority number in the `->on` method. 74 | 75 | ```php 76 | $container->on('my_event', function(){ 77 | // This will be run last 78 | }, 1); 79 | 80 | $container->on('my_event', function(){ 81 | // This will be run first 82 | }, 2); 83 | ``` 84 | 85 | ## Using contexts and prioritizing together 86 | 87 | You can also combine contexts and prioritizing. In this scenario, first define the context and then supply the priority like so: 88 | 89 | ```php 90 | $container->on('my_event', function(){ 91 | // Do something 92 | }, $context, 3); 93 | ``` 94 | 95 | ## Triggering events 96 | 97 | You can trigger an event like so: 98 | 99 | ```php 100 | $container->trigger('my_event'); 101 | ``` 102 | 103 | In some cases you'll want to pass arguments to the callback, every argument after the event name will be passed to the handler. Those arguments will be appended to the arguments array used to fire the handler. The first argument is always the event object. Following are the params you've profided in `->trigger()`. 104 | 105 | ```php 106 | $container->on('my_event', function($event, $param1, $param2){ 107 | // do stuff with $param1 and $param2 108 | }); 109 | 110 | // Trigger the event with params. 111 | $container->trigger('my_event', 'param 1', 'param 2'); 112 | ``` 113 | 114 | ## Prevent event propagation 115 | 116 | You can break the chain of event listeners by calling `stopPropagation` on the event object. 117 | 118 | ```php 119 | $container->on('my_event', function($e){ 120 | $event->stopPropagation(); 121 | }); 122 | 123 | $container->on('my_event', function($e){ 124 | // This will not get executed. 125 | }); 126 | 127 | $container->trigger('my_event'); 128 | ``` 129 | 130 | ## Getting results 131 | 132 | When an event is triggered, all the return values will be collected and returned. 133 | 134 | ```php 135 | $container->on('my_event', function(){ 136 | return 1; 137 | }); 138 | 139 | $container->on('my_event', function(){ 140 | return 2; 141 | }); 142 | 143 | $container->on('my_event', function(){ 144 | return 3; 145 | }); 146 | 147 | $result = $container->trigger('my_event'); 148 | // [1, 2, 3] 149 | ``` 150 | 151 | ## Eventable objects 152 | 153 | PHP 5.4 gives us `traits`, an awesome way to share functionalities and allow for multiple inheritance. Models can become eventable when they use the `Fuel\Event\Eventable` trait. Using it is pretty straight forward. 154 | 155 | ### Implementing the trait 156 | 157 | ```php 158 | class EventableObject 159 | { 160 | // Incluse/use the trait 161 | use \Fuel\Event\EventTrait; 162 | } 163 | 164 | // Get a new instance. 165 | $myObject = new EventableObject(); 166 | ``` 167 | 168 | Now your models/object instances have the power of events under their hood. So the following becomes possible: 169 | 170 | ```php 171 | $myObject = new EventableObject(); 172 | 173 | $myObject->on('event', function($event){ 174 | // act on the event 175 | }); 176 | ``` 177 | 178 | ### Configuration options 179 | 180 | There are 2 configuration options to make it even easier to work with eventable objects, which can: 181 | 182 | * make objects self binding, 183 | * auto prepend itself to the arguments array. 184 | 185 | ### Self binding objects 186 | 187 | ```php 188 | 189 | class EventableObject 190 | { 191 | use Fuel\Event\EventTrait; 192 | 193 | // Set to true to bind itself as the callback context. 194 | protected $_eventBindSelf = true; 195 | } 196 | 197 | $myObject = new EventableObject(); 198 | 199 | $myObject->on('event', function(){ 200 | // $this is now $myObject 201 | }); 202 | ``` 203 | 204 | You are still able to overwrite the context by supplying it. 205 | 206 | ```php 207 | $myObject->on('event', function(){ 208 | // $this is now $otherObject 209 | }, $otherObject); 210 | ``` 211 | 212 | ### Self prepending object 213 | 214 | Use this when you want to prepend the model to the arguments array. 215 | 216 | ```php 217 | on('event', function($event, $self){ 230 | // $self now is $object 231 | }); 232 | ``` 233 | 234 | When supplying params to the `->trigger` method they will be appended after the event and model: 235 | 236 | ```php 237 | $object->on('event', function($event, $self, $param1, $param2){ 238 | // Act on the event. 239 | }); 240 | 241 | $object->trigger('event', 'param 1', 'param 2'); 242 | ``` 243 | 244 | ## Enjoy! 245 | 246 | ### For any questions about this package, hop into the #fuelphp IRC channel on freenode.net and look for FrenkyNet. 247 | -------------------------------------------------------------------------------- /tests/unit/UnitTester.php: -------------------------------------------------------------------------------- 1 | scenario->runStep(new \Codeception\Step\Action('assertEquals', func_get_args())); 41 | } 42 | 43 | 44 | /** 45 | * [!] Method is generated. Documentation taken from corresponding module. 46 | * 47 | * Checks that two variables are not equal 48 | * 49 | * @param $expected 50 | * @param $actual 51 | * @param string $message 52 | * @see \Codeception\Module\Asserts::assertNotEquals() 53 | */ 54 | public function assertNotEquals($expected, $actual, $message = null) { 55 | return $this->scenario->runStep(new \Codeception\Step\Action('assertNotEquals', func_get_args())); 56 | } 57 | 58 | 59 | /** 60 | * [!] Method is generated. Documentation taken from corresponding module. 61 | * 62 | * Checks that expected is greater than actual 63 | * 64 | * @param $expected 65 | * @param $actual 66 | * @param string $message 67 | * @see \Codeception\Module\Asserts::assertGreaterThan() 68 | */ 69 | public function assertGreaterThan($expected, $actual, $message = null) { 70 | return $this->scenario->runStep(new \Codeception\Step\Action('assertGreaterThan', func_get_args())); 71 | } 72 | 73 | 74 | /** 75 | * [!] Method is generated. Documentation taken from corresponding module. 76 | * 77 | * @deprecated 78 | * @see \Codeception\Module\Asserts::assertGreaterThen() 79 | */ 80 | public function assertGreaterThen($expected, $actual, $message = null) { 81 | return $this->scenario->runStep(new \Codeception\Step\Action('assertGreaterThen', func_get_args())); 82 | } 83 | 84 | 85 | /** 86 | * [!] Method is generated. Documentation taken from corresponding module. 87 | * 88 | * Checks that expected is greater or equal than actual 89 | * 90 | * @param $expected 91 | * @param $actual 92 | * @param string $message 93 | * @see \Codeception\Module\Asserts::assertGreaterThanOrEqual() 94 | */ 95 | public function assertGreaterThanOrEqual($expected, $actual, $message = null) { 96 | return $this->scenario->runStep(new \Codeception\Step\Action('assertGreaterThanOrEqual', func_get_args())); 97 | } 98 | 99 | 100 | /** 101 | * [!] Method is generated. Documentation taken from corresponding module. 102 | * 103 | * @deprecated 104 | * @see \Codeception\Module\Asserts::assertGreaterThenOrEqual() 105 | */ 106 | public function assertGreaterThenOrEqual($expected, $actual, $message = null) { 107 | return $this->scenario->runStep(new \Codeception\Step\Action('assertGreaterThenOrEqual', func_get_args())); 108 | } 109 | 110 | 111 | /** 112 | * [!] Method is generated. Documentation taken from corresponding module. 113 | * 114 | * Checks that expected is less than actual 115 | * 116 | * @param $expected 117 | * @param $actual 118 | * @param string $message 119 | * @see \Codeception\Module\Asserts::assertLessThan() 120 | */ 121 | public function assertLessThan($expected, $actual, $message = null) { 122 | return $this->scenario->runStep(new \Codeception\Step\Action('assertLessThan', func_get_args())); 123 | } 124 | 125 | 126 | /** 127 | * [!] Method is generated. Documentation taken from corresponding module. 128 | * 129 | * Checks that expected is less or equal than actual 130 | * 131 | * @param $expected 132 | * @param $actual 133 | * @param string $message 134 | * @see \Codeception\Module\Asserts::assertLessThanOrEqual() 135 | */ 136 | public function assertLessThanOrEqual($expected, $actual, $message = null) { 137 | return $this->scenario->runStep(new \Codeception\Step\Action('assertLessThanOrEqual', func_get_args())); 138 | } 139 | 140 | 141 | /** 142 | * [!] Method is generated. Documentation taken from corresponding module. 143 | * 144 | * Checks that haystack contains needle 145 | * 146 | * @param $needle 147 | * @param $haystack 148 | * @param string $message 149 | * @see \Codeception\Module\Asserts::assertContains() 150 | */ 151 | public function assertContains($needle, $haystack, $message = null) { 152 | return $this->scenario->runStep(new \Codeception\Step\Action('assertContains', func_get_args())); 153 | } 154 | 155 | 156 | /** 157 | * [!] Method is generated. Documentation taken from corresponding module. 158 | * 159 | * Checks that haystack doesn't contain needle. 160 | * 161 | * @param $needle 162 | * @param $haystack 163 | * @param string $message 164 | * @see \Codeception\Module\Asserts::assertNotContains() 165 | */ 166 | public function assertNotContains($needle, $haystack, $message = null) { 167 | return $this->scenario->runStep(new \Codeception\Step\Action('assertNotContains', func_get_args())); 168 | } 169 | 170 | 171 | /** 172 | * [!] Method is generated. Documentation taken from corresponding module. 173 | * 174 | * Checks that variable is empty. 175 | * 176 | * @param $actual 177 | * @param string $message 178 | * @see \Codeception\Module\Asserts::assertEmpty() 179 | */ 180 | public function assertEmpty($actual, $message = null) { 181 | return $this->scenario->runStep(new \Codeception\Step\Action('assertEmpty', func_get_args())); 182 | } 183 | 184 | 185 | /** 186 | * [!] Method is generated. Documentation taken from corresponding module. 187 | * 188 | * Checks that variable is not empty. 189 | * 190 | * @param $actual 191 | * @param string $message 192 | * @see \Codeception\Module\Asserts::assertNotEmpty() 193 | */ 194 | public function assertNotEmpty($actual, $message = null) { 195 | return $this->scenario->runStep(new \Codeception\Step\Action('assertNotEmpty', func_get_args())); 196 | } 197 | 198 | 199 | /** 200 | * [!] Method is generated. Documentation taken from corresponding module. 201 | * 202 | * Checks that variable is NULL 203 | * 204 | * @param $actual 205 | * @param string $message 206 | * @see \Codeception\Module\Asserts::assertNull() 207 | */ 208 | public function assertNull($actual, $message = null) { 209 | return $this->scenario->runStep(new \Codeception\Step\Action('assertNull', func_get_args())); 210 | } 211 | 212 | 213 | /** 214 | * [!] Method is generated. Documentation taken from corresponding module. 215 | * 216 | * Checks that variable is not NULL 217 | * 218 | * @param $actual 219 | * @param string $message 220 | * @see \Codeception\Module\Asserts::assertNotNull() 221 | */ 222 | public function assertNotNull($actual, $message = null) { 223 | return $this->scenario->runStep(new \Codeception\Step\Action('assertNotNull', func_get_args())); 224 | } 225 | 226 | 227 | /** 228 | * [!] Method is generated. Documentation taken from corresponding module. 229 | * 230 | * Checks that condition is positive. 231 | * 232 | * @param $condition 233 | * @param string $message 234 | * @see \Codeception\Module\Asserts::assertTrue() 235 | */ 236 | public function assertTrue($condition, $message = null) { 237 | return $this->scenario->runStep(new \Codeception\Step\Action('assertTrue', func_get_args())); 238 | } 239 | 240 | 241 | /** 242 | * [!] Method is generated. Documentation taken from corresponding module. 243 | * 244 | * Checks that condition is negative. 245 | * 246 | * @param $condition 247 | * @param string $message 248 | * @see \Codeception\Module\Asserts::assertFalse() 249 | */ 250 | public function assertFalse($condition, $message = null) { 251 | return $this->scenario->runStep(new \Codeception\Step\Action('assertFalse', func_get_args())); 252 | } 253 | 254 | 255 | /** 256 | * [!] Method is generated. Documentation taken from corresponding module. 257 | * 258 | * Fails the test with message. 259 | * 260 | * @param $message 261 | * @see \Codeception\Module\Asserts::fail() 262 | */ 263 | public function fail($message) { 264 | return $this->scenario->runStep(new \Codeception\Step\Action('fail', func_get_args())); 265 | } 266 | } 267 | --------------------------------------------------------------------------------