Beam-Emitter

 view release on metacpan or  search on metacpan

lib/Beam/Emitter.pm  view on Meta::CPAN

#pod                 data => \@data,
#pod             );
#pod
#pod             # Give event listeners a chance to stop the write
#pod             return if $event->is_default_stopped;
#pod
#pod             # Write the data
#pod             open my $file, '>', 'output';
#pod             print { $file } @data;
#pod             close $file;
#pod
#pod             # Notify listeners we're done writing and send them the data
#pod             # we wrote
#pod             $self->emit( 'after_write', class => 'My::Event', data => \@data );
#pod         }
#pod     }
#pod
#pod     # An event handler that increments every input value in our data
#pod     sub increment {
#pod         my ( $event ) = @_;
#pod         my $data = $event->data;
#pod         $_++ for @$data;
#pod     }
#pod
#pod     # An event handler that performs data validation and stops the
#pod     # processing if invalid
#pod     sub prevent_negative {
#pod         my ( $event ) = @_;
#pod         my $data = $event->data;
#pod         $event->prevent_default if grep { $_ < 0 } @$data;
#pod     }
#pod
#pod     # An event handler that logs the data to STDERR after we've written in
#pod     sub log_data {
#pod         my ( $event ) = @_;
#pod         my $data = $event->data;
#pod         print STDERR "Wrote data: " . join( ',', @$data );
#pod     }
#pod
#pod     # Wire up our event handlers to a new processing object
#pod     my $processor = My::Emitter->new;
#pod     $processor->on( process_data => \&increment );
#pod     $processor->on( process_data => \&prevent_negative );
#pod     $processor->on( after_write => \&log_data );
#pod
#pod     # Process some data
#pod     $processor->process_data( 1, 2, 3, 4, 5 );
#pod     $processor->process_data( 1, 3, 7, -9, 11 );
#pod
#pod     # Log data before and after writing
#pod     my $processor = My::Emitter->new;
#pod     $processor->on( process_data => \&log_data );
#pod     $processor->on( after_write => \&log_data );
#pod
#pod =head1 DESCRIPTION
#pod
#pod This role is used by classes that want to add callback hooks to allow
#pod users to add new behaviors to their objects. These hooks are called
#pod "events". A subscriber registers a callback for an event using the
#pod L</subscribe> or L</on> methods. Then, the class can call those
#pod callbacks by L<emitting an event with the emit() method|/emit>.
#pod
#pod Using the L<Beam::Event> class, subscribers can stop an event from being
#pod processed, or prevent the default action from happening.
#pod
#pod =head2 Using Beam::Event
#pod
#pod L<Beam::Event> is an event object with some simple methods to allow subscribers
#pod to influence the handling of the event. By calling L<the stop
#pod method|Beam::Event/stop>, subscribers can stop all futher handling of the
#pod event. By calling the L<the stop_default method|Beam::Event/stop_default>,
#pod subscribers can allow other subscribers to be notified about the event, but let
#pod the emitter know that it shouldn't continue with what it was going to do.
#pod
#pod For example, let's build a door that notifies when someone tries to open it.
#pod Different instances of a door should allow different checks before the door
#pod opens, so we'll emit an event before we decide to open.
#pod
#pod     package Door;
#pod     use Moo;
#pod     with 'Beam::Emitter';
#pod
#pod     sub open {
#pod         my ( $self, $who ) = @_;
#pod         my $event = $self->emit( 'before_open' );
#pod         return if $event->is_default_stopped;
#pod         $self->open_the_door;
#pod     }
#pod
#pod     package main;
#pod     my $door = Door->new;
#pod     $door->open;
#pod
#pod Currently, our door will open for anybody. But let's build a door that only
#pod open opens after noon (to keep us from having to wake up in the morning).
#pod
#pod     use Time::Piece;
#pod     my $restful_door = Door->new;
#pod
#pod     $restful_door->on( before_open => sub {
#pod         my ( $event ) = @_;
#pod
#pod         my $time = Time::Piece->now;
#pod         if ( $time->hour < 12 ) {
#pod             $event->stop_default;
#pod         }
#pod
#pod     } );
#pod
#pod     $restful_door->open;
#pod
#pod By calling L<stop_default|Beam::Event/stop_default>, we set the
#pod L<is_default_stopped|Beam::Event/is_default_stopped> flag, which the door sees
#pod and decides not to open.
#pod
#pod =head2 Using Custom Events
#pod
#pod The default C<Beam::Event> is really only useful for notifications. If you want
#pod to give your subscribers some data, you need to create a custom event class.
#pod This allows you to add attributes and methods to your events (with all
#pod the type constraints and coersions you want).

lib/Beam/Emitter.pm  view on Meta::CPAN

#pod An alias for L</subscribe>. B<NOTE>: Do not use this alias for method
#pod modifiers! If you want to override behavior, override C<subscribe>.
#pod
#pod =cut

sub on { shift->subscribe( @_ ) }

#pod =method unsubscribe ( event_name [, subref ] )
#pod
#pod Unsubscribe from an event. C<event_name> is the name of the event. C<subref> is
#pod the single listener subref to be removed. If no subref is given, will remove
#pod all listeners for this event.
#pod
#pod =cut

sub unsubscribe {
    my ( $self, $name, $sub ) = @_;
    if ( !$sub ) {
        delete $self->_listeners->{$name};
    }
    else {
        my $listeners = $self->_listeners->{$name};
        my $idx = 0;
        $idx++ until $idx > $#{$listeners} or refaddr $listeners->[$idx]->callback eq refaddr $sub;
        if ( $idx > $#{$listeners} ) {
            croak "Could not find sub in listeners";
        }
        splice @{$self->_listeners->{$name}}, $idx, 1;
    }
    return;
}

#pod =method un ( event_name [, subref ] )
#pod
#pod An alias for L</unsubscribe>. B<NOTE>: Do not use this alias for method
#pod modifiers! If you want to override behavior, override C<unsubscribe>.
#pod
#pod =cut

sub un { shift->unsubscribe( @_ ) }

#pod =method emit ( name, event_args )
#pod
#pod Emit a L<Beam::Event> with the given C<name>. C<event_args> is a list of name => value
#pod pairs to give to the C<Beam::Event> constructor.
#pod
#pod Use the C<class> key in C<event_args> to specify a different Event class.
#pod
#pod =cut

sub emit {
    my ( $self, $name, %args ) = @_;

    my $class = delete $args{ class } || "Beam::Event";
    $args{ emitter  } = $self if ! defined $args{ emitter };
    $args{ name     } ||= $name;
    my $event = $class->new( %args );

    return $event unless exists $self->_listeners->{$name};

    # don't use $self->_listeners->{$name} directly, as callbacks may unsubscribe
    # from $name, changing the array, and confusing the for loop
    my @listeners = @{ $self->_listeners->{$name} };

    for my $listener ( @listeners  ) {
        $listener->callback->( $event );
        last if $event->is_stopped;
    }
    return $event;
}

#pod =method emit_args ( name, callback_args )
#pod
#pod Emit an event with the given C<name>. C<callback_args> is a list that will be given
#pod directly to each subscribed callback.
#pod
#pod Use this if you want to avoid using L<Beam::Event>, though you miss out on the control
#pod features like L<stop|Beam::Event/stop> and L<stop default|Beam::Event/stop_default>.
#pod
#pod =cut

sub emit_args {
    my ( $self, $name, @args ) = @_;

    return unless exists $self->_listeners->{$name};

    # don't use $self->_listeners->{$name} directly, as callbacks may unsubscribe
    # from $name, changing the array, and confusing the for loop
    my @listeners = @{ $self->_listeners->{$name} };

    for my $listener ( @listeners ) {
        $listener->callback->( @args );
    }
    return;
}

#pod =method listeners ( event_name )
#pod
#pod Returns a list containing the listeners which have subscribed to the
#pod specified event from this emitter.  The list elements are either
#pod instances of L<Beam::Listener> or of custom classes specified in calls
#pod to L</subscribe>.
#pod
#pod =cut

sub listeners {

    my ( $self, $name ) = @_;

    return @{ $self->_listeners->{$name} || [] };
}

1;

__END__

=pod

=head1 NAME

Beam::Emitter - Role for event emitting classes

=head1 VERSION

version 1.007

=head1 SYNOPSIS

    # A simple custom event class to perform data validation
    { package My::Event;
        use Moo;
        extends 'Beam::Event';
        has data => ( is => 'ro' );
    }

    # A class that reads and writes data, allowing event handlers to
    # process the data
    { package My::Emitter;
        use Moo;
        with 'Beam::Emitter';

        sub write_data {
            my ( $self, @data ) = @_;

            # Give event listeners a chance to perform further processing of
            # data
            my $event = $self->emit( "process_data",

lib/Beam/Emitter.pm  view on Meta::CPAN

                data => \@data,
            );

            # Give event listeners a chance to stop the write
            return if $event->is_default_stopped;

            # Write the data
            open my $file, '>', 'output';
            print { $file } @data;
            close $file;

            # Notify listeners we're done writing and send them the data
            # we wrote
            $self->emit( 'after_write', class => 'My::Event', data => \@data );
        }
    }

    # An event handler that increments every input value in our data
    sub increment {
        my ( $event ) = @_;
        my $data = $event->data;
        $_++ for @$data;
    }

    # An event handler that performs data validation and stops the
    # processing if invalid
    sub prevent_negative {
        my ( $event ) = @_;
        my $data = $event->data;
        $event->prevent_default if grep { $_ < 0 } @$data;
    }

    # An event handler that logs the data to STDERR after we've written in
    sub log_data {
        my ( $event ) = @_;
        my $data = $event->data;
        print STDERR "Wrote data: " . join( ',', @$data );
    }

    # Wire up our event handlers to a new processing object
    my $processor = My::Emitter->new;
    $processor->on( process_data => \&increment );
    $processor->on( process_data => \&prevent_negative );
    $processor->on( after_write => \&log_data );

    # Process some data
    $processor->process_data( 1, 2, 3, 4, 5 );
    $processor->process_data( 1, 3, 7, -9, 11 );

    # Log data before and after writing
    my $processor = My::Emitter->new;
    $processor->on( process_data => \&log_data );
    $processor->on( after_write => \&log_data );

=head1 DESCRIPTION

This role is used by classes that want to add callback hooks to allow
users to add new behaviors to their objects. These hooks are called
"events". A subscriber registers a callback for an event using the
L</subscribe> or L</on> methods. Then, the class can call those
callbacks by L<emitting an event with the emit() method|/emit>.

Using the L<Beam::Event> class, subscribers can stop an event from being
processed, or prevent the default action from happening.

=head2 Using Beam::Event

L<Beam::Event> is an event object with some simple methods to allow subscribers
to influence the handling of the event. By calling L<the stop
method|Beam::Event/stop>, subscribers can stop all futher handling of the
event. By calling the L<the stop_default method|Beam::Event/stop_default>,
subscribers can allow other subscribers to be notified about the event, but let
the emitter know that it shouldn't continue with what it was going to do.

For example, let's build a door that notifies when someone tries to open it.
Different instances of a door should allow different checks before the door
opens, so we'll emit an event before we decide to open.

    package Door;
    use Moo;
    with 'Beam::Emitter';

    sub open {
        my ( $self, $who ) = @_;
        my $event = $self->emit( 'before_open' );
        return if $event->is_default_stopped;
        $self->open_the_door;
    }

    package main;
    my $door = Door->new;
    $door->open;

Currently, our door will open for anybody. But let's build a door that only
open opens after noon (to keep us from having to wake up in the morning).

    use Time::Piece;
    my $restful_door = Door->new;

    $restful_door->on( before_open => sub {
        my ( $event ) = @_;

        my $time = Time::Piece->now;
        if ( $time->hour < 12 ) {
            $event->stop_default;
        }

    } );

    $restful_door->open;

By calling L<stop_default|Beam::Event/stop_default>, we set the
L<is_default_stopped|Beam::Event/is_default_stopped> flag, which the door sees
and decides not to open.

=head2 Using Custom Events

The default C<Beam::Event> is really only useful for notifications. If you want
to give your subscribers some data, you need to create a custom event class.
This allows you to add attributes and methods to your events (with all
the type constraints and coersions you want).



( run in 3.365 seconds using v1.01-cache-2.11-cpan-140bd7fdf52 )