File Coverage

blib/lib/Catalyst/Continuation.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package Catalyst::Continuation;
4 1     1   474 use Moose;
  0            
  0            
5              
6             use strict;
7             use warnings;
8              
9             use Storable ();
10             use Carp qw/croak/;
11              
12             use overload '""' => "uri";
13              
14             use Data::Visitor::Callback;
15              
16             sub Catalyst::Continuation::SerializedAction::new {
17             my ( $class, $action ) = @_;
18             bless \( $action->reverse ), $class;
19             }
20              
21             has id => (
22             isa => "Str",
23             is => "ro",
24             );
25              
26             has c => (
27             isa => "Catalyst", # FIXME Catalyst::Context
28             is => "ro",
29             weak_ref => 1,
30             );
31              
32             has saved_in_store => (
33             isa => "Bool",
34             is => "rw",
35             default => 0,
36             );
37              
38             has forward_to_caller => (
39             isa => "Str",
40             is => "rw",
41             lazy => 1,
42             default => "",
43             );
44              
45             has method => (
46             isa => "Str",
47             is => "rw",
48             default => "forward",
49             );
50              
51             has auto_delete => (
52             isa => "Bool",
53             is => "rw",
54             default => 1,
55             );
56              
57             has caller => (
58             is => "ro",
59             );
60              
61             # this is what we are forwarding to, simply the args to ->forward
62             has forward => ( isa => "ArrayRef", is => "ro" );
63              
64             has stash => ( isa => "HashRef", is => "ro" );
65             has action => ( is => "ro" );
66             has namespace => ( isa => "Str", is => "ro" );
67             has state => ( is => "ro" );
68             has request_arguments => ( isa => "ArrayRef", is => "ro" );
69             has request_action => ( isa => "Str", is => "ro" );
70             has request_path => ( isa => "Str", is => "ro" );
71             has request_match => ( isa => "Str", is => "ro" );
72             has request_parameters => ( isa => "HashRef", is => "ro" );
73              
74             my %unsaved_attrs = map { $_ => undef } qw/id c saved_in_store/;
75             my %meta_attrs = map { $_ => undef } qw/forward_to_caller method auto_delete/;
76              
77             sub new {
78             my ( $class, %attrs ) = @_;
79              
80             croak 'You must provide something to forward to'
81             unless exists $attrs{forward};
82             croak 'You must provide the $c object' unless my $c = $attrs{c};
83              
84             my $v = Data::Visitor::Callback->new(
85             'Catalyst::Action' => sub {
86             Catalyst::Continuation::SerializedAction->new( $_ );
87             },
88             );
89              
90             %attrs = (
91             %attrs,
92             id => $c->generate_continuation_id,
93             action => $v->visit( $c->action ),
94             caller => $v->visit( $c->stack->[-1] ),
95             );
96              
97             # initialize all the "dumb" fields
98             foreach my $attr (
99             grep { not exists $attrs{$_} }
100             grep { not exists $meta_attrs{$_} }
101             grep { not exists $unsaved_attrs{$_} }
102             keys %{ $class->meta->get_attribute_map }
103             )
104             {
105             my $value = $c;
106             my @chain = split '_', $attr;
107             while (@chain) {
108             my $meth = shift @chain;
109             $value = $value->$meth;
110             }
111              
112             $attrs{$attr} = ref($value) ? Storable::dclone($value) : $value;
113             }
114              
115             $class->SUPER::new(%attrs);
116             }
117              
118             sub new_from_store {
119             my ( $class, $c, $id ) = @_;
120             my $fields = Storable::dclone( $c->get_continuation($id) || return );
121             $class->SUPER::new(
122             forward_to_caller => "forward",
123             %$fields,
124             id => $id,
125             c => $c,
126             );
127             }
128              
129             sub save_in_store {
130             my $self = shift;
131              
132             unless ( $self->saved_in_store ) {
133             $self->c->set_continuation( $self->id => $self->as_hashref );
134             $self->saved_in_store(1);
135             }
136             }
137              
138             sub delete_from_store {
139             my $self = shift;
140              
141             $self->saved_in_store(0);
142             $self->c->delete_continuation( $self->id );
143             }
144              
145             sub as_hashref {
146             my $self = shift;
147              
148             return {
149             map { $_ => $self->$_ }
150             grep { exists $self->{$_} }
151             grep { not exists $unsaved_attrs{$_} }
152             keys %{ $self->meta->get_attribute_map },
153             };
154             }
155              
156             sub as_deep_hashref {
157             my $self = shift;
158             my $localized = $self->as_hashref;
159             my $ret = {};
160              
161             foreach my $key ( grep { not exists $meta_attrs{$_} } keys %$localized ) {
162             my @chain = split '_', $key;
163             my $last = pop @chain;
164             my $value = $ret;
165              
166             while (@chain) {
167             $value = ( $value->{ shift @chain } ||= {} );
168             }
169              
170             $value->{$last} = delete $localized->{$key};
171             }
172              
173             my $d = $self->c->dispatcher;
174              
175             my $v = Data::Visitor::Callback->new(
176             "Catalyst::Continuation::SerializedAction" => sub {
177             $d->get_action_by_path( $$_ );
178             }
179             );
180              
181             $v->visit( $ret );
182             }
183              
184             sub uri {
185             my $self = shift;
186             $self->save_in_store;
187             $self->c->_uri_to_cont( $self );
188             }
189              
190             sub execute {
191             my $self = shift;
192              
193             my $c = $self->c;
194              
195             my $localized = $self->as_deep_hashref;
196              
197             my $caller = delete $localized->{caller};
198             my $forward = delete $localized->{forward};
199              
200             $localized->{stack} = [ @{ $c->stack }, $caller ];
201              
202             my $stats_info = $c->_stats_start_execute( $caller );
203             if ( my $node = $stats_info->{node} ) {
204             $node->getNodeValue->{comment} = " (continuation)";
205             }
206              
207             my $ret = $c->_localize_fields(
208             $localized,
209             sub {
210             $c->forward(@$forward);
211             if ( my $meth = $self->forward_to_caller ) {
212             $meth = "forward" unless $c->can($meth);
213             $c->$meth( "/" . $caller->reverse );
214             }
215             }
216             );
217              
218             $self->delete_from_store if $self->auto_delete;
219              
220             $c->_stats_finish_execute( $stats_info );
221              
222             return $ret;
223             }
224              
225             __PACKAGE__;
226              
227             __END__
228              
229             =pod
230              
231             =head1 NAME
232              
233             Catalyst::Continuation - Pseudo-continuation objects for Catalyst
234              
235             =head1 SYNOPSIS
236              
237             $c->cont("foo");
238              
239             =head1 DESCRIPTION
240              
241             This object is returned by the L<Catalyst::Plugin::Continuation/cont> method.
242             It captures the current state of execution within the context object as best as
243             it can, within the limitations perl and Perl.
244              
245             Please do not try to construct it directly.
246              
247             =head1 METHODS
248              
249             =head2 new %attrs
250              
251             Create a continuation
252              
253             =head2 new_from_store
254              
255             Restore a continuation. Takes a value as returned by C<as_hashref>. Requires
256             the C<$c> object to be specified.
257              
258             =head2 as_hashref
259              
260             Returns a hash ref that can be serialized. This is required for serialization
261             due to the fact that C<$c> is different between requests.
262              
263             =head2 as_deep_hashref
264              
265             Create the structure that shadows C<$c>'s fields. Suitable for passing to C<_localize_fields>.
266              
267             =head2 id
268              
269             The ID of this continuation.
270              
271             =head2 execute
272              
273             Invoke the continuation, localizing the whole $c object to what it was when the
274             continuation was created, and calling the ->forward.
275              
276             See also C<forward_to_caller> for what happens once this is done.
277              
278             =head2 uri
279              
280             This method will return a URI that will cause the continuation to be reinvoked.
281              
282             It automatically calls C<save_in_store>, in order to allow this continuation to
283             be invoked from different requests.
284              
285             =head2 save_in_store
286              
287             This method causes the continuation to ask the C<$c> object to save it
288             somewhere. This is handled by L<Catalyst::Plugin::Continuation>, and any
289             overrides that may have been added.
290              
291             =head2 delete_from_store
292              
293             The inverse of C<saved_in_store>.
294              
295             =head2 method
296              
297             Which method to invoke on C<$c> as the continuation.
298              
299             Defaults to C<forward>.
300              
301             =head2 forward
302              
303             The argumetns to pass to C<method>. This is an array reference, typically
304             containing the string of the path to forward to.
305              
306             =head2 forward_to_caller
307              
308             Whether or not to ->forward back to the action that created the continuation.
309             This defaults to true when a continuation is being restored from storage in a
310             new request, and defaults to false otherwise.
311              
312             When false nothing happens. When true defaults to a regular forward. When any
313             string, invokes that method.
314              
315             =head2 auto_delete
316              
317             Whether or not a continuation should delete itself after being executed.
318              
319             Defaults to true.
320              
321             =head2 meta
322              
323             This is thte L<Moose> meta class instance for the continuation's class.
324              
325             =head2 saved_in_store
326              
327             =head2 c
328              
329             These two fields are used internally to integrate the continuation with the current request.
330              
331             =head1 SAVED FIELDS
332              
333             These paramters contain the collected data. You may use this as a reference to
334             find out what is saved/restored when a continuation is created/executed.
335              
336             =over 4
337              
338             =item stash
339              
340             =item action
341              
342             =item namespace
343              
344             =item request_parameters
345              
346             =item request_arguments
347              
348             =item request_path
349              
350             =item request_match
351              
352             =item request_action
353              
354             =item state
355              
356             These correspond to the methods/fields of $c.
357              
358             =item caller
359              
360             The last element on C<< $c->stack >>
361              
362             =back
363              
364             =cut
365              
366