File Coverage

blib/lib/Prancer/Application.pm
Criterion Covered Total %
statement 33 68 48.5
branch 0 6 0.0
condition 0 6 0.0
subroutine 11 21 52.4
pod 0 2 0.0
total 44 103 42.7


line stmt bran cond sub pod time code
1             package Prancer::Application;
2              
3 4     4   4007 use strict;
  4         4  
  4         116  
4 4     4   12 use warnings FATAL => 'all';
  4         3  
  4         98  
5              
6 4     4   11 use Exporter;
  4         1  
  4         128  
7 4     4   9 use parent qw(Exporter);
  4         3  
  4         16  
8              
9 4     4   165 use Carp;
  4         4  
  4         158  
10 4     4   10 use Try::Tiny;
  4         4  
  4         150  
11 4     4   10 use Module::Load ();
  4         6  
  4         56  
12 4     4   781 use Router::Boom::Method;
  4         13916  
  4         94  
13 4     4   15 use Prancer;
  4         4  
  4         371  
14              
15             our @EXPORT_OK = qw(context mount dispatch config logger database template);
16             our %EXPORT_TAGS = ('all' => [ @EXPORT_OK ]);
17              
18             sub new {
19 0     0 0       my ($class, $context) = @_;
20 0               my $self = bless({ '_context' => $context }, $class);
21 0               $self->{'_dispatcher'} = Router::Boom::Method->new();
22              
23             # because the actual method needs a reference to the object it belongs to,
24             # it will be wrapped to provide $self as the first argument. we assume that
25             # the exported methods are actually implemented by a method with the same
26             # name but prefixed with an underscore. this loop dynamically creates the
27             # exported method and makes it call the actual method.
28 0               for my $method (@EXPORT_OK) {
29 4     4   11         no strict 'refs';
  4         4  
  4         84  
30 4     4   9         no warnings 'redefine';
  4         4  
  4         1586  
31 0                   my $exported = __PACKAGE__ . "::${method}";
32 0                   *{"${exported}"} = sub {
33 0     0                 my $internal = "_${method}";
34 0                       &$internal($self, @_);
35 0                   };
36                 }
37              
38 0               return $self;
39             }
40              
41             sub handle {
42 0     0 0       my ($self, $env) = @_;
43 0               croak "method 'handle' must be implemented by subclass to " . __PACKAGE__;
44             }
45              
46             ## no critic (ProhibitUnusedPrivateSubroutines)
47             sub _config {
48 0     0         my $self = shift;
49 0               return Prancer::config(@_);
50             }
51              
52             ## no critic (ProhibitUnusedPrivateSubroutines)
53             sub _logger {
54 0     0         my $self = shift;
55 0               return Prancer::logger(@_);
56             }
57              
58             ## no critic (ProhibitUnusedPrivateSubroutines)
59             sub _database {
60 0     0         my $self = shift;
61 0               return Prancer::database(@_);
62             }
63              
64             ## no critic (ProhibitUnusedPrivateSubroutines)
65             sub _template {
66 0     0         my $self = shift;
67 0               return Prancer::template(@_);
68             }
69              
70             ## no critic (ProhibitUnusedPrivateSubroutines)
71             sub _context {
72 0     0         my $self = shift;
73 0               return $self->{'_context'};
74             }
75              
76             ## no critic (ProhibitUnusedPrivateSubroutines)
77             sub _mount {
78 0     0         my ($self, $method, $path, $sub) = @_;
79              
80 0 0 0           unless ($sub && ref($sub) && ref($sub) eq "CODE") {
      0        
81 0                   croak "can only dispatch to a sub";
82                 }
83              
84 0               $self->{'_dispatcher'}->add($method, $path, $sub);
85 0               return;
86             }
87              
88             ## no critic (ProhibitUnusedPrivateSubroutines)
89             sub _dispatch {
90 0     0         my $self = shift;
91              
92 0               my $env = $self->{'_context'}->env();
93 0               my $path = $env->{PATH_INFO};
94 0               my $method = $env->{REQUEST_METHOD};
95 0               my ($matched, $captured, $is_method_not_allowed) = $self->{'_dispatcher'}->match($method, $path);
96              
97 0 0             return [405, ['Content-Type', 'text/plain'], ['method not allowed']] if ($is_method_not_allowed);
98 0 0             return [404, ['Content-Type', 'text/plain'], ['not found']] unless defined($matched);
99 0               return $matched->($captured);
100             }
101              
102             1;
103              
104             =head1 NAME
105            
106             Prancer::Application
107            
108             =head1 SYNOPSIS
109            
110             This package is where your application should start.
111            
112             package MyApp;
113            
114             use Prancer::Application qw(:all);
115             use parent qw(Prancer::Application);
116            
117             sub new {
118             my $class = shift;
119             my $self = $class->SUPER::new(shift);
120            
121             # when prancer is instantiated, the programmer has the option to pass
122             # extra arguments after the handler class name. those arguments will end
123             # up in here!
124             #
125             # of course, you don't have to write a ->new method if you don't want to as
126             # one is created automatically in Prancer::Application. but that means that
127             # if you DO create one you must call to ->SUPER::new first and you MUST
128             # pass the first argument to ->new (after $class) to ->SUPER::new in order
129             # for Prancer to work correctly.
130            
131             return $self;
132             }
133            
134             sub handle {
135             my ($self, $env) = @_;
136            
137             mount('GET', '/', sub {
138             context->header(set => 'Content-Type', value => 'text/plain');
139             context->body("hello world");
140             context->finalize(200);
141             });
142            
143             return dispatch;
144             }
145            
146             A new instance of this package is created on every request so that request
147             specific fields may be filled in and available. It also means that your code
148             should be as lightweight as possible.
149            
150             Your class should implement C<handle>. Two arguments are passed to this method:
151             the instance of your class created for this request and C<$env> for the
152             request. You probably don't need to use C<$env> because the methods detailed
153             below should give you everything you need, especially C<context>.
154            
155             =head1 EXPORTABLE
156            
157             The following methods are exportable: C<context>, C<mount>, C<dispatch>,
158             C<config>, C<logger>, C<database>, C<template>. They can all be exported at
159             once with C<:all>.
160            
161             =head1 METHODS
162            
163             By default this package exports nothing. But that makes it difficult to use.
164             You should probably export C<:all>. That will give you quick access to these
165             methods.
166            
167             =over 4
168            
169             =item config
170            
171             Passes through to C<Prancer::config>. This is made available for your
172             application.
173            
174             =item logger
175            
176             Passes through to C<Prancer::logger>. This is made available for your
177             application.
178            
179             =item database
180            
181             Passes through to C<Prancer::database>. This is made available for your
182             application.
183            
184             =item template
185            
186             Passes through to C<Prancer::template>. This is made available for your
187             application.
188            
189             =item context
190            
191             This gives access to the request context. See L<Prancer::Context> for more
192             information about what that makes available. But here is a short example:
193            
194             context->header(set => 'Content-Type', value => 'text/plain');
195             context->body("hello world");
196             context->finalize(200);
197            
198             =item mount METHOD, PATH, SUB
199            
200             This adds a routed path. Prancer uses L<Router::Boom::Method> to handle
201             routing. If it is not installed then calls to this method will croak. The first
202             argument will always be the method or methods that should match. For example:
203            
204             mount('GET', ..., ...);
205             mount(['GET','POST'], ..., ...);
206            
207             The second argument should be the path that will match. For example:
208            
209             mount(..., '/', ...);
210             mount(..., '/:user', ...);
211             mount(..., '/blog/{year}', ...);
212             mount(..., '/blog/{year}/{month:\d+}', ...);
213             mount(..., '/download/*', ...);
214            
215             The last argument should be a sub that will run on a successful match. For
216             example:
217            
218             mount(..., ..., sub {
219             my $captured = shift;
220            
221             context->header(set => 'Content-Type', value => 'text/plain');
222             context->body("hello world");
223             context->finalize(200);
224             });
225            
226             Of course the sub doesn't have to be anonymous and could point to anything. The
227             only argument that gets passed to a sub is a hashref containing what, if
228             anything, was captured in the route. For example:
229            
230             mount(..., '/', sub {
231             my $captured = shift;
232             # captured = {}
233             });
234            
235             # :user matches qr{([^/]+)}
236             mount(..., '/:user', sub {
237             my $captured = shift;
238             print $captured->{'user'};
239             });
240            
241             # {year} matches qr{([^/]+)}
242             mount(..., '/blog/{year}/{month:\d+}', sub {
243             my $captured = shift;
244             print $captured->{'year'};
245             print $captured->{'month'};
246             });
247            
248             # * matches qr{(.+)}
249             mount(..., '/download/*', sub {
250             my $captured = shift;
251             print $captured->{'*'};
252             });
253            
254             Further documentation on how to use routes can be found by reading the docs
255             for L<Router::Boom> and L<Router::Boom::Method>.
256            
257             =item dispatch
258            
259             This should be called at the end of your implementation to C<handle>. It will
260             run the configured routes and return a valid PSGI response to the application
261             server. If you do not have L<Router::Boom> installed then calling this method
262             will croak. If you are not using L<Router::Boom> then you should not use this
263             method but should instead have your implementation of C<handle> return a valid
264             PSGI response.
265            
266             =back
267            
268             =cut
269