File Coverage

lib/Leyland/Context.pm
Criterion Covered Total %
statement 33 210 15.7
branch 0 84 0.0
condition 0 77 0.0
subroutine 11 37 29.7
pod 14 14 100.0
total 58 422 13.7


line stmt bran cond sub pod time code
1             package Leyland::Context;
2              
3             # ABSTRACT: The working environment of an HTTP request and Leyland response
4              
5 1     1   711 use Moo;
  1         1  
  1         7  
6 1     1   401 use namespace::clean;
  1         2  
  1         9  
7              
8 1     1   209 use Carp;
  1         2  
  1         81  
9 1     1   186574 use Data::Dumper;
  1         9128  
  1         96  
10 1     1   12 use JSON;
  1         2  
  1         10  
11 1     1   800 use Leyland::Exception;
  1         3  
  1         49  
12 1     1   687 use Leyland::Logger;
  1         3  
  1         34  
13 1     1   8 use Module::Load;
  1         2  
  1         10  
14 1     1   45 use Text::SpanningTable;
  1         2  
  1         23  
15 1     1   10 use Try::Tiny;
  1         1  
  1         86  
16 1     1   164978 use XML::TreePP;
  1         13445  
  1         4338  
17              
18             extends 'Plack::Request';
19              
20             =head1 NAME
21              
22             Leyland::Context - The working environment of an HTTP request and Leyland response
23              
24             =head1 SYNOPSIS
25              
26             # every request automatically gets a Leyland::Context object, which
27             # is available in your routes and your views:
28             post '^/blog$' {
29             my $post = Blog->new(
30             subject => $c->params->{subject},
31             user => $c->user,
32             date => DateTime->now,
33             text => $c->params->{text}
34             );
35             return $c->template('blog.html', { post => $post });
36             }
37              
38             # by the way, since Leyland is RESTful, your application can accept
39             # requests of any content type, making the above something like
40             # this:
41             post '^/blog$' accepts 'text/plain' {
42             my $post = Blog->new(
43             subject => $c->params->{subject},
44             user => $c->user,
45             date => DateTime->now,
46             text => $c->data
47             );
48             return $c->template('blog.html', { post => $post });
49             }
50              
51             =head1 DESCRIPTION
52              
53             The Leyland context object is the heart and soul of your application. Or
54             something. Anyway, it's an object that escorts an HTTP request to somewhere
55             in your application through its entire lifetime, up to the point where an
56             HTTP response is sent back to the client. This is quite similar to the
57             L context object.
58              
59             The context object holds a lot of information about the request, such as
60             its content type, its method, the parameters/content provided with it,
61             the routes it matched in your application, etc. Your controllers and views
62             will mostly interact with this object, which apart from the information
63             mentioned just now, provides you with useful methods to generate the final
64             response, and perform other necessary operations such as logging.
65              
66             The Leyland context object inherits from L, so you can
67             use all of its attributes and methods. But keep in mind that this class
68             performs some modifications on these methods, all documented in the
69             L section.
70              
71             This document is for reference purposes only, please refer to L
72             for more information on using the context object.
73              
74             =head1 EXTENDS
75              
76             L
77              
78             =head1 ATTRIBUTES
79              
80             =head2 app
81              
82             Holds the object of the application.
83              
84             =head2 cwe
85              
86             Holds the L environment in which the application is running. This
87             is the C environment variable. See the C<-E> or C<--env> switch
88             in L for more information. Defaults to 'development'.
89              
90             =head2 res
91              
92             The L object used to respond to the client.
93              
94             =head2 routes
95              
96             An array reference of all routes matched by the request (there can be
97             more than one).
98              
99             =head2 current_route
100              
101             The index of the route to be invoked in the "routes" attribute above. By
102             default this would be the first route (i.e. index 0), unless Ces are
103             performed.
104              
105             =head2 froutes
106              
107             An array reference of all routes matched by an internal forward.
108              
109             =head2 current_froute
110              
111             The index of the route to be forwarded to in the "froutes" attribute above.
112             By default this would be the first route (i.e. index 0), unless Ces
113             are performed.
114              
115             =head2 controller
116              
117             The controller class of the current route.
118              
119             =head2 wanted_mimes
120              
121             An array reference of all media types the client accepts, ordered by
122             the client's preference, as defined by the "Accept" HTTP header.
123              
124             =head2 want
125              
126             The media type the L has decided to return to the
127             client.
128              
129             =head2 lang
130              
131             The language to use when localizing responses, probably according to the
132             client's wishes. Defaults to 'en' for English.
133              
134             =head2 stash
135              
136             A hash-ref of data meant to be available for the views/templates when
137             rendering resources.
138              
139             =head2 user
140              
141             Something (anything really) that describes the user that initiated the
142             request. This attribute will only be defined by the application, you are
143             free to choose whatever scheme of authentication as you wish, this attribute
144             is provided for your convenience. You will use the C method
145             to set the value of this attribute.
146              
147             =head2 json
148              
149             A L object for usage by routes as they see fit.
150              
151             =head2 xml
152              
153             An L object for usage by routes as they see fit.
154              
155             =head2 _pass_next
156              
157             Holds a boolean value indicating whether the route has decided to pass
158             the request to the next matching route. Not to be used directly.
159              
160             =head2 _data
161              
162             Holds the content of the HTTP request for POST and PUT requests after
163             parsing. Not to be used directly.
164              
165             =cut
166              
167             has 'app' => (
168             is => 'ro',
169             isa => sub { die "app must be a Leyland object" unless ref $_[0] && $_[0]->isa('Leyland') },
170             required => 1,
171             handles => ['cwe', 'views', 'config']
172             );
173              
174             has 'res' => (
175             is => 'lazy',
176             isa => sub { die "res must be a Plack::Response object" unless ref $_[0] && $_[0]->isa('Plack::Response') }
177             );
178              
179             has 'routes' => (
180             is => 'ro',
181             isa => sub { die "routes must be an array-ref" unless ref $_[0] && ref $_[0] eq 'ARRAY' },
182             predicate => 'has_routes',
183             writer => '_set_routes'
184             );
185              
186             has 'current_route' => (
187             is => 'ro',
188             isa => sub { die "current_route must be an integer" unless $_[0] =~ m/^\d+$/ },
189             default => sub { 0 },
190             writer => '_set_current_route'
191             );
192              
193             has 'froutes' => (
194             is => 'ro',
195             isa => sub { die "froutes must be an array-ref" unless ref $_[0] && ref $_[0] eq 'ARRAY' },
196             predicate => 'has_froutes',
197             writer => '_set_froutes',
198             clearer => '_clear_froutes'
199             );
200              
201             has 'current_froute' => (
202             is => 'ro',
203             isa => sub { die "current_froute must be an integer" unless $_[0] =~ m/^\d+$/ },
204             default => sub { 0 },
205             writer => '_set_current_froute'
206             );
207              
208             has 'controller' => (
209             is => 'ro',
210             isa => sub { die "controller must be a scalar" if ref $_[0] },
211             writer => '_set_controller'
212             );
213              
214             has 'wanted_mimes' => (
215             is => 'ro',
216             isa => sub { die "wanted_mimes must be an array-ref" unless ref $_[0] && ref $_[0] eq 'ARRAY' },
217             builder => '_build_mimes'
218             );
219              
220             has 'want' => (
221             is => 'ro',
222             isa => sub { die "want must be a scalar" if ref $_[0] },
223             writer => '_set_want'
224             );
225              
226             has 'lang' => (
227             is => 'ro',
228             isa => sub { die "lang must be a scalar" if ref $_[0] },
229             writer => 'set_lang',
230             default => sub { 'en' }
231             );
232              
233             has 'stash' => (
234             is => 'ro',
235             isa => sub { die "stash must be an hash-ref" unless ref $_[0] && ref $_[0] eq 'HASH' },
236             default => sub { {} }
237             );
238              
239             has 'user' => (
240             is => 'ro',
241             predicate => 'has_user',
242             writer => 'set_user',
243             clearer => 'clear_user'
244             );
245              
246             has 'log' => (
247             is => 'lazy',
248             isa => sub { die "log must be a Leyland::Logger object" unless ref $_[0] && ref $_[0] eq 'Leyland::Logger' }
249             );
250              
251             has 'json' => (
252             is => 'ro',
253             isa => sub { die "json must be a JSON object" unless ref $_[0] && ref $_[0] eq 'JSON' },
254             default => sub { JSON->new->utf8(0)->convert_blessed }
255             );
256              
257             has 'xml' => (
258             is => 'ro',
259             isa => sub { die "xml must be an XML::TreePP object" unless ref $_[0] && ref $_[0] eq 'XML::TreePP' },
260             default => sub { my $xml = XML::TreePP->new(); $xml->set(utf8_flag => 1); return $xml; }
261             );
262              
263             has '_pass_next' => (
264             is => 'ro',
265             default => sub { 0 },
266             writer => '_set_pass_next'
267             );
268              
269             has '_data' => (
270             is => 'ro',
271             predicate => '_has_data',
272             writer => '_set_data'
273             );
274              
275             =head1 OBJECT METHODS
276              
277             Since this class extends L, it inherits all its methods,
278             so refer to Plack::Request for a full list. However, this module performs
279             some modifications on certain Plack::Request methods, all of which are
280             documented in the L section.
281              
282             =head2 leyland
283              
284             An alias for the "app" attribute.
285              
286             =cut
287              
288 0     0 1   sub leyland { shift->app }
289              
290             =head2 has_routes()
291              
292             Returns a true value if the request matched any routes.
293              
294             =head2 has_froutes()
295              
296             Returns a true value if the request has routes matched in an internal forward.
297              
298             =head2 set_lang( $lang )
299              
300             Sets the language to be used for localization.
301              
302             =head2 has_user()
303              
304             Returns a true value if the "user" argument has a value.
305              
306             =head2 set_user( $user )
307              
308             Sets the "user" argument with a new value. This value could be anything,
309             a simple string/number, a hash-ref, an object, whatever your app uses.
310              
311             =head2 clear_user()
312              
313             Clears the value of the "user" argument, if any. Useful for logout actions.
314              
315             =head2 params()
316              
317             A shortcut for C<< $c->parameters->as_hashref_mixed >>. Note that this
318             is read-only, so changes you make to the hash-ref returned by this method
319             are not stored. For example, if you run C<< $c->params->{something} = 'whoa' >>,
320             subsequent calls to C<< $c->params >> will not have the "something"
321             key.
322              
323             =cut
324              
325 0     0 1   sub params { shift->parameters->as_hashref_mixed }
326              
327             =head2 data( [ $dont_parse ] )
328              
329             Returns the data of the request for POST and PUT requests. If the data
330             is JSON or XML, this module will attempt to automatically convert it to a Perl
331             data structure, which will be returned by this method (if conversion will
332             fail, this method will return an empty hash-ref). Otherwise, the data will be returned
333             as is. You can force this method to return the data as is even if it's
334             JSON or XML by passing a true value to this method.
335              
336             If the request had no content, an empty hash-ref will be returned. This is different
337             than version 0.003 and down where it would have returned C.
338              
339             =cut
340              
341             sub data {
342 0     0 1   my ($self, $dont_parse) = @_;
343              
344 0 0 0       return {} unless $self->content_type && $self->content;
345              
346 0 0         return $self->_data if $self->_has_data;
347              
348 0 0 0       if ($self->content_type =~ m!^application/json! && !$dont_parse) {
    0 0        
349 0     0     my $data = try { $self->json->decode($self->content) } catch { {} };
  0            
  0            
350 0 0         return unless $data;
351 0           $self->_set_data($data);
352 0           return $self->_data;
353             } elsif ($self->content_type =~ m!^application/(atom+)?xml! && !$dont_parse) {
354 0     0     my $data = try { $self->xml->parse($self->content) } catch { {} };
  0            
  0            
355 0 0         return unless $data;
356 0           $self->_set_data($data);
357 0           return $self->_data;
358             } else {
359 0           my $data = $self->content;
360 0           $self->_set_data($data);
361 0           return $self->_data;
362             }
363              
364 0           return;
365             }
366              
367             =head2 pass()
368              
369             Causes Leyland to invoke the next matching route, if any, after this
370             request has finished (meaning it does not pass immediately). Since you
371             will most likely want to pass routes immediately, use C<< return $self->pass >>
372             in your routes to do so.
373              
374             =cut
375              
376             sub pass {
377 0     0 1   my $self = shift;
378              
379             # are we passing inside an internal forward or externally?
380             # in any case, do not allow passing if we don't have routes to pass to
381 0 0 0       if ($self->has_froutes && $self->current_froute + 1 < scalar @{$self->froutes}) {
  0 0 0        
  0            
382 0           $self->_set_current_froute($self->current_froute + 1);
383 0           $self->_set_pass_next(1);
384 0           return 1;
385             } elsif (!$self->has_froutes && $self->current_route + 1 < scalar @{$self->routes}) {
386 0           $self->_set_current_route($self->current_route + 1);
387 0           $self->_set_pass_next(1);
388 0           return 1;
389             }
390              
391 0           return 0;
392             }
393              
394             =head2 render( $tmpl_name, [ \%context, $use_layout ] )
395              
396             =head2 template( $tmpl_name, [ \%context, $use_layout ] )
397              
398             Renders the view/template named C<$tmpl_name> using the first view class
399             defined by the application. Anything in the C<$context> hash-ref will be
400             automatically available inside the template, which will be rendered inside
401             whatever layout template is defined, unless C<$use_layout> is provided
402             and holds a false value (well, 0 really). The context object (i.e. C<$c>)
403             will automatically be embedded in the C<$context> hash-ref under the name
404             "c", as well as the application object (i.e. C<< $c->app >>) under the
405             name "l". Anything in the stash (i.e. C<< $c->stash >>) will also be
406             embedded in the context hash-ref, but keys in C<$context> take precedence
407             to the stash, so if the stash has the key 'name' and C<$context> also has
408             the key 'name', then the one from C<$context> will be used.
409              
410             Returns the rendered output. You will mostly use this at the end of your
411             routes as the return value.
412              
413             The two methods are the same, C