File Coverage

blib/lib/Mixin/Event/Dispatch.pm
Criterion Covered Total %
statement 61 66 92.4
branch 14 26 53.8
condition 7 10 70.0
subroutine 14 15 93.3
pod 6 6 100.0
total 102 123 82.9


line stmt bran cond sub pod time code
1             package Mixin::Event::Dispatch;
2             # ABSTRACT: Mixin methods for simple event/message dispatch framework
3 5     5   9239 use strict;
  5         9  
  5         143  
4 5     5   25 use warnings;
  5         7  
  5         193  
5              
6             our $VERSION = '1.006';
7              
8             # Key name to use for event handlers. Nothing should be
9             # accessing this directly so we don't mind something
10             # unreadable, it's only used in two methods which subclasses
11             # can override at will
12 5     5   25 use constant EVENT_HANDLER_KEY => '__MED_event_handlers';
  5         7  
  5         399  
13              
14             # Legacy support, newer classes probably would turn this off
15 5     5   22 use constant EVENT_DISPATCH_ON_FALLBACK => 1;
  5         7  
  5         229  
16              
17             =encoding utf8
18              
19             =head1 NAME
20              
21             Mixin::Event::Dispatch - mixin methods for simple event/message dispatch framework
22              
23             =head1 VERSION
24              
25             Version 1.006
26              
27             =head1 SYNOPSIS
28              
29             # Add a handler then invoke it
30             package Some::Class;
31             use parent qw(Mixin::Event::Dispatch);
32             sub new { bless {}, shift }
33              
34             my $obj = Some::Class->new;
35              
36             # Subscribe to events - subscribers will be called with an event object,
37             # and any event parameters, each time the event is raised.
38             $obj->subscribe_to_event(another_event => (my $code = sub {
39             my $ev = shift;
40             warn "[] @_";
41             }));
42             $obj->invoke_event(another_event => 'like this');
43             # should get output 'Event data: like this'
44             $obj->unsubscribe_from_event(another_event => $code);
45              
46             $obj->trace_events_for(sub {
47             $obj->invoke_event(another_event => 'like this');
48             });
49              
50             # Note that handlers will be called for each instance of an event until they return false,
51             # at which point the handler will be removed, so for a permanent handler, make sure to return 1.
52             $obj->add_handler_for_event(some_event => sub { my $self = shift; warn "had some_event: @_"; 1; });
53             $obj->invoke_event(some_event => 'message here');
54              
55             # Attach event handler for all on_XXX named parameters
56             package Event::User;
57             sub configure {
58             my $self = shift;
59             my %args = @_;
60             $self->add_handler_for_event(
61             map { (/^on_(.*)$/) ? ($1 => $args{$_}) : () } keys %args
62             );
63             return $self;
64             }
65              
66             =head1 DESCRIPTION
67              
68             Add this in as a parent to your class, and it'll provide some methods for defining event handlers (L</subscribe_to_event> or L</add_handler_for_event>) and calling them (L</invoke_event>).
69              
70             Note that handlers should return 0 for a one-off handler, and 1 if it should be called again on the next event.
71              
72             =head1 SPECIAL EVENTS
73              
74             A single event has been reserved for cases where a callback dies:
75              
76             =over 4
77              
78             =item * C< event_error > - if a handler is available, this will be called instead of dying whenever any other handler dies. If an C< event_error > handler also fails,
79             then this error will be re-thrown. As with the other handlers, you can have more than one C< event_error > handler.
80              
81             =back
82              
83             =cut
84              
85 5     5   4273 use List::UtilsBy ();
  5         7838  
  5         90  
86 5     5   32 use Scalar::Util ();
  5         11  
  5         80  
87 5     5   3078 use Mixin::Event::Dispatch::Event;
  5         11  
  5         3427  
88              
89             =head1 METHODS
90              
91             =cut
92              
93             =head2 invoke_event
94              
95             Takes an C<event> parameter, and optional additional parameters that are passed to any callbacks.
96              
97             $self->invoke_event('new_message', from => 'fred', subject => 'test message');
98              
99             Returns $self if a handler was found, undef if not.
100              
101             =cut
102              
103             sub invoke_event {
104 8     8 1 2401 my ($self, $event_name, @param) = @_;
105 8   100     24 my $handlers = $self->event_handlers->{$event_name} || [];
106 8 100       25 unless(@$handlers) {
107             # Legacy flag - when set, pass control to on_$event_name
108             # if we don't have a handler defined.
109 3 100 66     38 if($self->EVENT_DISPATCH_ON_FALLBACK && (my $code = $self->can("on_$event_name"))) {
110 2         3 local $@;
111             eval {
112 2         7 $code->($self, @_);
113 2         11 1;
114 2 50       4 } or do {
115 0 0       0 die $@ if $event_name eq 'event_error';
116 0 0       0 $self->invoke_event(event_error => $@) or die "$@ and no event_error handler found";
117             };
118             }
119 3         15 return $self;
120             }
121              
122             # We should really do this...
123             # my $ev = Mixin::Event::Dispatch::Event->new(
124             # name => $event_name,
125             # instance => $self,
126             # handlers => [ @$handlers ],
127             # );
128             # $ev->dispatch;
129             # ... but this gives better performance (examples/benchmark.pl)
130 5         58 (bless {
131             name => $event_name,
132             instance => $self,
133             # Passing a copy since we might change these later and
134             # we do not want those changes to affect any events
135             # currently in flight
136             handlers => [ @$handlers ],
137             }, 'Mixin::Event::Dispatch::Event')->dispatch(@param);
138 5         43 return $self;
139             }
140              
141             =head2 subscribe_to_event
142              
143             Subscribe the given coderef to the named event.
144              
145             Called with a list of event name and handler pairs. An
146             event name can be any string value. The handler is one
147             of the following:
148              
149             =over 4
150              
151             =item * a coderef will be used directly as a handler,
152             and will be passed the L<Mixin::Event::Dispatch::Event>
153             object representing this event.
154              
155             =item * a plain string will be used as a method name
156              
157             =item * a subclass of L<Mixin::Event::Dispatch> will
158             be used to delegate the event - use this if you have
159             an object hierarchy and want the parent object to handle
160             events on the current object
161              
162             =back
163              
164             If you have an overloaded object which is both a
165             L<Mixin::Event::Dispatch> subclass and provides a
166             coderef overload, it will default to event delegation
167             behaviour. To ensure the overloaded coderef is used
168             instead, pass \&$obj instead.
169              
170             All handlers will be given an event (a
171             L<Mixin::Event::Dispatch::Event> object) as the first
172             parameter, and any passed event parameters as the
173             remainder of @_.
174              
175             Example usage:
176              
177             my $parent = $obj->parent;
178             $obj->subscribe_to_event(
179             connect => sub { warn shift->name }, # warns 'connect'
180             connect => $parent, # $parent->invoke_event(connect => @_)
181             connect => \&$parent, # $parent's overloaded &{}
182             joined => 'on_joined', # the on_joined method in $obj
183             );
184              
185             Note that multiple handlers can be assigned to the same
186             event name.
187              
188             =cut
189              
190             sub subscribe_to_event {
191 2     2 1 971 my $self = shift;
192              
193             # Init if we haven't got a valid event_handlers yet
194 2 50       20 $self->clear_event_handlers unless $self->event_handlers;
195              
196             # Add the defined handlers
197 2         15 while(@_) {
198 2         9 my ($ev, $code) = splice @_, 0, 2;
199 2 50       8 die 'Undefined event?' unless defined $ev;
200 2         5 push @{$self->event_handlers->{$ev}}, $code;
  2         7  
201 2 50 33     40 Scalar::Util::weaken($self->event_handlers->{$ev}[-1]) if ref($code) && Scalar::Util::reftype($code) ne 'CODE'
202             }
203 2         35 return $self;
204             }
205              
206             =head2 unsubscribe_from_event
207              
208             Removes the given coderef from the list of handlers for this event.
209              
210             Expects pairs of (event name, coderef) entries for the events to
211             unsubscribe from.
212              
213             Example usage:
214              
215             $obj->subscribe_to_event(
216             some_event => (my $code = sub { }),
217             );
218             $obj->unsubscribe_from_event(
219             some_event => $code,
220             );
221              
222             If you need to unsubscribe from the event currently being
223             handled, try the L<Mixin::Event::Dispatch::Event/unsubscribe>
224             method.
225              
226             Returns $self.
227              
228             =cut
229              
230             sub unsubscribe_from_event {
231 2     2 1 1008 my $self = shift;
232              
233             # Init if we haven't got a valid event_handlers yet
234 2 50       7 $self->clear_event_handlers unless $self->event_handlers;
235              
236             # Add the defined handlers
237 2         16 while(@_) {
238 2         9 my ($ev, $code) = splice @_, 0, 2;
239 2 50       7 die 'Undefined event?' unless defined $ev;
240             List::UtilsBy::extract_by {
241 2     2   29 Scalar::Util::refaddr($code) == Scalar::Util::refaddr($_)
242 2 50       11 } @{$self->event_handlers->{$ev}} or die "Was not subscribed to $ev for $code";
  2         5  
243             }
244 2         34 return $self;
245             }
246              
247             =head2 add_handler_for_event
248              
249             Adds handlers to the stack for the given events.
250              
251             $self->add_handler_for_event(
252             new_message => sub { warn @_; 1 },
253             login => sub { warn @_; 1 },
254             logout => sub { warn @_; 1 },
255             );
256              
257             =cut
258              
259             sub add_handler_for_event {
260 2     2 1 5 my $self = shift;
261              
262             # Init if we haven't got a valid event_handlers yet
263 2 50       5 $self->clear_event_handlers unless $self->event_handlers;
264              
265             # Add the defined handlers
266 2         6 while(@_) {
267 2         25 my ($ev, $code) = splice @_, 0, 2;
268             # Support legacy interface via wrapper
269             # * handler is passed $self
270             # * returning false means we want to unsubscribe
271 2         6 push @{$self->event_handlers->{$ev}}, sub {
272 2     2   3 my $ev = shift;
273 2 100       7 return if $code->($ev->instance, @_);
274 1         9 $ev->unsubscribe;
275 2         4 };
276             }
277 2         7 return $self;
278             }
279              
280             =head2 event_handlers
281              
282             Accessor for the event stack itself - should return a hashref which maps event names to arrayrefs for
283             the currently defined handlers.
284              
285             =cut
286              
287 20   100 20 1 152 sub event_handlers { shift->{+EVENT_HANDLER_KEY} ||= {} }
288              
289             =head2 clear_event_handlers
290              
291             Removes all queued event handlers.
292              
293             Will also be called when defining the first handler to create the initial L</event_handlers> entry, should
294             be overridden by subclass if something other than $self->{event_handlers} should be used.
295              
296             =cut
297              
298             sub clear_event_handlers {
299 0     0 1   my $self = shift;
300 0           $self->{+EVENT_HANDLER_KEY} = { };
301 0           return $self;
302             }
303              
304             1;
305              
306             __END__
307              
308             =head1 API HISTORY
309              
310             Version 1.000 implemented L</subscribe_to_event> and L<Mixin::Event::Dispatch::Event>.
311              
312             Version 0.002 changed to use L</event_handlers> instead of C< event_stack > for storing the available handlers (normally only L<invoke_event> and
313             L<add_handler_for_event> are expected to be called directly).
314              
315             =head1 ROLE vs. MIXIN
316              
317             Most role systems should be able to use this class - either directly, or through a thin wrapper which adds
318             any required boilerplate. Try L<Moose> or L<Role::Tiny> / L<Moo::Role> for that.
319              
320             Alternatively, you could use this as a component via L<Class::C3::Componentised>.
321              
322             (I haven't tried any of the above options myself, please let me know if I'm spreading
323             disinformation here)
324              
325             =head1 SEE ALSO
326              
327             There are at least a dozen similar modules already on CPAN, here's a small sample:
328              
329             =over 4
330              
331             =item * L<Event::Distributor> - uses L<Future> to sequence callbacks, implementing
332             the concepts discussed in
333             L<Event-Reflexive programming|http://leonerds-code.blogspot.co.uk/search/label/event-reflexive>
334              
335             =item * L<Object::Event> - event callback interface used in several L<AnyEvent> modules.
336              
337             =item * L<Ambrosia::Event> - part of the L<Ambrosia> web application framework
338              
339             =item * L<Net::MessageBus> - event subscription via TCP-based message bus
340              
341             =item * L<Event::Wrappable> - wrapping for event listeners
342              
343             =item * L<MooseX::Event> - node.js-inspired events, for Moose users
344              
345             =back
346              
347             Note that some frameworks such as L<Reflex> and L<POE> already have comprehensive message-passing
348             and callback interfaces.
349              
350             If you're looking for usage examples, try the following:
351              
352             =over 4
353              
354             =item * L<Adapter::Async>
355              
356             =item * L<Net::Async::AMQP>
357              
358             =item * L<EntityModel> - uses this as the underlying event-passing mechanism, with some
359             support in L<EntityModel::Class> for indicating event usage metadata
360              
361             =item * L<Protocol::PostgreSQL> - mostly an adapter converting PostgreSQL database messages
362             to/from events using this class
363              
364             =item * L<Protocol::IMAP> - the same, but for the IMAPv4bis protocol
365              
366             =item * L<Protocol::XMPP> - and again for Jabber/XMPP
367              
368             =back
369              
370             =head1 AUTHOR
371              
372             Tom Molesworth <cpan@perlsite.co.uk>
373              
374             with thanks to various helpful people on freenode #perl who suggested making L</event_handlers> into an
375             accessor (to support non-hashref objects) and who patiently tried to explain about roles.
376              
377             =head1 LICENSE
378              
379             Copyright Tom Molesworth 2011-2014, based on code originally part of L<EntityModel>.
380             Licensed under the same terms as Perl itself.