File Coverage

blib/lib/Beam/Emitter.pm
Criterion Covered Total %
statement 71 71 100.0
branch 16 18 88.8
condition 9 10 90.0
subroutine 16 16 100.0
pod 7 7 100.0
total 119 122 97.5


line stmt bran cond sub pod time code
1             package Beam::Emitter;
2             our $VERSION = '1.007';
3             # ABSTRACT: Role for event emitting classes
4              
5             #pod =head1 SYNOPSIS
6             #pod
7             #pod # A simple custom event class to perform data validation
8             #pod { package My::Event;
9             #pod use Moo;
10             #pod extends 'Beam::Event';
11             #pod has data => ( is => 'ro' );
12             #pod }
13             #pod
14             #pod # A class that reads and writes data, allowing event handlers to
15             #pod # process the data
16             #pod { package My::Emitter;
17             #pod use Moo;
18             #pod with 'Beam::Emitter';
19             #pod
20             #pod sub write_data {
21             #pod my ( $self, @data ) = @_;
22             #pod
23             #pod # Give event listeners a chance to perform further processing of
24             #pod # data
25             #pod my $event = $self->emit( "process_data",
26             #pod class => 'My::Event',
27             #pod data => \@data,
28             #pod );
29             #pod
30             #pod # Give event listeners a chance to stop the write
31             #pod return if $event->is_default_stopped;
32             #pod
33             #pod # Write the data
34             #pod open my $file, '>', 'output';
35             #pod print { $file } @data;
36             #pod close $file;
37             #pod
38             #pod # Notify listeners we're done writing and send them the data
39             #pod # we wrote
40             #pod $self->emit( 'after_write', class => 'My::Event', data => \@data );
41             #pod }
42             #pod }
43             #pod
44             #pod # An event handler that increments every input value in our data
45             #pod sub increment {
46             #pod my ( $event ) = @_;
47             #pod my $data = $event->data;
48             #pod $_++ for @$data;
49             #pod }
50             #pod
51             #pod # An event handler that performs data validation and stops the
52             #pod # processing if invalid
53             #pod sub prevent_negative {
54             #pod my ( $event ) = @_;
55             #pod my $data = $event->data;
56             #pod $event->prevent_default if grep { $_ < 0 } @$data;
57             #pod }
58             #pod
59             #pod # An event handler that logs the data to STDERR after we've written in
60             #pod sub log_data {
61             #pod my ( $event ) = @_;
62             #pod my $data = $event->data;
63             #pod print STDERR "Wrote data: " . join( ',', @$data );
64             #pod }
65             #pod
66             #pod # Wire up our event handlers to a new processing object
67             #pod my $processor = My::Emitter->new;
68             #pod $processor->on( process_data => \&increment );
69             #pod $processor->on( process_data => \&prevent_negative );
70             #pod $processor->on( after_write => \&log_data );
71             #pod
72             #pod # Process some data
73             #pod $processor->process_data( 1, 2, 3, 4, 5 );
74             #pod $processor->process_data( 1, 3, 7, -9, 11 );
75             #pod
76             #pod # Log data before and after writing
77             #pod my $processor = My::Emitter->new;
78             #pod $processor->on( process_data => \&log_data );
79             #pod $processor->on( after_write => \&log_data );
80             #pod
81             #pod =head1 DESCRIPTION
82             #pod
83             #pod This role is used by classes that want to add callback hooks to allow
84             #pod users to add new behaviors to their objects. These hooks are called
85             #pod "events". A subscriber registers a callback for an event using the
86             #pod L or L methods. Then, the class can call those
87             #pod callbacks by L.
88             #pod
89             #pod Using the L class, subscribers can stop an event from being
90             #pod processed, or prevent the default action from happening.
91             #pod
92             #pod =head2 Using Beam::Event
93             #pod
94             #pod L is an event object with some simple methods to allow subscribers
95             #pod to influence the handling of the event. By calling L
96             #pod method|Beam::Event/stop>, subscribers can stop all futher handling of the
97             #pod event. By calling the L,
98             #pod subscribers can allow other subscribers to be notified about the event, but let
99             #pod the emitter know that it shouldn't continue with what it was going to do.
100             #pod
101             #pod For example, let's build a door that notifies when someone tries to open it.
102             #pod Different instances of a door should allow different checks before the door
103             #pod opens, so we'll emit an event before we decide to open.
104             #pod
105             #pod package Door;
106             #pod use Moo;
107             #pod with 'Beam::Emitter';
108             #pod
109             #pod sub open {
110             #pod my ( $self, $who ) = @_;
111             #pod my $event = $self->emit( 'before_open' );
112             #pod return if $event->is_default_stopped;
113             #pod $self->open_the_door;
114             #pod }
115             #pod
116             #pod package main;
117             #pod my $door = Door->new;
118             #pod $door->open;
119             #pod
120             #pod Currently, our door will open for anybody. But let's build a door that only
121             #pod open opens after noon (to keep us from having to wake up in the morning).
122             #pod
123             #pod use Time::Piece;
124             #pod my $restful_door = Door->new;
125             #pod
126             #pod $restful_door->on( before_open => sub {
127             #pod my ( $event ) = @_;
128             #pod
129             #pod my $time = Time::Piece->now;
130             #pod if ( $time->hour < 12 ) {
131             #pod $event->stop_default;
132             #pod }
133             #pod
134             #pod } );
135             #pod
136             #pod $restful_door->open;
137             #pod
138             #pod By calling L, we set the
139             #pod L flag, which the door sees
140             #pod and decides not to open.
141             #pod
142             #pod =head2 Using Custom Events
143             #pod
144             #pod The default C is really only useful for notifications. If you want
145             #pod to give your subscribers some data, you need to create a custom event class.
146             #pod This allows you to add attributes and methods to your events (with all
147             #pod the type constraints and coersions you want).
148             #pod
149             #pod Let's build a door that can keep certain people out. Right now, our door
150             #pod doesn't care who is trying to open it, and our subscribers do not get enough
151             #pod information to deny entry to certain people.
152             #pod
153             #pod So first we need to build an event object that can let our subscribers know
154             #pod who is knocking on the door.
155             #pod
156             #pod package Door::Knock;
157             #pod use Moo;
158             #pod extends 'Beam::Event';
159             #pod
160             #pod has who => (
161             #pod is => 'ro',
162             #pod required => 1,
163             #pod );
164             #pod
165             #pod Now that we can represent who is knocking, let's notify our subscribers.
166             #pod
167             #pod package Door;
168             #pod use Moo;
169             #pod use Door::Knock; # Our emitter must load the class, Beam::Emitter will not
170             #pod with 'Beam::Emitter';
171             #pod
172             #pod sub open {
173             #pod my ( $self, $who ) = @_;
174             #pod my $event = $self->emit( 'before_open', class => 'Door::Knock', who => $who );
175             #pod return if $event->is_default_stopped;
176             #pod $self->open_the_door;
177             #pod }
178             #pod
179             #pod Finally, let's build a listener that knows who is allowed in the door.
180             #pod
181             #pod my $private_door = Door->new;
182             #pod $private_door->on( before_open => sub {
183             #pod my ( $event ) = @_;
184             #pod
185             #pod if ( $event->who ne 'preaction' ) {
186             #pod $event->stop_default;
187             #pod }
188             #pod
189             #pod } );
190             #pod
191             #pod $private_door->open;
192             #pod
193             #pod =head2 Without Beam::Event
194             #pod
195             #pod Although checking C is completely optional, if you do not
196             #pod wish to use the C object, you can instead call L
197             #pod instead of L to give arbitrary arguments to your listeners.
198             #pod
199             #pod package Door;
200             #pod use Moo;
201             #pod with 'Beam::Emitter';
202             #pod
203             #pod sub open {
204             #pod my ( $self, $who ) = @_;
205             #pod $self->emit_args( 'open', $who );
206             #pod $self->open_the_door;
207             #pod }
208             #pod
209             #pod There's no way to stop the door being opened, but you can at least notify
210             #pod someone before it does.
211             #pod
212             #pod =head1 SEE ALSO
213             #pod
214             #pod =over 4
215             #pod
216             #pod =item L
217             #pod
218             #pod =item L
219             #pod
220             #pod This document contains some useful patterns for your event emitters and
221             #pod listeners.
222             #pod
223             #pod =item L
224             #pod
225             #pod Coordinating Christmas Dinner with Beam::Emitter by Yanick Champoux.
226             #pod
227             #pod =back
228             #pod
229             #pod =cut
230              
231 10     10   105830 use strict;
  10         20  
  10         267  
232 10     10   34 use warnings;
  10         13  
  10         278  
233              
234 10     10   4559 use Types::Standard qw(:all);
  10         472218  
  10         109  
235 10     10   259787 use Scalar::Util qw( weaken refaddr );
  10         19  
  10         666  
236 10     10   41 use Carp qw( croak );
  10         16  
  10         418  
237 10     10   4859 use Beam::Event;
  10         26  
  10         358  
238 10     10   65 use Module::Runtime qw( use_module );
  10         19  
  10         59  
239 10     10   2165 use Moo::Role; # Put this last to ensure proper, automatic cleanup
  10         22447  
  10         67  
240              
241              
242             # The event listeners on this object, a hashref of arrayrefs of
243             # EVENT_NAME => [ Beam::Listener object, ... ]
244              
245             has _listeners => (
246             is => 'ro',
247             isa => HashRef,
248             default => sub { {} },
249             );
250              
251             #pod =method subscribe ( event_name, subref, [ %args ] )
252             #pod
253             #pod Subscribe to an event from this object. C is the name of the event.
254             #pod C is a subroutine reference that will get either a L object
255             #pod (if using the L method) or something else (if using the L method).
256             #pod
257             #pod Returns a coderef that, when called, unsubscribes the new subscriber.
258             #pod
259             #pod my $unsubscribe = $emitter->subscribe( open_door => sub {
260             #pod warn "ding!";
261             #pod } );
262             #pod $emitter->emit( 'open_door' ); # ding!
263             #pod $unsubscribe->();
264             #pod $emitter->emit( 'open_door' ); # no ding
265             #pod
266             #pod This unsubscribe subref makes it easier to stop our subscription in a safe,
267             #pod non-leaking way:
268             #pod
269             #pod my $unsub;
270             #pod $unsub = $emitter->subscribe( open_door => sub {
271             #pod $unsub->(); # Only handle one event
272             #pod } );
273             #pod $emitter->emit( 'open_door' );
274             #pod
275             #pod The above code does not leak memory, but the following code does:
276             #pod
277             #pod # Create a memory cycle which must be broken manually
278             #pod my $cb;
279             #pod $cb = sub {
280             #pod my ( $event ) = @_;
281             #pod $event->emitter->unsubscribe( open_door => $cb ); # Only handle one event
282             #pod # Because the callback sub ($cb) closes over a reference to itself
283             #pod # ($cb), it can never be cleaned up unless something breaks the
284             #pod # cycle explicitly.
285             #pod };
286             #pod $emitter->subscribe( open_door => $cb );
287             #pod $emitter->emit( 'open_door' );
288             #pod
289             #pod The way to fix this second example is to explicitly C inside the callback
290             #pod sub. Forgetting to do that will result in a leak. The returned unsubscribe coderef
291             #pod does not have this issue.
292             #pod
293             #pod By default, the emitter only stores the subroutine reference in an
294             #pod object of class L. If more information should be
295             #pod stored, create a custom subclass of L and use C<%args>
296             #pod to specify the class name and any attributes to be passed to its
297             #pod constructor:
298             #pod
299             #pod {
300             #pod package MyListener;
301             #pod extends 'Beam::Listener';
302             #pod
303             #pod # add metadata with subscription time
304             #pod has sub_time => is ( 'ro',
305             #pod init_arg => undef,
306             #pod default => sub { time() },
307             #pod );
308             #pod }
309             #pod
310             #pod # My::Emitter consumes the Beam::Emitter role
311             #pod my $emitter = My::Emitter->new;
312             #pod $emitter->on( "foo",
313             #pod sub { print "Foo happened!\n"; },
314             #pod class => MyListener
315             #pod );
316             #pod
317             #pod The L method can be used to examine the subscribed listeners.
318             #pod
319             #pod
320             #pod =cut
321              
322             sub subscribe {
323 52     52 1 10464 my ( $self, $name, $sub, %args ) = @_;
324              
325 52   100     4053 my $class = delete $args{ class } || "Beam::Listener";
326 52 50       4195 croak( "listener object must descend from Beam::Listener" )
327             unless use_module($class)->isa( 'Beam::Listener' );
328              
329 52         118321 my $listener = $class->new( %args, callback => $sub );
330              
331 50         145128 push @{ $self->_listeners->{$name} }, $listener;
  50         7654  
332 50         3907 weaken $self;
333 50         3768 weaken $sub;
334             return sub {
335 16 100   16   8176 $self->unsubscribe($name => $sub)
336             if defined $self;
337 50         13266 };
338             }
339              
340             #pod =method on ( event_name, subref )
341             #pod
342             #pod An alias for L. B: Do not use this alias for method
343             #pod modifiers! If you want to override behavior, override C.
344             #pod
345             #pod =cut
346              
347 38     38 1 97758 sub on { shift->subscribe( @_ ) }
348              
349             #pod =method unsubscribe ( event_name [, subref ] )
350             #pod
351             #pod Unsubscribe from an event. C is the name of the event. C is
352             #pod the single listener subref to be removed. If no subref is given, will remove
353             #pod all listeners for this event.
354             #pod
355             #pod =cut
356              
357             sub unsubscribe {
358 24     24 1 3200 my ( $self, $name, $sub ) = @_;
359 24 100       2825 if ( !$sub ) {
360 1         4 delete $self->_listeners->{$name};
361             }
362             else {
363 23         2868 my $listeners = $self->_listeners->{$name};
364 23         2800 my $idx = 0;
365 23   100     2807 $idx++ until $idx > $#{$listeners} or refaddr $listeners->[$idx]->callback eq refaddr $sub;
  24         5767  
366 23 100       2815 if ( $idx > $#{$listeners} ) {
  23         5594  
367 2         200 croak "Could not find sub in listeners";
368             }
369 21         2809 splice @{$self->_listeners->{$name}}, $idx, 1;
  21         8412  
370             }
371 22         11249 return;
372             }
373              
374             #pod =method un ( event_name [, subref ] )
375             #pod
376             #pod An alias for L. B: Do not use this alias for method
377             #pod modifiers! If you want to override behavior, override C.
378             #pod
379             #pod =cut
380              
381 6     6 1 2248 sub un { shift->unsubscribe( @_ ) }
382              
383             #pod =method emit ( name, event_args )
384             #pod
385             #pod Emit a L with the given C. C is a list of name => value
386             #pod pairs to give to the C constructor.
387             #pod
388             #pod Use the C key in C to specify a different Event class.
389             #pod
390             #pod =cut
391              
392             sub emit {
393 37     37 1 10958 my ( $self, $name, %args ) = @_;
394              
395 37   100     2966 my $class = delete $args{ class } || "Beam::Event";
396 37 100       2937 $args{ emitter } = $self if ! defined $args{ emitter };
397 37   66     2922 $args{ name } ||= $name;
398 37         28659 my $event = $class->new( %args );
399              
400 37 100       129241 return $event unless exists $self->_listeners->{$name};
401              
402             # don't use $self->_listeners->{$name} directly, as callbacks may unsubscribe
403             # from $name, changing the array, and confusing the for loop
404 36         2815 my @listeners = @{ $self->_listeners->{$name} };
  36         5674  
405              
406 36         2852 for my $listener ( @listeners ) {
407 38         3802 $listener->callback->( $event );
408 38 100       36777 last if $event->is_stopped;
409             }
410 36         25769 return $event;
411             }
412              
413             #pod =method emit_args ( name, callback_args )
414             #pod
415             #pod Emit an event with the given C. C is a list that will be given
416             #pod directly to each subscribed callback.
417             #pod
418             #pod Use this if you want to avoid using L, though you miss out on the control
419             #pod features like L and L.
420             #pod
421             #pod =cut
422              
423             sub emit_args {
424 3     3 1 694 my ( $self, $name, @args ) = @_;
425              
426 3 100       12 return unless exists $self->_listeners->{$name};
427              
428             # don't use $self->_listeners->{$name} directly, as callbacks may unsubscribe
429             # from $name, changing the array, and confusing the for loop
430 2         3 my @listeners = @{ $self->_listeners->{$name} };
  2         6  
431              
432 2         6 for my $listener ( @listeners ) {
433 3         7 $listener->callback->( @args );
434             }
435 2         518 return;
436             }
437              
438             #pod =method listeners ( event_name )
439             #pod
440             #pod Returns a list containing the listeners which have subscribed to the
441             #pod specified event from this emitter. The list elements are either
442             #pod instances of L or of custom classes specified in calls
443             #pod to L.
444             #pod
445             #pod =cut
446              
447             sub listeners {
448              
449 4     4 1 3535 my ( $self, $name ) = @_;
450              
451 4 50       3 return @{ $self->_listeners->{$name} || [] };
  4         24  
452             }
453              
454             1;
455              
456             __END__