File Coverage

blib/lib/Aspect/Advice/Around.pm
Criterion Covered Total %
statement 65 68 95.5
branch 20 24 83.3
condition n/a
subroutine 13 13 100.0
pod n/a
total 98 105 93.3


line stmt bran cond sub pod time code
1             package Aspect::Advice::Around;
2              
3 26     26   141 use strict;
  26         41  
  26         829  
4              
5             # Added by eilara as hack around caller() core dump
6             # NOTE: Now we've switched to Sub::Uplevel can this be removed? --ADAMK
7 26     26   142 use Carp::Heavy ();
  26         47  
  26         348  
8 26     26   127 use Carp ();
  26         98  
  26         410  
9 26     26   161 use Sub::Uplevel ();
  26         40  
  26         359  
10 26     26   117 use Aspect::Hook ();
  26         38  
  26         348  
11 26     26   142 use Aspect::Advice ();
  26         66  
  26         347  
12 26     26   130 use Aspect::Point ();
  26         43  
  26         13968  
13              
14             our $VERSION = '1.04';
15             our @ISA = 'Aspect::Advice';
16              
17             sub _install {
18 44     44   92 my $self = shift;
19 44         120 my $pointcut = $self->pointcut;
20 44         218 my $code = $self->code;
21 44         258 my $lexical = $self->lexical;
22              
23             # Get the curried version of the pointcut we will use for the
24             # runtime checks instead of the original.
25             # Because $MATCH_RUN is used in boolean conditionals, if there
26             # is nothing to do the compiler will optimise away the code entirely.
27 44         212 my $curried = $pointcut->curry_runtime;
28 44 100       226 my $compiled = $curried ? $curried->compiled_runtime : undef;
29 44 100       146 my $MATCH_RUN = $compiled ? 'do { local $_ = $Aspect::POINT; $compiled->() }' : 1;
30              
31             # When an aspect falls out of scope, we don't attempt to remove
32             # the generated hook code, because it might (for reasons potentially
33             # outside our control) have been recursively hooked several times
34             # by both Aspect and other modules.
35             # Instead, we store an "out of scope" flag that is used to shortcut
36             # past the hook as quickely as possible.
37             # This flag is shared between all the generated hooks for each
38             # installed Aspect.
39             # If the advice is going to last lexical then we don't need to
40             # check or use the $out_of_scope variable.
41 44         78 my $out_of_scope = undef;
42 44 100       142 my $MATCH_DISABLED = $lexical ? '$out_of_scope' : '0';
43              
44             # Find all pointcuts that are statically matched
45             # wrap the method with advice code and install the wrapper
46 44         314 foreach my $name ( $pointcut->match_all ) {
47 49         126 my $NAME = $name; # For completeness
48              
49 26     26   163 no strict 'refs';
  26         68  
  26         2981  
50 49         265 my $original = *$name{CODE};
51 49 50       206 unless ( $original ) {
52 0         0 Carp::croak("Can't wrap non-existent subroutine ", $name);
53             }
54              
55             # Any way to set prototypes other than eval?
56 49         113 my $PROTOTYPE = prototype($original);
57 49 100       200 $PROTOTYPE = defined($PROTOTYPE) ? "($PROTOTYPE)" : '';
58              
59             # Generate the new function
60 26     26   138 no warnings 'redefine';
  26         57  
  26         9427  
61 49 50   23   15495 eval <<"END_PERL"; die $@ if $@;
  49 100       202  
  23 100       7352  
  19 100       34  
  19         201  
  19         34  
  19         30  
  19         116  
  10         28  
  10         32  
  5         29  
  10         191  
  7         2956  
  12         72  
62             package Aspect::Hook;
63              
64             *$NAME = sub $PROTOTYPE {
65             # Is this a lexically scoped hook that has finished
66             goto &\$original if $MATCH_DISABLED;
67              
68             # Apply any runtime-specific context checks
69             my \$wantarray = wantarray;
70             local \$Aspect::POINT = bless {
71             type => 'around',
72             pointcut => \$pointcut,
73             original => \$original,
74             sub_name => \$name,
75             wantarray => \$wantarray,
76             args => \\\@_,
77             return_value => \$wantarray ? [ ] : undef,
78             topic => \\\$_,
79             }, 'Aspect::Point';
80              
81             # Can we shortcut the advice code
82             goto &\$original unless $MATCH_RUN;
83              
84             # Run the advice code
85             SCOPE: {
86             local \$_ = \$Aspect::POINT;
87             Sub::Uplevel::uplevel(
88             1, \$code, \$Aspect::POINT,
89             );
90             }
91              
92             # Return the result
93             return \@{\$Aspect::POINT->{return_value}} if \$wantarray;
94             return \$Aspect::POINT->{return_value};
95             };
96             END_PERL
97 49         297 $self->{installed}++;
98             }
99              
100             # If this will run lexical we don't need a descoping hook
101 44 100       251 return unless $lexical;
102              
103             # Return the lexical descoping hook.
104             # This MUST be stored and run at DESTROY-time by the
105             # parent object calling _install. This is less bullet-proof
106             # than the DESTROY-time self-executing blessed coderef
107 28     28   285 return sub { $out_of_scope = 1 };
  28         307  
108             }
109              
110             # Check for pointcut usage not supported by the advice type
111             sub _validate {
112 44     44   124 my $self = shift;
113 44         295 my $pointcut = $self->pointcut;
114              
115             # Pointcuts using "throwing" are irrelevant in before advice
116 44 50       345 if ( $pointcut->match_contains('Aspect::Pointcut::Throwing') ) {
117 0         0 return 'The pointcut throwing is illegal when used by around advice';
118             }
119              
120             # Pointcuts using "throwing" are irrelevant in before advice
121 44 50       193 if ( $pointcut->match_contains('Aspect::Pointcut::Returning') ) {
122 0         0 return 'The pointcut returning is illegal when used by around advice';
123             }
124              
125 44         312 $self->SUPER::_validate(@_);
126             }
127              
128             1;
129              
130             =pod
131              
132             =head1 NAME
133              
134             Aspect::Advice::Around - Execute code both before and after a function
135              
136             =head1 SYNOPSIS
137              
138             use Aspect;
139            
140             around {
141             # Trace all calls to your module
142             print STDERR "Called my function " . $_->sub_name . "\n";
143            
144             # Lexically alter a global for this function
145             local $MyModule::MAXSIZE = 1000;
146            
147             # Continue and execute the function
148             $_->run_original;
149            
150             # Suppress exceptions for the call
151             $_->return_value(1) if $_->exception;
152            
153             } call qr/^ MyModule::\w+ $/;
154              
155             =head1 DESCRIPTION
156              
157             The C advice type is used to execute code on either side of a
158             function, allowing deep and precise control of how the function will be
159             called when none of the other advice types are good enough.
160              
161             Using C advice is also critical if you want to lexically alter
162             the environment in which the call will be made (as in the example above
163             where a global variable is temporarily changed).
164              
165             This advice type is also the most computationally expensive to run, so if
166             your problem can be solved with the use of a different advice type,
167             particularly C, you should use that instead.
168              
169             Please note that unlike the other advice types, your code in C is
170             required to trigger the execution of the target function yourself with the
171             C method. If you do not C and also do not set either a
172             C or C, the function call will return C
173             in scalar context or the null list C<()> in list context.
174              
175             =head1 AUTHORS
176              
177             Adam Kennedy Eadamk@cpan.orgE
178              
179             =head1 COPYRIGHT AND LICENSE
180              
181             Copyright 2010 - 2013 Adam Kennedy.
182              
183             This library is free software; you can redistribute it and/or modify
184             it under the same terms as Perl itself.
185              
186             =cut