File Coverage

blib/lib/Class/Trigger.pm
Criterion Covered Total %
statement 103 103 100.0
branch 48 50 96.0
condition 28 32 87.5
subroutine 15 15 100.0
pod 3 3 100.0
total 197 203 97.0


line stmt bran cond sub pod time code
1             package Class::Trigger;
2 10     10   13159 use 5.008_001;
  10         27  
3 10     10   43 use strict;
  10         11  
  10         251  
4 10     10   37 use vars qw($VERSION);
  10         17  
  10         501  
5             $VERSION = "0.13_01";
6              
7 10     10   39 use Carp ();
  10         27  
  10         656  
8              
9             my (%Triggers, %TriggerPoints);
10             my %Fetch_All_Triggers_Cache;
11              
12             sub import {
13 11     11   137 my $class = shift;
14 11         19 my $pkg = caller(0);
15              
16 11 100       40 $TriggerPoints{$pkg} = { map { $_ => 1 } @_ } if @_;
  3         11  
17              
18             # export mixin methods
19 10     10   45 no strict 'refs';
  10         10  
  10         5522  
20 11         22 my @methods = qw(add_trigger call_trigger last_trigger_results);
21 11         738 *{"$pkg\::$_"} = \&{$_} for @methods;
  33         2313  
  33         76  
22             }
23              
24             sub add_trigger {
25 30     30 1 258 my $proto = shift;
26              
27 30         67 my $triggers = __fetch_triggers($proto);
28              
29 30         86 my %params = @_;
30 30         68 my @values = values %params;
31 30 100 100     98 if (@_ > 2 && (grep { ref && ref eq 'CODE' } @values) == @values) {
  20 100       83  
32 1         124 Carp::croak "mutiple trigger registration in one add_trigger() call is deprecated.";
33             }
34              
35 29 100 100     192 if ($#_ == 1 && ref($_[1]) eq 'CODE') {
36 20         55 @_ = (name => $_[0], callback => $_[1]);
37             }
38              
39 29         106 my %args = ( name => undef, callback => undef, abortable => undef, @_ );
40 29         37 my $when = $args{'name'};
41 29         36 my $code = $args{'callback'};
42 29         27 my $abortable = $args{'abortable'};
43 29         56 __validate_triggerpoint( $proto, $when );
44 28 100       441 Carp::croak('add_trigger() needs coderef') unless ref($code) eq 'CODE';
45 26         24 push @{ $triggers->{$when} }, [ $code, $abortable ];
  26         82  
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       72 %Fetch_All_Triggers_Cache = () unless ref $proto;
51              
52 26         129 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         8 return $result_store->{'_class_trigger_results'};
60             }
61              
62             sub call_trigger {
63 37     37 1 225 my $self = shift;
64 37         40 my $when = shift;
65              
66 37         33 my @return;
67              
68 37 100       70 my $result_store = ref($self) ? $self : ${Class::Trigger::_trigger_results}->{$self};
69              
70 37         93 $result_store->{'_class_trigger_results'} = [];
71              
72 37 100       123 if (my @triggers = __fetch_all_triggers($self, $when)) { # any triggers?
73 31         40 for my $trigger (@triggers) {
74 51         118 my @return = $trigger->[0]->($self, @_);
75 51         57 push @{$result_store->{'_class_trigger_results'}}, \@return;
  51         79  
76 51 100 100     168 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         16 __validate_triggerpoint($self, $when);
84             }
85              
86 35         33 return scalar @{$result_store->{'_class_trigger_results'}};
  35         85  
87             }
88              
89             sub __fetch_all_triggers {
90 43     43   63 my ($obj, $when, $list, $order, $nocache) = @_;
91 43 100       85 $nocache = 0 unless defined $nocache;
92 43   66     102 my $class = ref $obj || $obj;
93 43         38 my $return;
94 43 100       67 my $when_key = defined $when ? $when : '';
95            
96 43 100       79 unless ($nocache) {
97             return __cached_triggers($obj, $when)
98 37 100       96 if $Fetch_All_Triggers_Cache{$class}{$when_key};
99             }
100            
101 36 100       62 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         33 $list = {};
108 30         39 $order = [];
109 30         29 $return = 1;
110             }
111 10     10   47 no strict 'refs';
  10         10  
  10         3811  
112 36         29 my @classes = @{$class . '::ISA'};
  36         142  
113 36         49 push @classes, $class;
114 36         50 foreach my $c (@classes) {
115 42 100       84 next if $list->{$c};
116             # if (UNIVERSAL::can($c, 'call_trigger')) {
117 36 50       204 if ($c->can('call_trigger')) {
118 36         63 $list->{$c} = [];
119 36 100       108 __fetch_all_triggers($c, $when, $list, $order, 1)
120             unless $c eq $class;
121 36 100 66     137 if (defined $when && $Triggers{$c}{$when}) {
122 29         41 push @$order, $c;
123 29         49 $list->{$c} = $Triggers{$c}{$when};
124             }
125             }
126             }
127 36 100       101 if ($return) {
128 30         28 my @triggers;
129 30         38 foreach my $class (@$order) {
130 29         40 push @triggers, @{ $list->{$class} };
  29         45  
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         49 $Fetch_All_Triggers_Cache{$class}{$when_key} = \@triggers;
137              
138 30         61 return __cached_triggers($obj, $when);
139             }
140             }
141              
142              
143             sub __cached_triggers {
144 37     37   42 my($proto, $when) = @_;
145 37   66     78 my $class = ref $proto || $proto;
146            
147 37   100     92 return @{ $Fetch_All_Triggers_Cache{$class}{$when || ''} },
148 37         27 @{ __object_triggers($proto, $when) };
  37         58  
149             }
150              
151              
152             sub __object_triggers {
153 37     37   38 my($obj, $when) = @_;
154            
155 37 100 100     172 return [] unless ref $obj && defined $when;
156 35   100     260 return $obj->{__triggers}{$when} || [];
157             }
158              
159              
160             sub __validate_triggerpoint {
161 35 100 66 35   154 return unless my $points = $TriggerPoints{ref $_[0] || $_[0]};
162 5         7 my ($self, $when) = @_;
163             Carp::croak("$when is not valid triggerpoint for ".(ref($self) ? ref($self) : $self))
164 5 100       257 unless $points->{$when};
    100          
165             }
166              
167             sub __fetch_triggers {
168 30     30   47 my ($obj, $proto) = @_;
169             # check object based triggers first
170 30 100 100     185 return ref $obj ? $obj->{__triggers} ||= {} : $Triggers{$obj} ||= {};
      100        
171             }
172              
173             1;
174             __END__