File Coverage

blib/lib/Catalyst/View/BasePerRequest.pm
Criterion Covered Total %
statement 132 542 24.3
branch 43 216 19.9
condition 10 24 41.6
subroutine 33 192 17.1
pod 10 183 5.4
total 228 1157 19.7


line stmt bran cond sub pod time code
1             package Catalyst::View::BasePerRequest;
2              
3             our $VERSION = '0.010';
4             our $DEFAULT_FACTORY = 'Catalyst::View::BasePerRequest::Lifecycle::Request';
5              
6 1     1   2202061 use Moose;
  1         3  
  1         6  
7 1     1   6504 use HTTP::Status ();
  1         3  
  1         29  
8 1     1   9 use Scalar::Util ();
  1         3  
  1         14  
9 1     1   5 use Module::Runtime ();
  1         3  
  1         671  
10              
11             extends 'Catalyst::View';
12              
13             has 'catalyst_component_name' => (is=>'ro');
14             has 'app' => (is=>'ro', required=>1);
15             has 'ctx' => (is=>'ro', required=>1);
16             has 'root' => (is=>'rw', required=>1, default=>sub { shift });
17             has 'parent' => (is=>'rw', predicate=>'has_parent');
18             has 'content_type' => (is=>'ro', required=>0, predicate=>'has_content_type');
19             has 'code' => (is=>'rw', predicate=>'has_code');
20             has 'status_codes' => (is=>'rw', predicate=>'has_status_codes');
21             has 'injected_views' => (is=>'rw', predicate=>'has_injected_views');
22             has 'forwarded_args' => (is=>'rw', predicate=>'has_forwarded_args');
23              
24 0     0 0 0 sub application { shift->app }
25              
26             sub views {
27 0     0 0 0 my ($app, %views) = @_;
28 0         0 $app->config->{views} = \%views;
29             }
30              
31             sub view {
32 0     0 0 0 my ($app, $method, @args) = @_;
33 0 0       0 if(scalar(@args) > 1) {
34 0         0 $app->config->{views}{$method} = \@args;
35             } else {
36 0         0 $app->config->{views}{$method} = $args[0];
37             }
38             }
39              
40             sub set_content_type {
41 0     0 0 0 my ($app, $ct) = @_;
42 0         0 $app->config->{content_type} = $ct;
43             }
44              
45             sub set_status_codes {
46 0     0 0 0 my $app = shift;
47 0 0       0 my $codes = ref($_[0]) ? shift : [shift];
48 0         0 $app->config->{status_codes} = $codes;
49             }
50              
51             sub COMPONENT {
52 3     3 1 101852 my ($class, $app, $args) = @_;
53 3         13 my $merged_args = $class->merge_config_hashes($class->config, $args);
54              
55 3 50       319 $merged_args = $class->modify_init_args($app, $merged_args) if $class->can('modify_init_args');
56 3         20 my %status_codes = $class->inject_http_status_helpers($merged_args);
57 3 100       14 $merged_args->{status_codes} = \%status_codes if scalar(keys(%status_codes));
58 3         19 my @injected_views = $class->inject_view_helpers($merged_args);
59 3 50       7 $merged_args->{injected_views} = \@injected_views if scalar @injected_views;
60              
61 3         17 my $factory_class = Module::Runtime::use_module($class->factory_class($app, $merged_args));
62 3         88 return my $factory = $class->build_factory($factory_class, $app, $merged_args);
63             }
64              
65             sub inject_view_helpers {
66 3     3 0 10 my ($class, $merged_args) = @_;
67 3 50       17 if(my $views = $merged_args->{views}) {
68 0         0 require Sub::Util;
69 0         0 foreach my $method (keys %$views) {
70 0         0 my ($view_name, @args_proto) = ();
71 0         0 my $options_proto = $views->{$method};
72              
73 0         0 my $global_args_generator;
74 0 0 0     0 if( (ref($options_proto)||'') eq 'ARRAY') {
75 0         0 ($view_name, @args_proto) = @$options_proto;
76             $global_args_generator = (ref($args_proto[0])||'') eq 'CODE' ?
77             shift(@args_proto) :
78 0 0 0 0   0 sub { @args_proto };
  0         0  
79             } else {
80 0         0 $view_name = $options_proto;
81             }
82              
83 1     1   8 no strict 'refs';
  1         3  
  1         2151  
84 0         0 *{"${class}::${method}"} = Sub::Util::set_subname "${class}::${method}" => sub {
85 0     0   0 my ($self, @args) = @_;
86 0 0       0 my @global_args = $global_args_generator ? $global_args_generator->($self, $self->ctx, @args) : ();
87 0         0 my $view = $self->ctx->view($view_name, @global_args, @args);
88              
89 0         0 $view->root($self->root);
90 0         0 $view->parent($self);
91              
92 0         0 return $view;
93 0         0 };
94             }
95 0         0 return keys %$views;
96             }
97 3         9 return ();
98             }
99              
100             sub inject_http_status_helpers {
101 3     3 0 10 my ($class, $merged_args) = @_;
102              
103 3         8 my %status_codes = ();
104 3 100       10 if(exists $merged_args->{status_codes}) {
105 2         4 %status_codes = map { $_=>1 } @{$merged_args->{status_codes}};
  6         22  
  2         6  
106             }
107              
108 3         14 foreach my $helper( grep { $_=~/^http/i} @HTTP::Status::EXPORT_OK) {
  219         477  
109 207         498 my $subname = lc $helper;
110 207         624 my $code = HTTP::Status->$helper;
111 207 100       436 if(scalar(keys(%status_codes))) {
112 138 100       305 next unless $status_codes{$code};
113             }
114 75     0 0 130 eval "sub ${\$class}::${\$subname} { return shift->respond(HTTP::Status::$helper,\\\@_) }";
  75     0 0 179  
  75     0 0 3805  
  0     0 0 0  
  0     0 0 0  
  0     1 0 0  
  0     0 0 0  
  0     0 0 0  
  1     0 0 643  
  0     1 0 0  
  0     0 0 0  
  0     0 0 0  
  1     0 0 622  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0         0  
  0         0  
  0         0  
115 75 0   0 0 229 eval "sub ${\$class}::set_${\$subname} {
  75 0   0 0 210  
  75 0   0 0 5794  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 50   1 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  1 0   0 0 608  
  1 0   0 0 34  
  1 0   0 0 200  
  1 0   0 0 218  
  0 50   1 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  1         605  
  1         32  
  1         161  
  1         3  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
116             my (\$self, \@headers) = \@_;
117             \$self->ctx->res->status(HTTP::Status::$helper);
118             \$self->ctx->res->headers->push_header(\@headers) if \@headers;
119             return \$self;
120             }";
121             }
122              
123 3         28 return %status_codes;
124             }
125              
126             sub factory_class {
127 3     3 0 8 my ($class, $app, $merged_args) = @_;
128 3 100       10 if(exists $merged_args->{lifecycle}) {
129 1         11 my $lifecycle = $merged_args->{lifecycle};
130 1 50       22 $lifecycle = "Catalyst::View::BasePerRequest::Lifecycle::${lifecycle}" unless $lifecycle=~/^\+/;
131 1         6 return $lifecycle;
132             }
133 2         10 return $DEFAULT_FACTORY;
134             }
135              
136             sub build {
137 24     24 1 87 my ($class, %args) = @_;
138 24         894 return $class->new(%args);
139             }
140              
141             sub build_factory {
142 3     3 0 13 my ($class, $factory_class, $app, $merged_args) = @_;
143 3         25 return my $factory = bless +{
144             class=>$class,
145             app=>$app,
146             merged_args=>$merged_args,
147             }, $factory_class;
148             }
149              
150             sub render {
151 0     0 0 0 my ($self, $ctx, @args) = @_;
152 0         0 die "You need to write a 'render' method!";
153             }
154              
155             sub process {
156 1     1 1 1701 my ($self, $c, @args) = @_;
157 1         44 $self->forwarded_args(\@args);
158 1         6 return $self->respond();
159             }
160              
161             sub respond {
162 3     3 1 11 my ($self, $status, $headers, @args) = @_;
163 3         95 for my $r ($self->ctx->res) {
164 3 100 66     132 $r->status($status) if $status && $r->status; # != 200; # Catalyst sets 200
165              
166             Module::Runtime::use_module('Catalyst::View::BasePerRequest::Exception::InvalidStatusCode')->throw(status_code=>$r->status)
167 3 50 33     519 if $self->has_status_codes && !$self->status_codes->{$r->status};
168            
169 3 50 33     356 $r->content_type($self->content_type) if !$r->content_type && $self->has_content_type;
170 3 100       620 $r->headers->push_header(@{$headers}) if $headers;
  2         198  
171 3         44 $r->body($self->get_rendered(@args));
172             }
173 3         36 return $self; # allow chaining
174             }
175              
176             sub get_rendered {
177 24     24 0 1444 my $self = shift;
178 24         49 my @rendered = ();
179              
180             eval {
181 24         71 my @args = $self->prepare_render_args(@_);
182             @rendered = map {
183 24 100 66     95 Scalar::Util::blessed($_) && $_->can('get_rendered') ? $_->get_rendered : $_;
  24         1038  
184             } $self->render(@args);
185 24         77 1;
186 24 50       49 } || do {
187 0         0 $self->do_handle_render_exception($@);
188             };
189              
190 24         70 return $self->flatten_rendered_for_response_body(@rendered);
191             }
192              
193 24     24 0 62 sub flatten_rendered_for_response_body { return shift->flatten_rendered(@_) }
194              
195 0     0 0 0 sub render_error_class { 'Catalyst::View::BasePerRequest::Exception::RenderError' }
196              
197             sub do_handle_render_exception {
198 0     0 0 0 my ($self, $err) = @_;
199 0 0 0     0 return $err->rethrow if Scalar::Util::blessed($err) && $err->can('rethrow');
200 0         0 my $class = Module::Runtime::use_module($self->render_error_class);
201 0         0 $class->throw(render_error=>$err);
202             }
203              
204             sub prepare_render_args {
205 24     24 0 54 my ($self, @args) = @_;
206              
207 24 100       905 if($self->has_code) {
208 6         16 my $inner = $self->render_code;
209 6         16 unshift @args, $inner; # pass any $inner as an argument to ->render()
210             }
211              
212 24         725 return ($self->ctx, @args);
213             }
214              
215             sub render_code {
216 6     6 0 12 my $self = shift;
217             my @inner = map {
218 6 100 66     16 Scalar::Util::blessed($_) && $_->can('get_rendered') ? $_->get_rendered : $_;
  18         392  
219             } $self->execute_code_callback($self->prepare_render_code_args);
220              
221 6         20 my $flat = $self->flatten_rendered_for_inner_content(@inner);
222 6         18 return $flat;
223             }
224              
225             sub execute_code_callback {
226 6     6 0 25 my ($self, @args) = @_;
227 6         201 return $self->code->(@args);
228             }
229              
230             sub prepare_render_code_args {
231 6     6 0 13 my ($self) = @_;
232 6         17 return $self;
233             }
234              
235 6     6 0 21 sub flatten_rendered_for_inner_content { return shift->flatten_rendered(@_) }
236              
237             sub flatten_rendered {
238 54     54 0 122 my $self = shift;
239 54         96 return join '', grep { defined($_) } @_;
  66         932  
240             }
241              
242             sub content {
243 12     12 1 1167 my ($self, $name, $options) = @_;
244 12 100       38 my %options = $options ? %$options : ();
245 12 100       32 my $default = exists($options{default}) ? $options{default} : '';
246              
247             return exists($self->ctx->stash->{view_blocks}{$name}) ?
248 12 100       369 $self->ctx->stash->{view_blocks}{$name} :
249             $default;
250             }
251              
252             sub render_content_value {
253 24     24 0 750 my $self = shift;
254 24 100 100     88 if((ref($_[0])||'') eq 'CODE') {
255 6         25 return $self->flatten_rendered_for_content_blocks($_[0]->($_[1]));
256             } else {
257 18         44 return $self->flatten_rendered_for_content_blocks(@_);
258             }
259             }
260              
261 24     24 0 79 sub flatten_rendered_for_content_blocks { return shift->flatten_rendered(@_) }
262              
263             sub content_for {
264 6     6 1 66 my ($self, $name, $value) = @_;
265 6 50       19 Module::Runtime::use_module($self->_content_exception_class)
266             ->throw(content_name=>$name, content_msg=>'Content block is already defined') if $self->_content_exists($name);
267 6         410 $self->ctx->stash->{view_blocks}{$name} = $self->render_content_value($value);
268 6         377 return;
269             }
270              
271             sub content_append {
272 6     6 1 40 my ($self, $name, $value) = @_;
273 6 50       14 Module::Runtime::use_module($self->_content_exception_class)
274             ->throw(content_name=>$name, content_msg=>'Content block doesnt exist for appending') unless $self->_content_exists($name);
275 6         549 $self->ctx->stash->{view_blocks}{$name} .= $self->render_content_value($value);
276 6         18 return;
277             }
278              
279             sub content_prepend {
280 6     6 0 40 my ($self, $name, $value) = @_;
281 6 50       12 Module::Runtime::use_module($self->_content_exception_class)
282             ->throw(content_name=>$name, content_msg=>'Content block doesnt exist for prepending') unless $self->_content_exists($name);
283 6         387 $self->ctx->stash->{view_blocks}{$name} = $self->render_content_value($value) . $self->ctx->stash->{view_blocks}{$name};
284 6         369 return;
285             }
286              
287              
288             sub content_replace {
289 0     0 1 0 my ($self, $name, $value) = @_;
290 0 0       0 Module::Runtime::use_module($self->_content_exception_class)
291             ->throw(content_name=>$name, content_msg=>'Content block doesnt exist for replacing') unless $self->_content_exists($name);
292 0         0 $self->ctx->stash->{view_blocks}{$name} = $self->render_content_value($value);
293 0         0 return;
294             }
295              
296             sub content_around {
297 6     6 1 55 my ($self, $name, $value) = @_;
298 6 50       15 Module::Runtime::use_module($self->_content_exception_class)
299             ->throw(content_name=>$name, content_msg=>'Content block doesnt exist') unless $self->_content_exists($name);
300 6         608 $self->ctx->stash->{view_blocks}{$name} = $self->render_content_value($value, $self->ctx->stash->{view_blocks}{$name});
301 6         373 return;
302             }
303              
304 0     0   0 sub _content_exception_class { return 'Catalyst::View::BasePerRequest::Exception::ContentBlockError' }
305              
306             sub _content_exists {
307 24     24   46 my ($self, $name) = @_;
308 24 100       763 return exists $self->ctx->stash->{view_blocks}{$name} ? 1:0;
309             }
310              
311 0     0 1   sub detach { return shift->ctx->detach }
312              
313             __PACKAGE__->meta->make_immutable;
314              
315             =head1 NAME
316            
317             Catalyst::View::Template::BasePerRequest - Catalyst base view for per request, strongly typed templates
318              
319             =head1 SYNOPSIS
320              
321             package Example::View::Hello;
322              
323             use Moose;
324              
325             extends 'Catalyst::View::BasePerRequest';
326              
327             has name => (is=>'ro', required=>1);
328             has age => (is=>'ro', required=>1);
329              
330             sub render {
331             my ($self, $c) = @_;
332             return "<div>Hello @{[ $self->name] }",
333             "I see you are @{[ $self->age]} years old!</div>";
334             }
335              
336             __PACKAGE__->config(
337             content_type=>'text/html',
338             status_codes=>[200]
339             );
340              
341             __PACKAGE__->meta->make_immutable();
342              
343             One way to use it in a controller:
344              
345             package Example::Controller::Root;
346              
347             use Moose;
348             use MooseX::MethodAttributes;
349              
350             extends 'Catalyst::Controller';
351              
352             sub root :Chained(/) PathPart('') CaptureArgs(0) { }
353              
354             sub hello :Chained(root) Args(0) {
355             my ($self, $c) = @_;
356             return $c->view(Hello =>
357             name => 'John',
358             age => 53
359             )->http_ok;
360             }
361              
362             __PACKAGE__->config(namespace=>'');
363             __PACKAGE__->meta->make_immutable;
364              
365             =head1 DESCRIPTION
366              
367             B<NOTE>: This is early access code. Although it's based on several other internal projects
368             which I have iterated over this concept for a number of years I still reserve the right
369             to make breaking changes as needed.
370              
371             B<NOTE>: You probably won't actually use this directly, it's intended to be a base framework
372             for building / prototyping strongly typed / per request views in L<Catalyst>. This
373             documentation serves as an overview of the concept. In particular please note that
374             this code does not address any issues around HTML / Javascript injection attacks or
375             provides any auto escaping. You'll need to bake those features into whatever you
376             build on top of this. Because of this the following documentation is light and is mostly
377             intended to help anyone who is planning to build something on top of this framework
378             rather than use it directly.
379              
380             B<NOTE>: This distribution's C</example> directory gives you a toy prototype using L<HTML::Tags>
381             as the basis to a view as well as some raw examples using this code directly (again,
382             not recommended for anything other than learning).
383              
384             In a classic L<Catalyst> application with server side templates, the canonical approach
385             is to use a 'view' as a sort of handler for an underlying template system (such as
386             L<Template::Toolkit> or L<Xslate>) and to send data to this template by populating
387             the stash. These views are very lean, and in general don't provide much in the way
388             of view logic processing; they generally are just a thin proxy for the underlying
389             templating system.
390              
391             This approach has the upside of being very simple to understand and in general works
392             ok with a simple websites. There are however downsides as your site becomes more
393             complex. First of all the stash as a means to pass data from the Controller to the
394             template can be fragile. For example just making a simple typo in the stash key
395             can break your templates in ways that might not be easy to figure out. Also your
396             template can't enforce its requirements very easily (and it's not easy for someone
397             working in the controller to know exactly what things need to go into the stash in
398             order for the template to function as desired.) The view itself has no way of
399             providing view / display oriented logic; generally that logic ends up creeping back up
400             into the controller in ways that break the notion of MVC's separation of concerns.
401              
402             Lastly the controller doesn't have a defined API with the view. All it can ask the view
403             is 'go ahead and process yourself using the current context' and all it gets back from
404             the view is a string response. If the controller wishes to introspect this response
405             or modify it in some way prior to it being sent back to the client, you have few options
406             apart from using regular expression matching to try and extract the required information
407             or to modify the response string.
408              
409             Basically the classic approach works acceptable well for a simple website but starts to
410             break down as your site becomes more complicated.
411              
412             An alternative approach, which is explored in this distribution, is to have a defined view for
413             each desired response and for it to define an explicit API that the controller uses to provide the required
414             and optional data to the view. This defined view can further define its own methods
415             used to generate suitable information for display. Such an approach is more initial work
416             as well as learning for the website developers, but in the long term it can provide
417             an easier path to sustainable development and maintainence with hopefully fewer bugs
418             and overall site issues.
419              
420             =head1 EXAMPLE: Basic
421              
422             The most minimal thing your view must provide in a C<render> method. This method gets
423             the view object and the context (it can also receive additional arguments if this view is
424             being called from other views as a wrapper or parent view; more on that later).
425              
426             The C<render> method should return a string or array of strings suitable for the body of
427             the response> B<NOTE> if you return an array of strings we flatten the array into a single
428             string since the C<body> method of L<Catalyst::Response> can't take an array.
429              
430             Here's a minimal example:
431              
432             package Example::View::Hello;
433              
434             use Moose;
435              
436             extends 'Catalyst::View::BasePerRequest';
437              
438             sub render {
439             my ($self, $c) = @_;
440             return "<p>Hello</p>";
441             }
442              
443             __PACKAGE__->config(content_type=>'text/html');
444              
445             And here's an example view with attributes:
446              
447             package Example::View::HelloPerson;
448              
449             use Moose;
450              
451             extends 'Catalyst::View::BasePerRequest';
452              
453             has name => (is=>'ro', required=>1);
454              
455             sub render {
456             my ($self, $c) = @_;
457             return qq[
458             <div>
459             Hello @{[ $self->name ]}
460             </div>];
461             }
462              
463             __PACKAGE__->meta->make_immutable();
464              
465             One way to invoke this view from the controller using the traditional C<forward> method:
466              
467             package Example::Controller::Root;
468              
469             use Moose;
470             use MooseX::MethodAttributes;
471              
472             extends 'Catalyst::Controller';
473              
474             sub root :Chained(/) PathPart('') CaptureArgs(0) { }
475              
476             sub hello :Chained(root) Args(0) {
477             my ($self, $c) = @_;
478             my $view = $c->view(HelloPerson => (name => 'John'));
479             return $c->forward($view);
480             }
481              
482             __PACKAGE__->config(namespace=>'');
483             __PACKAGE__->meta->make_immutable;
484              
485             Alternatively using L</"RESPONSE HELPERS">:
486              
487             package Example::Controller::Root;
488              
489             use Moose;
490             use MooseX::MethodAttributes;
491              
492             extends 'Catalyst::Controller';
493              
494             sub root :Chained(/) PathPart('') CaptureArgs(0) { }
495              
496             sub hello :Chained(root) Args(0) {
497             my ($self, $c) = @_;
498             return $c->view(HelloPerson => (name => 'John'))->http_ok;
499             }
500              
501             __PACKAGE__->config(namespace=>'');
502             __PACKAGE__->meta->make_immutable;
503              
504             =head1 ATTRIBUTES
505              
506             The following Moose attributes are considered part of this classes public API
507              
508             =head2 app
509              
510             The string namespace of your L<Catalyst> application.
511              
512             =head2 ctx
513              
514             The current L<Catalyst> context
515              
516             =head2 root
517              
518             The root view object (that is the top view that was called first, usually from
519             the controller).
520              
521             =head2 parent
522              
523             =head2 has_parent
524              
525             If the view was called from another view, that first view is set as the parent.
526              
527             =head2 injected_views
528              
529             =head2 has_injected_views
530              
531             An arrayref of the method names associated with any injected views.
532              
533             =head1 METHODS
534              
535             The following methods are considered part of this classes public API
536              
537             =head2 process
538              
539             Renders a view and sets up the response object. Generally this is called from a
540             controller via the C<forward> method and not directly:
541              
542             $c->forward($view);
543              
544             =head2 respond
545              
546             Accepts an HTTP status code and an arrayref of key / values used to set HTTP headers for a
547             response. Example:
548              
549             $view->respond(201, [ location=>$url ]);
550              
551             Returns the view object to make it easier to do method chaining
552              
553             =head2 detach
554              
555             Just a shortcut to ->detach via the context
556              
557             =head1 CONTENT BLOCK HELPERS
558              
559             Content block helpers are an optional feature to make it easy to create and populate content
560             areas between different views. Although you can also do this with object attributes you may
561             wish to separate template / text from data. Example:
562              
563             package Example::View::Layout;
564              
565             use Moose;
566              
567             extends 'Catalyst::View::BasePerRequest';
568              
569             has title => (is=>'ro', required=>1, default=>'Missing Title');
570              
571             sub render {
572             my ($self, $c, $inner) = @_;
573             return "
574             <html>
575             <head>
576             <title>@{[ $self->title ]}</title>
577             @{[ $self->content('css') ]}
578             </head>
579             <body>$inner</body>
580             </html>";
581             }
582              
583             __PACKAGE__->config(content_type=>'text/html');
584             __PACKAGE__->meta->make_immutable();
585              
586             package Example::View::Hello;
587              
588             use Moose;
589              
590             extends 'Catalyst::View::BasePerRequest';
591              
592             has name => (is=>'ro', required=>1);
593              
594             sub render {
595             my ($self, $c) = @_;
596             return $c->view(Layout => title=>'Hello', sub {
597             my $layout = shift;
598             $self->content_for('css', "<style>...</style>");
599             return "<div>Hello @{[ $self->name ]}!</div>";
600             });
601             }
602              
603             __PACKAGE__->config(content_type=>'text/html', status_codes=>[200]);
604             __PACKAGE__->meta->make_immutable();
605              
606             =head2 content
607              
608             Examples:
609              
610             $self->content($name);
611             $self->content($name, +{ default=>'No main content' });
612              
613             Gets a content block string by '$name'. If the block has not been defined returns either
614             a zero length string or whatever you set the default key of the hashref options to.
615              
616             =head2 content_for
617              
618             Sets a named content block or throws an exception if the content block already exists.
619              
620             =head2 content_append
621              
622             Appends to a named content block or throws an exception if the content block doesn't exist.
623              
624             =head2 content_replace
625              
626             Replaces a named content block or throws an exception if the content block doesn't exist.
627              
628             =head2 content_around
629              
630             Wraps an existing content with new content. Throws an exception if the named content block doesn't exist.
631              
632             $self->content_around('footer', sub {
633             my $footer = shift;
634             return "wrapped $footer end wrap";
635             });
636              
637             =head1 VIEW INJECTION
638              
639             Usually when building a website of more than toy complexity you will find that you will
640             decompose your site into sub views and view wrappers. Although you can call the C<view>
641             method on the context, I think its more in the spirit of the idea of a strong or structured
642             view to have a view declare upfront what views its calling as sub views. That lets you
643             have more central control over view initalization and decouples how you are calling your
644             views from the actual underlying views. It can also tidy up some of the code and lastly
645             makes it easy to immediately know what views are needed for the current one. This can
646             help with later refactoring (I've worked on projects where sub views got detached from
647             actual use but nobody ever cleaned them up.)
648              
649             To inject a view into the current one, you need to declare it in configuration:
650              
651             __PACKAGE__->config(
652             content_type=>'text/html',
653             status_codes=>[200,404,400],
654             views=>+{
655             layout1 => [ Layout => sub { my ($self, $c) = @_; return title=>'Hey!' } ],
656             layout2 => [ Layout => (title=>'Yeah!') ],
657             layout3 => 'Layout',
658             },
659             );
660              
661             Basically this is a hashref under the C<views> key, where each key in the hashref is the name
662             of the method you are injecting into the current view which is responsible for creating the
663             sub view and the value is one of three options:
664              
665             =over
666              
667             =item A scalar value
668              
669             __PACKAGE__->config(
670             content_type=>'text/html',
671             status_codes=>[200,404,400],
672             views=>+{
673             layout => 'Layout',
674             },
675             );
676              
677             This is the simplest option, it just injects a method that will call the named view and pass
678             any arguments from the method but does not add any global arguments.
679              
680             =item An arrayref
681              
682             __PACKAGE__->config(
683             content_type=>'text/html',
684             status_codes=>[200,404,400],
685             views=>+{
686             layout => [ Layout => (title=>'Yeah!') ],
687             },
688             );
689              
690             This option allows you to set some argument defaults to the view called. The first item in the
691             arrayref must be the real name of the view, followed by arguments which are merged with any provided
692             to the method.
693              
694             =item A coderef
695              
696             __PACKAGE__->config(
697             content_type=>'text/html',
698             status_codes=>[200,404,400],
699             views=>+{
700             layout => [ Layout => sub { my ($self, $c) = @_; return title=>'Hey!' } ],
701             },
702             );
703              
704             The most complex option, you should probably reserve for very special needs. Basically this coderef
705             will be called with the current view instance and Catalyst context; it should return arguments which
706             with then be merged and treated as in the arrayref option.
707              
708             =back
709              
710             Now you can call for the sub view via a simple method call on the view, rather than via the context:
711              
712             package Example::View::Hello;
713              
714             use Moose;
715              
716             extends 'Catalyst::View::BasePerRequest';
717              
718             has name => (is=>'ro', required=>1);
719             has age => (is=>'ro', required=>1);
720              
721              
722             sub render {
723             my ($self, $c) = @_;
724              
725             return $self->layout(title=>'Hello!', sub {
726             my $layout = shift;
727             return "Hello @{[$self->name]}; you are @{[$self->age]} years old!";
728             });
729             }
730              
731              
732             __PACKAGE__->config(
733             content_type=>'text/html',
734             status_codes=>[200,404,400],
735             views=>+{
736             layout => 'Layout',
737             },
738             );
739              
740             __PACKAGE__->meta->make_immutable;
741              
742              
743             =head1 RESPONSE HELPERS
744              
745             When you create a view instance the actual response is not send to the client until
746             the L</respond> method is called (either directly, via L</process> or thru the generated
747             response helpers).
748              
749             Response helpers are just methods that call L</respond> with the correct status code
750             and using a more easy to remember name (and possibly a more self documenting one).
751              
752             For example:
753              
754             $c->view('Login')->http_ok;
755              
756             calls the L</respond> method with the expected http status code. You can also pass
757             arguments to the response helper which are send to L</respond> and used to add HTTP
758             headers to the response.
759              
760             $c->view("NewUser")
761             ->http_created(location=>$url);
762              
763             Please note that calling a response helper only sets up the response object, it doesn't
764             stop any future actions in you controller. If you really want to stop action processing
765             you'll need to call L</detach>:
766              
767             return $c->view("Error")
768             ->http_bad_request
769             ->detach;
770              
771             If you don't want to generate the response yet (perhaps you'll leave that to a global 'end'
772             action) you can use the 'set_http_$STATUS' helpers instead which wil just set the response
773             status.
774              
775             return $c->view("Error")
776             ->set_http_bad_request
777             ->detach;
778              
779             Response helpers are just lowercased names you'll find for the codes listed in L<HTTP::Status>.
780             Some of the most common ones I find in my code:
781              
782             http_ok
783             http_created
784             http_bad_request
785             http_unauthorized
786             http_not_found
787             http_internal_server_error
788              
789             By default we create response helpers for all the status codes in L<HTTP::Status>. However
790             if you set the C<status_codes> configuration key (see L</status_codes>) you can limit the
791             generated helpers to specific codes. This can be useful since most views are only meaningful
792             with a limited set of response codes.
793              
794             =head1 RUNTIME HOOKS
795            
796             This class defines the following method hooks you may optionally defined in your
797             view subclass in order to control or otherwise influence how the view works.
798            
799             =head2 $class->modify_init_args($app, $args)
800            
801             Runs when C<COMPONENT> is called during C<setup_components>. This gets a reference
802             to the merged arguments from all configuration. You should return this reference
803             after modification.
804              
805             This is for modifying or adding arguments that are application scoped rather than context
806             scoped.
807              
808             =head2 prepare_build_args
809              
810             This method will be called (if defined) by the factory class during build time. It can be used
811             to inject args and modify args. It gets the context and C<@args> as arguments and should return
812             all the arguments you want to pass to C<new>. Example:
813              
814             sub prepare_build_args {
815             my ($class, $c, @args) = @_;
816             # Mess with @args
817             return @args;
818             }
819              
820             =head2 build
821              
822             Receives the initialization hash and should return a new instance of the the view. By default this
823             just calls C<new> on the class with the hash of args but if you need to call some other method or
824             have some complex initialization work that can't be handled with L</prepare_build_args> you can
825             override.
826              
827             =head1 CONFIGURATION
828            
829             This Catalyst Component supports the following configuation.
830            
831             =head2 content_type
832              
833             The HTTP content type of the response. For example 'text/html'. Required.
834              
835             =head2 status_codes
836            
837             An ArrayRef of HTTP status codes used to provide response helpers. This is optional
838             but it allows you to specify the permitted HTTP response codes that a template can
839             generate. for example a NotFound view probably makes no sense to return anything
840             other than a 404 Not Found code.
841              
842             =head2 lifecycle
843              
844             By default your view lifecycle is 'per request' which means we only build it one during the entire
845             request cycle. This is handled by the lifecycle module L<Catalyst::View::BasePerRequest::Lifecycle::Request>.
846             However sometimes you would like your view to be build newly each you you request it. For example
847             you might have a view called from inside a loop, passing it different arguments each time. In that
848             case you want the 'Factory' lifecycle, which is handled by L<Catalyst::View::BasePerRequest::Lifecycle::Factory>.
849             In order to do that set this configuration value to 'Factory'. For example
850              
851             __PACKAGE__->config(
852             content_type => 'text/html',
853             status_codes => [200,201,400],
854             lifecycle => 'Factory',
855             );
856              
857             You can create your own lifecycle classes, but that's very advanced so for now if you
858             want to do that you should review the source of the existing ones. F<D-d>or example you might
859             create a view with a 'session' lifecycle, which returns the same view as long as the user
860             is logged in.
861              
862             =head1 ALSO SEE
863            
864             L<Catalyst>
865              
866             =head1 AUTHORS & COPYRIGHT
867            
868             John Napiorkowski L<email:jjnapiork@cpan.org>
869            
870             =head1 LICENSE
871            
872             Copyright 2023, John Napiorkowski L<email:jjnapiork@cpan.org>
873            
874             This library is free software; you can redistribute it and/or modify
875             it under the same terms as Perl itself.
876            
877             =cut