File Coverage

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


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