File Coverage

lib/Aspect/AdviceContext.pm
Criterion Covered Total %
statement 60 63 95.2
branch 14 22 63.6
condition 3 6 50.0
subroutine 15 15 100.0
pod 0 11 0.0
total 92 117 78.6


line stmt bran cond sub pod time code
1             package Aspect::AdviceContext;
2              
3 1     1   6 use strict;
  1         2  
  1         51  
4 1     1   25 use warnings;
  1         1  
  1         45  
5 1     1   8 use Carp;
  1         1  
  1         1187  
6              
7             sub new {
8 17     17 0 8152 my ($class, %spec) = @_;
9 17 50       47 croak "cannot create with no sub_name" unless $spec{sub_name};
10 95         173 my $self = bless {
11 17         48 (map { $_ => $spec{$_} } keys %spec),
12             proceed => 1,
13             }, $class;
14 17         65 return $self;
15             }
16              
17             sub run_original {
18 1     1 0 2 my $self = shift;
19 1         13 my $original = $self->original;
20 1         5 my @params = $self->params;
21 1         1 my $return_value;
22 1 50       4 if (wantarray)
23 0         0 { $return_value = [$original->(@params)] }
24             else
25 1         4 { $return_value = $original->(@params) }
26 1         7 $self->return_value($return_value);
27 1         2 return $self->return_value;
28             }
29              
30             sub proceed {
31 9     9 0 16 my ($self, $value) = @_;
32 9 50       50 return $self->get_value('proceed') if @_ == 1;
33 0         0 $self->{proceed} = $value;
34 0         0 return $self;
35             }
36              
37             sub append_param {
38 3     3 0 11 my ($self, @param) = @_;
39 3         4 push @{$self->params}, @param;
  3         7  
40 3         7 return $self;
41             }
42              
43 1     1 0 5 sub append_params { shift->append_param(@_) }
44              
45             sub params {
46 18     18 0 54 my ($self, @value) = @_;
47 18 100       73 return $self->get_value('params') if @_ == 1;
48 1         3 $self->{params} = \@value;
49 1         4 return $self;
50             }
51              
52 7     7 0 41 sub self { shift->{params}->[0] }
53              
54             sub package_name {
55 1     1 0 3 my $self = shift;
56 1         7 my $name = $self->sub_name;
57 1 50       6 return '' unless $name =~ /::/;
58 1         5 $name =~ s/::[^:]+$//;
59 1         6 return $name;
60             }
61              
62             sub short_sub_name {
63 1     1 0 3 my $self = shift;
64 1         7 my $name = $self->sub_name;
65 1 50       8 return $name unless $name =~ /::/;
66 1         6 $name =~ /::([^:]+)$/;
67 1         6 return $1;
68             }
69              
70             sub return_value {
71 23     23 0 464 my ($self, $value) = @_;
72 23 100       56 if (@_ == 1) {
73 13         27 my $return_value = $self->get_value('return_value');
74 13 50 33     120 return wantarray && ref $return_value eq 'ARRAY'?
75             @$return_value: $return_value;
76             }
77 10         36 $self->{return_value} = $value;
78 10         20 $self->{proceed} = 0;
79 10         22 return $self;
80             }
81              
82             sub AUTOLOAD {
83 10     10   1987 my $self = shift;
84 10         16 my $key = our $AUTOLOAD;
85 10 50       34 return if $key =~ /DESTROY$/;
86 10         77 $key =~ s/^.*:://;
87 10         31 return $self->get_value($key);
88             }
89              
90             sub get_value {
91 49     49 0 60 my ($self, $key) = @_;
92 49 50       144 croak "Key does not exist: [$key]" unless exists $self->{$key};
93 49         67 my $value = $self->{$key};
94 49 100 66     277 return wantarray && ref $value eq 'ARRAY'? @$value: $value;
95             }
96              
97             1;
98              
99             =head1 NAME
100              
101             Aspect::AdviceContext - a pointcut context for advice code
102              
103             =head1 SYNOPSIS
104              
105             $pointcut = call qr/^Person::[gs]et_/ & cflow company => qr/^Company::/;
106              
107             # using in 'before' advice code
108             before {
109             my $context = shift; # context is only param to advice code
110             print $context->type; # 'before': advice type: before/after
111             print $context->pointcut; # $pointcut: the pointcut for this advice
112             print $context->sub_name; # package + sub name of matched sub
113             print $context->package_name; # 'Person': package name of matched sub
114             print $context->short_sub_name; # sub name of matched sub
115             print $context->self; # 1st parameter to matched sub
116             print $context->params->[1]; # 2nd parameter to matched sub
117             $context->append_param($rdbms); # append param to matched sub
118             $context->append_params($a, $b); # append params to matched sub
119             $context->return_value(4) # don't proceed to matched sub, return 4
120             $context->original->(x => 3); # call matched sub, don't proceed
121             $context->proceed(1); # do proceed to matched sub after all
122             print $context->company->name; # access cflow pointcut advice context
123             } $pointcut;
124              
125             =head1 DESCRIPTION
126              
127             Advice code is called when the advice pointcut is matched. In this code,
128             there is always a need to access information about the context of the
129             advice. Information like: what is the actual sub name matched? What are
130             the parameters in this call that we matched? Sometimes you want to change
131             the context for the matched sub: append a parameter, or even stop the
132             matched sub from being called.
133              
134             You do all these things through the C. It is the only
135             parameter provided to the advice code. It provides all the information
136             required about the match context, and allows you to change the behavior
137             of the matched sub.
138              
139             Note that modifying parameters through the context, in the code of an
140             I advice, will have no effect, since the matched sub has already
141             been called.
142              
143             =head1 CFLOW CONTEXT
144              
145             If the pointcut of an advice is composed of at least one
146             L, advice code may require not only the context
147             of the advice, but also the context of the cflows. This is required if
148             you want to find out, for example, what is the name of the sub that
149             matched a cflow. E.g. for the synopsis example above, what method of
150             C started the chain of calls that eventually reached the get/set
151             on C?
152              
153             You can access cflow context in the synopsis above, by calling:
154              
155             $context->company;
156              
157             You get it from the main advice context, by calling a method named after
158             the context key used in the cflow spec. In the synopsis pointcut
159             definition, the cflow part was:
160              
161             cflow company => qr/^Company::/
162             ^^^^^^^
163              
164             An C will be created for the cflow, and you can access it
165             using the key C.
166              
167             =head1 EXAMPLES
168              
169             Print parameters to matched sub:
170              
171             before { my $c = shift; print join(',', $c->params) } $pointcut;
172              
173             Append a parameter:
174              
175             before { shift->append_param('extra-param') } $pointcut;
176              
177             Don't proceed to matched sub, return 4 instead:
178              
179             before { shift->return_value(4) } $pointcut;
180              
181             Call matched sub again, and again, until it returns something defined:
182              
183             after {
184             my $context = shift;
185             my $return = $context->return_value;
186             while (!defined $return)
187             { $return = $context->original($context->params) }
188             $context->return_value($return);
189             } $pointcut;
190              
191             Print the name of the C object that started the chain of calls
192             that eventually reached the get/set on C:
193              
194             before { print shift->company->name } $pointcut;
195              
196             =head1 SEE ALSO
197              
198             See the L pod for a guide to the Aspect module.
199              
200             You can find examples of using the C in any advice code.
201             The aspect library for example (e.g. L).
202              
203             L creates the main C, and
204             C creates contexts for each matched call flow.
205              
206             =cut
207