File Coverage

blib/lib/Catalyst/ActionChain.pm
Criterion Covered Total %
statement 79 88 89.7
branch 28 42 66.6
condition 3 3 100.0
subroutine 11 12 91.6
pod 7 7 100.0
total 128 152 84.2


line stmt bran cond sub pod time code
1             package Catalyst::ActionChain;
2              
3 103     103   2197 use Moose;
  103         298  
  103         726  
4             extends qw(Catalyst::Action);
5              
6             has chain => (is => 'rw');
7             has _current_chain_actions => (is=>'rw', init_arg=>undef, predicate=>'_has_current_chain_actions');
8             has _chain_last_action => (is=>'rw', init_arg=>undef, predicate=>'_has_chain_last_action', clearer=>'_clear_chain_last_action');
9             has _chain_captures => (is=>'rw', init_arg=>undef);
10             has _chain_original_args => (is=>'rw', init_arg=>undef, clearer=>'_clear_chain_original_args');
11             has _chain_next_args => (is=>'rw', init_arg=>undef, predicate=>'_has_chain_next_args', clearer=>'_clear_chain_next_args');
12             has _context => (is => 'rw', weak_ref => 1);
13              
14 103     103   699592 no Moose;
  103         426  
  103         923  
15              
16             =head1 NAME
17              
18             Catalyst::ActionChain - Chain of Catalyst Actions
19              
20             =head1 SYNOPSIS
21              
22             See L<Catalyst::Manual::Intro> for more info about Chained actions.
23              
24             =head1 DESCRIPTION
25              
26             This class represents a chain of Catalyst Actions. It behaves exactly like
27             the action at the *end* of the chain except on dispatch it will execute all
28             the actions in the chain in order.
29              
30             =cut
31              
32             sub dispatch {
33 247     247 1 952 my ( $self, $c ) = @_;
34 247 50       577 my @captures = @{$c->req->captures||[]};
  247         783  
35 247         600 my @chain = @{ $self->chain };
  247         7296  
36 247         671 my $last = pop(@chain);
37              
38 247         8146 $self->_current_chain_actions(\@chain);
39 247         7800 $self->_chain_last_action($last);
40 247         7743 $self->_chain_captures(\@captures);
41 247         6042 $self->_chain_original_args($c->request->{arguments});
42 247         7549 $self->_context($c);
43 247         1028 $self->_dispatch_chain_actions($c);
44             }
45              
46             sub next {
47 7     7 1 20 my ($self, @args) = @_;
48 7         195 my $ctx = $self->_context;
49              
50 7 50       256 if($self->_has_chain_last_action) {
51 7 50       220 @args ? $self->_chain_next_args(\@args) : $self->_chain_next_args([]);
52 7         35 $self->_dispatch_chain_actions($ctx);
53             } else {
54 0 0       0 $ctx->action->chain->[-1]->next($ctx, @args) if $ctx->action->chain->[-1]->can('next');
55             }
56              
57 7         167 return $ctx->state;
58              
59             }
60              
61             sub _dispatch_chain_actions {
62 254     254   732 my ($self, $c) = @_;
63 254 50       531 while( @{$self->_current_chain_actions||[]}) {
  548         18476  
64 299         1312 $self->_dispatch_chain_action($c);
65 298 100       1550 return if $self->_abort_needed($c);
66             }
67 249 100       9585 if($self->_has_chain_last_action) {
68 242         7310 $c->request->{arguments} = $self->_chain_original_args;
69 242         9613 $self->_clear_chain_original_args;
70 242 100       8314 unshift @{$c->request->{arguments}}, @{ $self->_chain_next_args} if $self->_has_chain_next_args;
  4         94  
  4         113  
71 242         8711 $self->_clear_chain_next_args;
72 242         7877 my $last_action = $self->_chain_last_action;
73 242         9397 $self->_clear_chain_last_action;
74 242         1036 $last_action->dispatch($c);
75             }
76             }
77              
78             sub _dispatch_chain_action {
79 299     299   782 my ($self, $c) = @_;
80 299 50       571 my ($action, @remaining_actions) = @{ $self->_current_chain_actions||[] };
  299         9008  
81 299         9396 $self->_current_chain_actions(\@remaining_actions);
82 299         716 my @args;
83 299 100       8492 if (my $cap = $action->number_of_captures) {
84 180 50       475 @args = splice(@{ $self->_chain_captures||[] }, 0, $cap);
  180         5657  
85             }
86 299 100       11503 unshift @args, @{ $self->_chain_next_args} if $self->_has_chain_next_args;
  3         90  
87 299         11334 $self->_clear_chain_next_args;
88 299         7456 local $c->request->{arguments} = \@args;
89 299         1542 $action->dispatch( $c );
90             }
91              
92             sub _abort_needed {
93 298     298   897 my ($self, $c) = @_;
94 298 100       1469 my $abort = defined($c->config->{abort_chain_on_error_fix}) ? $c->config->{abort_chain_on_error_fix} : 1;
95 298 100 100     1399 return 1 if ($c->has_errors && $abort);
96             }
97              
98             sub from_chain {
99 342     342 1 1059 my ( $self, $actions ) = @_;
100 342         744 my $final = $actions->[-1];
101 342         13976 return $self->new({ %$final, chain => $actions });
102             }
103              
104             sub number_of_captures {
105 67     67 1 159 my ( $self ) = @_;
106 67         2014 my $chain = $self->chain;
107 67         142 my $captures = 0;
108              
109 67         2007 $captures += $_->number_of_captures for @$chain;
110 67         186 return $captures;
111             }
112              
113             sub match_captures {
114 0     0 1 0 my ($self, $c, $captures) = @_;
115 0 0       0 my @captures = @{$captures||[]};
  0         0  
116              
117 0         0 foreach my $link(@{$self->chain}) {
  0         0  
118 0         0 my @local_captures = splice @captures,0,$link->number_of_captures;
119 0 0       0 return unless $link->match_captures($c, \@local_captures);
120             }
121 0         0 return 1;
122             }
123             sub match_captures_constraints {
124 47     47 1 113 my ($self, $c, $captures) = @_;
125 47 50       79 my @captures = @{$captures||[]};
  47         176  
126              
127 47         660 foreach my $link(@{$self->chain}) {
  47         1321  
128 116         3129 my @local_captures = splice @captures,0,$link->number_of_captures;
129 116 100       4371 next unless $link->has_captures_constraints;
130 13 100       50 return unless $link->match_captures_constraints($c, \@local_captures);
131             }
132 44         183 return 1;
133             }
134              
135             # the scheme defined at the end of the chain is the one we use
136             # but warn if too many.
137              
138             sub scheme {
139 48     48 1 109 my $self = shift;
140 48         86 my @chain = @{ $self->chain };
  48         1394  
141             my ($scheme, @more) = map {
142 48 100       145 exists $_->attributes->{Scheme} ? $_->attributes->{Scheme}[0] : ();
  117         3086  
143             } reverse @chain;
144              
145 48 50       143 warn "$self is a chain with two many Scheme attributes (only one is allowed)"
146             if @more;
147              
148 48         225 return $scheme;
149             }
150              
151             __PACKAGE__->meta->make_immutable;
152             1;
153              
154             __END__
155              
156             =head1 METHODS
157              
158             =head2 chain
159              
160             Accessor for the action chain; will be an arrayref of the Catalyst::Action
161             objects encapsulated by this chain.
162              
163             =head2 dispatch( $c )
164              
165             Dispatch this action chain against a context; will dispatch the encapsulated
166             actions in order.
167              
168             =head2 from_chain( \@actions )
169              
170             Takes a list of Catalyst::Action objects and constructs and returns a
171             Catalyst::ActionChain object representing a chain of these actions
172              
173             =head2 number_of_captures
174              
175             Returns the total number of captures for the entire chain of actions.
176              
177             =head2 match_captures
178              
179             Match all the captures that this chain encloses, if any.
180              
181             =head2 scheme
182              
183             Any defined scheme for the actionchain
184              
185             =head2 next ( @args)
186              
187             Dispatches to the next action in the chain immediately, suspending any remaining code in the action.
188             If there are no more actions in the chain, this is basically a no-op. When the last action in the chain
189             returns, we will return to the last action that called next and continue processing that action's
190             code exactly where it was left off. If more than one action in the chain called C<next> then we proceed
191             back up the chain stack in reverse order of calls after the last action completes.
192              
193             The return value of C<next> is the return value of the next action in the chain (that is the action that
194             was called with C<next>) or whatever $c->state is set to.
195              
196             Please note that since C<state> is a scalar, you cannot return a list of values from an action chain.
197             If you want to return a list you must return an arrayref or hashref. This limitation is due to
198             longstanding code in L<Catalyst> that is not easily changed without breaking backwards compatibility.
199              
200             You can call C<next> in as many actions in a long chain as you want and the chain will correctly
201             return to the last action that called C<next> based on order of execution. If there are actions inbetween
202             that didn't call C<next>, those will be skipped when proceeding back up the call stack. When we've completed
203             walking back up the action call stack the dispatcher will then return to normal processing order (for example
204             processing any C<end> action present).
205              
206             Any arguments you pass to C<next> will be passed to the next action in the chain as C<< $c->request->arguments >>.
207             You can pass more than one argument. All arguments passed via C<next> will be added into the argument list prior
208             to any CaptureArgs or Args that the action itself defines.
209              
210             Example:
211              
212             sub action_a :Chained('/') CaptureArgs(0) {
213             my ($self, $ctx) = @_;
214             my $abc = $c->action->next('a'); # $abc = "abc";
215             }
216              
217             sub action_b :Chained('action_a') CaptureArgs(0) {
218             my ($self, $ctx, $a) = @_;
219             my $abc = $c->action->next("${a}b");
220             return $abc;
221             }
222              
223             sub action_c :Chained('action_b') Args(0) {
224             my ($self, $ctx, $ab) = @_;
225             return "${ab}c";
226             }
227              
228             =head2 meta
229              
230             Provided by Moose
231              
232             =head1 AUTHORS
233              
234             Catalyst Contributors, see Catalyst.pm
235              
236             =head1 COPYRIGHT
237              
238             This library is free software. You can redistribute it and/or modify it under
239             the same terms as Perl itself.
240              
241             =cut