File Coverage

blib/lib/Class/Trigger.pm
Criterion Covered Total %
statement 103 103 100.0
branch 48 50 96.0
condition 29 32 90.6
subroutine 15 15 100.0
pod 3 3 100.0
total 198 203 97.5


line stmt bran cond sub pod time code
1             package Class::Trigger;
2 10     10   775034 use 5.008_001;
  10         118  
3 10     10   58 use strict;
  10         16  
  10         295  
4 10     10   51 use vars qw($VERSION);
  10         33  
  10         673  
5             $VERSION = "0.15";
6              
7 10     10   61 use Carp ();
  10         41  
  10         864  
8              
9             my (%Triggers, %TriggerPoints);
10             my %Fetch_All_Triggers_Cache;
11              
12             sub import {
13 11     11   194 my $class = shift;
14 11         28 my $pkg = caller(0);
15              
16 11 100       50 $TriggerPoints{$pkg} = { map { $_ => 1 } @_ } if @_;
  3         14  
17              
18             # export mixin methods
19 10     10   70 no strict 'refs';
  10         31  
  10         6596  
20 11         31 my @methods = qw(add_trigger call_trigger last_trigger_results);
21 11         28 *{"$pkg\::$_"} = \&{$_} for @methods;
  33         2385  
  33         82  
22             }
23              
24             sub add_trigger {
25 30     30 1 7489 my $proto = shift;
26              
27 30         77 my $triggers = __fetch_triggers($proto);
28              
29 30         112 my %params = @_;
30 30         85 my @values = values %params;
31 30 100 100     106 if (@_ > 2 && (grep { ref && ref eq 'CODE' } @values) == @values) {
  20 100       91  
32 1         94 Carp::croak "mutiple trigger registration in one add_trigger() call is deprecated.";
33             }
34              
35 29 100 100     179 if ($#_ == 1 && ref($_[1]) eq 'CODE') {
36 20         108 @_ = (name => $_[0], callback => $_[1]);
37             }
38              
39 29         138 my %args = ( name => undef, callback => undef, abortable => undef, @_ );
40 29         57 my $when = $args{'name'};
41 29         47 my $code = $args{'callback'};
42 29         44 my $abortable = $args{'abortable'};
43 29         75 __validate_triggerpoint( $proto, $when );
44 28 100       510 Carp::croak('add_trigger() needs coderef') unless ref($code) eq 'CODE';
45 26         36 push @{ $triggers->{$when} }, [ $code, $abortable ];
  26         88  
46              
47             # Clear the cache when class triggers are added. Because triggers are
48             # inherited adding a trigger to one class may effect others. Simplest
49             # thing to do is to clear the whole thing.
50 26 100       75 %Fetch_All_Triggers_Cache = () unless ref $proto;
51              
52 26         141 1;
53             }
54              
55              
56             sub last_trigger_results {
57 1     1 1 3 my $self = shift;
58 1 50       4 my $result_store = ref($self) ? $self : ${Class::Trigger::_trigger_results}->{$self};
59 1         7 return $result_store->{'_class_trigger_results'};
60             }
61              
62             sub call_trigger {
63 37     37 1 5891 my $self = shift;
64 37         64 my $when = shift;
65              
66 37         70 my @return;
67              
68 37 100       97 my $result_store = ref($self) ? $self : ${Class::Trigger::_trigger_results}->{$self};
69              
70 37         136 $result_store->{'_class_trigger_results'} = [];
71              
72 37 100       92 if (my @triggers = __fetch_all_triggers($self, $when)) { # any triggers?
73 31         64 for my $trigger (@triggers) {
74 51         131 my @return = $trigger->[0]->($self, @_);
75 51         705 push @{$result_store->{'_class_trigger_results'}}, \@return;
  51         126  
76 51 100 100     196 return undef if ($trigger->[1] and not $return[0]); # only abort on false values.
77             }
78             }
79             else {
80             # if validation is enabled we can only add valid trigger points
81             # so we only need to check in call_trigger() if there's no
82             # trigger with the requested name.
83 6         19 __validate_triggerpoint($self, $when);
84             }
85              
86 35         60 return scalar @{$result_store->{'_class_trigger_results'}};
  35         105  
87             }
88              
89             sub __fetch_all_triggers {
90 43     43   104 my ($obj, $when, $list, $order, $nocache) = @_;
91 43 100       102 $nocache = 0 unless defined $nocache;
92 43   66     126 my $class = ref $obj || $obj;
93 43         67 my $return;
94 43 100       91 my $when_key = defined $when ? $when : '';
95            
96 43 100       86 unless ($nocache) {
97             return __cached_triggers($obj, $when)
98 37 100       114 if $Fetch_All_Triggers_Cache{$class}{$when_key};
99             }
100            
101 36 100       97 unless ($list) {
102             # Absence of the $list parameter conditions the creation of
103             # the unrolled list of triggers. These keep track of the unique
104             # set of triggers being collected for each class and the order
105             # in which to return them (based on hierarchy; base class
106             # triggers are returned ahead of descendant class triggers).
107 30         51 $list = {};
108 30         51 $order = [];
109 30         58 $return = 1;
110             }
111 10     10   77 no strict 'refs';
  10         34  
  10         4842  
112 36         67 my @classes = @{$class . '::ISA'};
  36         152  
113 36         69 push @classes, $class;
114 36         84 foreach my $c (@classes) {
115 42 100       96 next if $list->{$c};
116             # if (UNIVERSAL::can($c, 'call_trigger')) {
117 36 50       199 if ($c->can('call_trigger')) {
118 36         82 $list->{$c} = [];
119 36 100       131 __fetch_all_triggers($c, $when, $list, $order, 1)
120             unless $c eq $class;
121 36 100 100     163 if (defined $when && $Triggers{$c}{$when}) {
122 29         52 push @$order, $c;
123 29         87 $list->{$c} = $Triggers{$c}{$when};
124             }
125             }
126             }
127 36 100       108 if ($return) {
128 30         48 my @triggers;
129 30         66 foreach my $class (@$order) {
130 29         41 push @triggers, @{ $list->{$class} };
  29         69  
131             }
132              
133             # Only cache the class triggers, object triggers would
134             # necessitate a much larger cache and they're cheap to
135             # get anyway.
136 30         78 $Fetch_All_Triggers_Cache{$class}{$when_key} = \@triggers;
137              
138 30         84 return __cached_triggers($obj, $when);
139             }
140             }
141              
142              
143             sub __cached_triggers {
144 37     37   92 my($proto, $when) = @_;
145 37   66     98 my $class = ref $proto || $proto;
146            
147 37   100     113 return @{ $Fetch_All_Triggers_Cache{$class}{$when || ''} },
148 37         57 @{ __object_triggers($proto, $when) };
  37         99  
149             }
150              
151              
152             sub __object_triggers {
153 37     37   72 my($obj, $when) = @_;
154            
155 37 100 100     158 return [] unless ref $obj && defined $when;
156 35   100     310 return $obj->{__triggers}{$when} || [];
157             }
158              
159              
160             sub __validate_triggerpoint {
161 35 100 66 35   173 return unless my $points = $TriggerPoints{ref $_[0] || $_[0]};
162 5         12 my ($self, $when) = @_;
163             Carp::croak("$when is not valid triggerpoint for ".(ref($self) ? ref($self) : $self))
164 5 100       334 unless $points->{$when};
    100          
165             }
166              
167             sub __fetch_triggers {
168 30     30   73 my ($obj, $proto) = @_;
169             # check object based triggers first
170 30 100 100     230 return ref $obj ? $obj->{__triggers} ||= {} : $Triggers{$obj} ||= {};
      100        
171             }
172              
173             1;
174             __END__