File Coverage

blib/lib/Catalyst/Plugin/Starch.pm
Criterion Covered Total %
statement 86 87 98.8
branch 11 16 68.7
condition 3 5 60.0
subroutine 43 43 100.0
pod 9 10 90.0
total 152 161 94.4


line stmt bran cond sub pod time code
1             package Catalyst::Plugin::Starch;
2 1     1   1054 use 5.010001;
  1         5  
3 1     1   6 use strictures 2;
  1         10  
  1         47  
4             our $VERSION = '0.07';
5              
6             =head1 NAME
7              
8             Catalyst::Plugin::Starch - Catalyst session plugin via Starch.
9              
10             =head1 SYNOPSIS
11              
12             package MyApp;
13            
14             use Catalyst qw(
15             Starch::Cookie
16             Starch
17             );
18            
19             __PACKAGE__->config(
20             'Plugin::Starch' => {
21             cookie_name => 'my_session',
22             store => { class=>'::Memory' },
23             },
24             );
25              
26             =head1 DESCRIPTION
27              
28             Integrates L<Starch> with L<Catalyst> providing a compatible replacement
29             for L<Catalyst::Plugin::Session>.
30              
31             Is is recommended that as part of implementing this module in your site
32             that you also create an in-house unit test using L<Test::Starch>.
33              
34             Note that this plugin is a L<Moose::Role> which means that Catalyst will
35             apply the plugin to the Catalyst object in reverse order than that listed
36             in the C<use Catalyst> stanza. This may not matter for you, but to be safe,
37             declare the C<Starch> plugin B<after> any other Starch plugins or any other
38             plugins that depend on sessions.
39              
40             =head1 CONFIGURATION
41              
42             Configuring Starch is a matter of setting the C<Plugin::Starch> configuration
43             key in your root Catalyst application class:
44              
45             __PACKAGE__->config(
46             'Plugin::Starch' => {
47             store => { class=>'::Memory' },
48             },
49             );
50              
51             In addition to the arguments you would normally pass to L<Starch> you
52             can also pass a C<plugins> argument which will be combined with the plugins
53             from L</default_starch_plugins>.
54              
55             See L<Starch> for more information about configuring Starch.
56              
57             =cut
58              
59 1     1   922 use Starch;
  1         341106  
  1         50  
60 1     1   12 use Types::Standard -types;
  1         3  
  1         12  
61 1     1   4679 use Types::Common::String -types;
  1         25  
  1         13  
62 1     1   1618 use Catalyst::Exception;
  1         2  
  1         46  
63 1     1   7 use Scalar::Util qw( blessed );
  1         2  
  1         73  
64 1     1   8 use Class::Method::Modifiers qw( fresh );
  1         2  
  1         47  
65              
66 1     1   17 use Moose::Role;
  1         3  
  1         11  
67 1     1   6789 use MooseX::ClassAttribute;
  1         91789  
  1         5  
68 1     1   298044 use namespace::clean;
  1         2  
  1         12  
69              
70             =head1 COMPATIBILITY
71              
72             This module is mostly API compliant with L<Catalyst::Plugin::Session>. The way you
73             configure this plugin will be different, but all your code that uses sessions, or
74             other plugins that use sessions, should not need to be changed unless they
75             depend on undocumented features.
76              
77             Everything documented in the L<Catalyst::Plugin::Session/METHODS> section is
78             supported except for:
79              
80             =over
81              
82             =item *
83              
84             The C<flash>, C<clear_flash>, and C<keep_flash> methods are not implemented
85             as its really a terrible idea. If this becomes a big issue for compatibility
86             with existing code and plugins then this may be reconsidered.
87              
88             =item *
89              
90             The C<session_expire_key> method is not supported, but can be if it is deemed
91             a good feature to port.
92              
93             =back
94              
95             Everything in the L<Catalyst::Plugin::Session/INTERNAL METHODS> section is
96             supported except for:
97              
98             =over
99              
100             =item *
101              
102             The
103             C<check_session_plugin_requirements>, C<setup_session>, C<initialize_session_data>,
104             C<validate_session_id>, C<generate_session_id>, C<session_hash_seed>,
105             C<calculate_extended_session_expires>, C<calculate_initial_session_expires>,
106             C<create_session_id_if_needed>, C<delete_session_id>, C<extend_session_expires>,
107             C<extend_session_id>, C<get_session_id>, C<reset_session_expires>,
108             C<set_session_id>, and C<initial_session_expires>
109             methods are not supported. Some of them could be, if a good case for their
110             existence presents itself.
111              
112             =item *
113              
114             The C<setup>, C<prepare_action>, and C<finalize_headers> methods are not altered
115             because they do not need to be.
116              
117             =back
118              
119             The above listed unimplemented methods and attributes will throw an exception
120             if called.
121              
122             =head1 PERFORMANCE
123              
124             Benchmarking L<Catalyst::Plugin::Session> and L<Catalyst::Plugin::Starch>
125             it was found that Starch is 1.5x faster (or, ~65% the run-time). While this
126             is a fairly big improvement, the difference in real-life should be a savings
127             of one or two millisecond per request.
128              
129             Most of this performance gain is made by the fact that Starch does not use
130             L<Moose> and instead it uses L<Moo> which has many run-time performance
131             benefits.
132              
133             =cut
134              
135             foreach my $method (qw(
136             flash clear_flash keep_flash
137             session_expire_key
138             check_session_plugin_requirements setup_session initialize_session_data
139             validate_session_id generate_session_id session_hash_seed
140             calculate_extended_session_expires calculate_initial_session_expires
141             create_session_id_if_needed delete_session_id extend_session_expires
142             extend_session_id get_session_id reset_session_expires
143             set_session_id initial_session_expires
144             )) {
145             fresh $method => sub{
146 1     1   1108 Catalyst::Exception->throw( "The $method method is not implemented by Catalyst::Plugin::Starch" );
        1      
        1      
        1      
        1      
        1      
        1      
        1      
        1      
        1      
        1      
        1      
        1      
        1      
        1      
        1      
        1      
        1      
        1      
        1      
147             };
148             }
149              
150             =head1 ATTRIBUTES
151              
152             =head2 sessionid
153              
154             The ID of the session.
155              
156             =cut
157              
158             has sessionid => (
159             is => 'ro',
160             init_arg => undef,
161             writer => '_set_sessionid',
162             clearer => '_clear_sessionid',
163             predicate => '_has_sessionid',
164             );
165              
166             =head2 session_expires
167              
168             Returns the time when the session will expire (in epoch time). If there
169             is no session then C<0> will be returned.
170              
171             =cut
172              
173             sub session_expires {
174 1     1 1 769 my ($self) = @_;
175 1 50       33 return 0 if !$self->_has_sessionid();
176 1         29 my $session = $self->starch_state();
177 1         52 return $session->modified() + $session->expires();
178             }
179              
180             =head2 session_delete_reason
181              
182             Returns the C<reason> value passsed to L</delete_session>.
183             Two common values are:
184              
185             =over
186              
187             =item *
188              
189             C<address mismatch>
190              
191             =item *
192              
193             C<session expired>
194              
195             =back
196              
197             =cut
198              
199             has session_delete_reason => (
200             is => 'ro',
201             isa => NonEmptySimpleStr,
202             init_arg => undef,
203             writer => '_set_session_delete_reason',
204             clearer => '_clear_session_delete_reason',
205             );
206              
207             =head2 default_starch_plugins
208              
209             This attribute returns the base set plugins that the L</starch>
210             object will be built with. Note that this does not include any
211             additional plugins you specify in the L</CONFIGURATION>.
212              
213             The intention of this attribute is for other Catalyst plugins, such as
214             L<Catalyst::Plugin::Starch::Cookie>, to be able to declare
215             additional Starch plugins by C<around()>ing this and injecting
216             their own plugins into the array ref.
217              
218             =cut
219              
220             sub default_starch_plugins {
221 1     1 1 6 return [];
222             }
223              
224             =head2 starch_state
225              
226             This holds the underlying L<Starch::State> object.
227              
228             =cut
229              
230             has starch_state => (
231             is => 'ro',
232             isa => InstanceOf[ 'Starch::State' ],
233             lazy => 1,
234             builder => '_build_starch_state',
235             writer => '_set_starch_state',
236             predicate => '_has_starch_state',
237             clearer => '_clear_starch_state',
238             );
239             sub _build_starch_state {
240 4     4   11 my ($c) = @_;
241 4         118 my $state = $c->starch->state( $c->sessionid() );
242 4         10502 $c->_set_sessionid( $state->id() );
243 4         107 return $state;
244             }
245              
246             =head1 CLASS ATTRIBUTES
247              
248             =head2 starch
249              
250             The L<Starch::Manager> object. This gets automatically constructed from
251             the C<Plugin::Starch> Catalyst configuration key per L</CONFIGURATION>.
252              
253             =cut
254              
255             class_has starch => (
256             is => 'ro',
257             isa => InstanceOf[ 'Starch::Manager' ],
258             lazy => 1,
259             builder => '_build_starch',
260             );
261             sub _build_starch {
262 1     1   5 my ($c) = @_;
263              
264 1         5 my $starch = $c->config->{'Plugin::Starch'};
265 1 50       87 Catalyst::Exception->throw( 'No Catalyst configuration was specified for Plugin::Starch' ) if !$starch;
266 1 50       5 Catalyst::Exception->throw( 'Plugin::Starch config was not a hash ref' ) if ref($starch) ne 'HASH';
267              
268 1         33 my $args = Starch::Manager->BUILDARGS( $starch );
269 1   50     337 my $plugins = delete( $args->{plugins} ) || [];
270              
271             $plugins = [
272 1         3 @{ $c->default_starch_plugins() },
  1         6  
273             @$plugins,
274             ];
275              
276 1         13 return Starch->new(
277             plugins => $plugins,
278             %$args,
279             );
280             }
281              
282             =head1 METHODS
283              
284             =head2 session
285              
286             $c->session->{foo} = 45;
287             $c->session( foo => 45 );
288             $c->session({ foo => 45 });
289              
290             Returns a hash ref of the session data which may be modified and
291             will be stored at the end of the request.
292              
293             A hash list or a hash ref may be passed to set values.
294              
295             =cut
296              
297             sub session {
298 8     8 1 20383 my $c = shift;
299              
300 8         249 my $data = $c->starch_state->data();
301 8 100       1050 return $data if !@_;
302              
303 2         5 my $new_data;
304 2 100 66     13 if (@_==1 and ref($_[0]) eq 'HASH') {
305 1         3 $new_data = $_[0];
306             }
307             else {
308 1         4 $new_data = { @_ };
309             }
310              
311 2         8 foreach my $key (keys %$new_data) {
312 2         6 $data->{$key} = $new_data->{$key};
313             }
314              
315 2         6 return $data;
316             }
317              
318             =head2 delete_session
319              
320             $c->delete_session();
321             $c->delete_session( $reason );
322              
323             Deletes the session, optionally with a reason specified.
324              
325             =cut
326              
327             sub delete_session {
328 1     1 1 6270 my ($c, $reason) = @_;
329              
330 1 50       43 if ($c->_has_starch_state()) {
331 1         29 $c->starch_state->delete();
332             }
333              
334 1         68 $c->_set_session_delete_reason( $reason );
335              
336 1         3 return;
337             }
338              
339             =head2 save_session
340              
341             Saves the session to the store.
342              
343             =cut
344              
345             sub save_session {
346 4     4 1 37 my ($c) = @_;
347 4         109 $c->starch_state->save();
348 4         1049 return;
349             }
350              
351             =head2 change_session_id
352              
353             $c->change_session_id();
354              
355             Generates a new ID for the session but retains the session
356             data in the new session.
357              
358             Some interesting discussion as to why this is useful is at
359             L<Catalyst::Plugin::Session/METHODS> under the C<change_session_id>
360             method.
361              
362             =cut
363              
364             sub change_session_id {
365 1     1 1 8 my ($c) = @_;
366              
367 1         34 $c->_clear_sessionid();
368              
369 1 50       34 $c->starch_state->reset_id() if $c->_has_starch_state();
370              
371 1         525 $c->_set_sessionid( $c->starch_state->id() );
372              
373 1         5 return;
374             }
375              
376             =head2 change_session_expires
377              
378             Sets the expires duration on the session which defaults to the
379             global expires set in L</CONFIGURATION>.
380              
381             =cut
382              
383             sub change_session_expires {
384 1     1 1 3 my $self = shift;
385 1         28 $self->starch_state->set_expires( @_ );
386 1         39 return;
387             }
388              
389             =head2 session_is_valid
390              
391             Currently this always returns C<1>.
392              
393             =cut
394              
395 1     1 1 8 sub session_is_valid { 1 }
396              
397             =head2 delete_expired_sessions
398              
399             Calls L<Starch::Store/reap_expired> on the store. This method is
400             here for backwards compatibility with L<Catalyst::Plugin::Session>
401             which expects you to delete expired sessions within the context of
402             an HTTP request. Since starch is available independently from Catalyst
403             you should consider calling C<reap_expired> yourself within a cronjob.
404              
405             If the store does not support expired session reaping then an
406             exception will be thrown.
407              
408             =cut
409              
410             sub delete_expired_sessions {
411 1     1 1 15 my ($self) = @_;
412              
413 1         34 $self->starch->store->reap_expired();
414              
415 0         0 return;
416             }
417              
418             sub finalize_session {
419 5     5 0 12 my ($c) = @_;
420              
421 5         163 $c->_clear_sessionid();
422 5         172 $c->_clear_session_delete_reason();
423              
424 5 100       160 return if !$c->_has_starch_state();
425              
426 4         19 $c->save_session();
427              
428 4         7 return;
429             }
430              
431             after setup_finalize => sub{
432             my ($c) = @_;
433             $c->starch();
434             return;
435             };
436              
437             before finalize_body => sub{
438             my ($c) = @_;
439             $c->finalize_session();
440             return;
441             };
442              
443             around dump_these => sub{
444             my $orig = shift;
445             my $c = shift;
446              
447             return $c->$orig( @_ ) if !$c->_has_sessionid();
448              
449             return(
450             $c->$orig( @_ ),
451             [ 'SessionID' => $c->sessionid() ],
452             [ 'Session' => $c->session() ],
453             );
454             };
455              
456             1;
457             __END__
458              
459             =head1 SUPPORT
460              
461             Please submit bugs and feature requests to the
462             Catalyst-Plugin-Starch GitHub issue tracker:
463              
464             L<https://github.com/bluefeet/Catalyst-Plugin-Starch/issues>
465              
466             =head1 AUTHORS
467              
468             Aran Clary Deltac <bluefeet@gmail.com>
469              
470             =head1 ACKNOWLEDGEMENTS
471              
472             Thanks to L<ZipRecruiter|https://www.ziprecruiter.com/>
473             for encouraging their employees to contribute back to the open
474             source ecosystem. Without their dedication to quality software
475             development this distribution would not exist.
476              
477             =head1 LICENSE
478              
479             This library is free software; you can redistribute it and/or modify
480             it under the same terms as Perl itself.
481              
482             =cut
483