Added $req->cookie
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Engine.pm
CommitLineData
fc7ec1d9 1package Catalyst::Engine;
2
3use strict;
424b2705 4use base qw/Class::Data::Inheritable Class::Accessor::Fast/;
d70195d8 5use attributes ();
fc7ec1d9 6use UNIVERSAL::require;
6dc87a0f 7use CGI::Cookie;
fc7ec1d9 8use Data::Dumper;
9use HTML::Entities;
10use HTTP::Headers;
11use Time::HiRes qw/gettimeofday tv_interval/;
0f7ecc53 12use Text::ASCIITable;
a2f2cde9 13use Catalyst::Exception;
fc7ec1d9 14use Catalyst::Request;
146554c5 15use Catalyst::Request::Upload;
fc7ec1d9 16use Catalyst::Response;
a268a011 17use Catalyst::Utils;
fc7ec1d9 18
19require Module::Pluggable::Fast;
20
99fe1710 21# For pretty dumps
fc7ec1d9 22$Data::Dumper::Terse = 1;
23
1abd6db7 24__PACKAGE__->mk_classdata('components');
6ef62eb2 25__PACKAGE__->mk_accessors(qw/counter depth request response state/);
fc7ec1d9 26
fc7ec1d9 27*comp = \&component;
28*req = \&request;
29*res = \&response;
30
06e1b616 31# For backwards compatibility
32*finalize_output = \&finalize_body;
33
99fe1710 34# For statistics
e88fa058 35our $COUNT = 1;
36our $START = time;
37our $RECURSION = 1000;
6ef62eb2 38our $DETACH = "catalyst_detach\n";
fc7ec1d9 39
40=head1 NAME
41
42Catalyst::Engine - The Catalyst Engine
43
44=head1 SYNOPSIS
45
46See L<Catalyst>.
47
48=head1 DESCRIPTION
49
23f9d934 50=head1 METHODS
fc7ec1d9 51
23f9d934 52=over 4
53
23f9d934 54=item $c->benchmark($coderef)
fc7ec1d9 55
56Takes a coderef with arguments and returns elapsed time as float.
57
58 my ( $elapsed, $status ) = $c->benchmark( sub { return 1 } );
59 $c->log->info( sprintf "Processing took %f seconds", $elapsed );
60
61=cut
62
63sub benchmark {
64 my $c = shift;
65 my $code = shift;
66 my $time = [gettimeofday];
67 my @return = &$code(@_);
68 my $elapsed = tv_interval $time;
69 return wantarray ? ( $elapsed, @return ) : $elapsed;
70}
71
23f9d934 72=item $c->comp($name)
73
74=item $c->component($name)
fc7ec1d9 75
76Get a component object by name.
77
78 $c->comp('MyApp::Model::MyModel')->do_stuff;
79
80Regex search for a component.
81
82 $c->comp('mymodel')->do_stuff;
83
84=cut
85
86sub component {
3245f607 87 my $c = shift;
99fe1710 88
e88fa058 89 if (@_) {
99fe1710 90
3245f607 91 my $name = shift;
92
93 if ( my $component = $c->components->{$name} ) {
94 return $component;
95 }
96
97 else {
98 for my $component ( keys %{ $c->components } ) {
99 return $c->components->{$component} if $component =~ /$name/i;
100 }
fc7ec1d9 101 }
102 }
99fe1710 103
3245f607 104 return sort keys %{ $c->components };
fc7ec1d9 105}
106
e88fa058 107=item $c->counter
108
109Returns a hashref containing coderefs and execution counts.
110(Needed for deep recursion detection)
111
6ef62eb2 112=item $c->depth
113
114Returns the actual forward depth.
115
a554cc3b 116=item $c->error
23f9d934 117
a554cc3b 118=item $c->error($error, ...)
23f9d934 119
a554cc3b 120=item $c->error($arrayref)
fc7ec1d9 121
a554cc3b 122Returns an arrayref containing error messages.
fc7ec1d9 123
a554cc3b 124 my @error = @{ $c->error };
fc7ec1d9 125
126Add a new error.
127
a554cc3b 128 $c->error('Something bad happened');
fc7ec1d9 129
130=cut
131
a554cc3b 132sub error {
fc7ec1d9 133 my $c = shift;
a554cc3b 134 my $error = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
135 push @{ $c->{error} }, @$error;
136 return $c->{error};
fc7ec1d9 137}
138
6dc87a0f 139=item $c->execute($class, $coderef)
140
141Execute a coderef in given class and catch exceptions.
142Errors are available via $c->error.
143
144=cut
145
146sub execute {
147 my ( $c, $class, $code ) = @_;
91571b7b 148 $class = $c->components->{$class} || $class;
6dc87a0f 149 $c->state(0);
39de91b0 150 my $callsub = ( caller(1) )[3];
99fe1710 151
e88fa058 152 my $action = '';
153 if ( $c->debug ) {
154 $action = $c->actions->{reverse}->{"$code"};
155 $action = "/$action" unless $action =~ /\-\>/;
156 $c->counter->{"$code"}++;
157
158 if ( $c->counter->{"$code"} > $RECURSION ) {
159 my $error = qq/Deep recursion detected in "$action"/;
160 $c->log->error($error);
161 $c->error($error);
162 $c->state(0);
163 return $c->state;
164 }
165
166 $action = "-> $action" if $callsub =~ /forward$/;
167 }
168
6ef62eb2 169 $c->{depth}++;
6dc87a0f 170 eval {
171 if ( $c->debug )
172 {
6ef62eb2 173 my ( $elapsed, @state ) =
174 $c->benchmark( $code, $class, $c, @{ $c->req->args } );
0f7ecc53 175 push @{ $c->{stats} }, [ $action, sprintf( '%fs', $elapsed ) ];
6dc87a0f 176 $c->state(@state);
177 }
3ceed047 178 else { $c->state( &$code( $class, $c, @{ $c->req->args } ) || 0 ) }
6dc87a0f 179 };
6ef62eb2 180 $c->{depth}--;
99fe1710 181
6dc87a0f 182 if ( my $error = $@ ) {
b9ffe28b 183
6ef62eb2 184 if ( $error eq $DETACH ) { die $DETACH if $c->{depth} > 1 }
185 else {
186 unless ( ref $error ) {
187 chomp $error;
188 $error = qq/Caught exception "$error"/;
189 }
b9ffe28b 190
6ef62eb2 191 $c->log->error($error);
192 $c->error($error);
193 $c->state(0);
194 }
6dc87a0f 195 }
196 return $c->state;
197}
198
23f9d934 199=item $c->finalize
fc7ec1d9 200
ca39d576 201Finalize request.
fc7ec1d9 202
203=cut
204
205sub finalize {
206 my $c = shift;
23f9d934 207
6dc87a0f 208 $c->finalize_cookies;
209
49490aab 210 if ( my $location = $c->response->redirect ) {
23f9d934 211 $c->log->debug(qq/Redirecting to "$location"/) if $c->debug;
6dc87a0f 212 $c->response->header( Location => $location );
e7c0c583 213 $c->response->status(302) if $c->response->status !~ /^3\d\d$/;
6dc87a0f 214 }
215
969647fd 216 if ( $#{ $c->error } >= 0 ) {
217 $c->finalize_error;
23f9d934 218 }
6ef62eb2 219
d290eee8 220 if ( !$c->response->body && $c->response->status == 200 ) {
969647fd 221 $c->finalize_error;
222 }
fc7ec1d9 223
d7945f32 224 if ( $c->response->body && !$c->response->content_length ) {
d290eee8 225 $c->response->content_length( bytes::length( $c->response->body ) );
226 }
6ef62eb2 227
d290eee8 228 if ( $c->response->status =~ /^(1\d\d|[23]04)$/ ) {
229 $c->response->headers->remove_header("Content-Length");
230 $c->response->body('');
231 }
6ef62eb2 232
d290eee8 233 if ( $c->request->method eq 'HEAD' ) {
234 $c->response->body('');
fc7ec1d9 235 }
969647fd 236
fc7ec1d9 237 my $status = $c->finalize_headers;
06e1b616 238 $c->finalize_body;
fc7ec1d9 239 return $status;
240}
241
cd3bb248 242=item $c->finalize_output
243
244<obsolete>, see finalize_body
245
06e1b616 246=item $c->finalize_body
247
248Finalize body.
249
250=cut
251
252sub finalize_body { }
253
6dc87a0f 254=item $c->finalize_cookies
255
256Finalize cookies.
257
258=cut
259
260sub finalize_cookies {
261 my $c = shift;
262
263 while ( my ( $name, $cookie ) = each %{ $c->response->cookies } ) {
264 my $cookie = CGI::Cookie->new(
265 -name => $name,
266 -value => $cookie->{value},
267 -expires => $cookie->{expires},
268 -domain => $cookie->{domain},
269 -path => $cookie->{path},
270 -secure => $cookie->{secure} || 0
271 );
272
273 $c->res->headers->push_header( 'Set-Cookie' => $cookie->as_string );
274 }
275}
276
969647fd 277=item $c->finalize_error
278
ca39d576 279Finalize error.
969647fd 280
281=cut
282
283sub finalize_error {
284 my $c = shift;
285
286 $c->res->headers->content_type('text/html');
287 my $name = $c->config->{name} || 'Catalyst Application';
288
289 my ( $title, $error, $infos );
290 if ( $c->debug ) {
291 $error = join '<br/>', @{ $c->error };
292 $error ||= 'No output';
293 $title = $name = "$name on Catalyst $Catalyst::VERSION";
294 my $req = encode_entities Dumper $c->req;
295 my $res = encode_entities Dumper $c->res;
296 my $stash = encode_entities Dumper $c->stash;
297 $infos = <<"";
298<br/>
299<b><u>Request</u></b><br/>
300<pre>$req</pre>
301<b><u>Response</u></b><br/>
302<pre>$res</pre>
303<b><u>Stash</u></b><br/>
304<pre>$stash</pre>
305
306 }
307 else {
308 $title = $name;
309 $error = '';
310 $infos = <<"";
311<pre>
312(en) Please come back later
313(de) Bitte versuchen sie es spaeter nocheinmal
314(nl) Gelieve te komen later terug
315(no) Vennligst prov igjen senere
316(fr) Veuillez revenir plus tard
317(es) Vuelto por favor mas adelante
318(pt) Voltado por favor mais tarde
319(it) Ritornato prego più successivamente
320</pre>
321
322 $name = '';
323 }
e060fe05 324 $c->res->body( <<"" );
969647fd 325<html>
326<head>
327 <title>$title</title>
328 <style type="text/css">
329 body {
330 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
331 Tahoma, Arial, helvetica, sans-serif;
332 color: #ddd;
333 background-color: #eee;
334 margin: 0px;
335 padding: 0px;
336 }
337 div.box {
338 background-color: #ccc;
339 border: 1px solid #aaa;
340 padding: 4px;
341 margin: 10px;
342 -moz-border-radius: 10px;
343 }
344 div.error {
345 background-color: #977;
346 border: 1px solid #755;
347 padding: 8px;
348 margin: 4px;
349 margin-bottom: 10px;
350 -moz-border-radius: 10px;
351 }
352 div.infos {
353 background-color: #797;
354 border: 1px solid #575;
355 padding: 8px;
356 margin: 4px;
357 margin-bottom: 10px;
358 -moz-border-radius: 10px;
359 }
360 div.name {
361 background-color: #779;
362 border: 1px solid #557;
363 padding: 8px;
364 margin: 4px;
365 -moz-border-radius: 10px;
366 }
367 </style>
368</head>
369<body>
370 <div class="box">
371 <div class="error">$error</div>
372 <div class="infos">$infos</div>
373 <div class="name">$name</div>
374 </div>
375</body>
376</html>
377
378}
379
23f9d934 380=item $c->finalize_headers
fc7ec1d9 381
ca39d576 382Finalize headers.
fc7ec1d9 383
384=cut
385
386sub finalize_headers { }
387
e2fd5b5f 388=item $c->handler( $class, @arguments )
fc7ec1d9 389
ca39d576 390Handles the request.
fc7ec1d9 391
392=cut
393
6dc87a0f 394sub handler {
e2fd5b5f 395 my ( $class, @arguments ) = @_;
fc7ec1d9 396
397 # Always expect worst case!
398 my $status = -1;
399 eval {
d41516b2 400 my @stats = ();
99fe1710 401
fc7ec1d9 402 my $handler = sub {
e2fd5b5f 403 my $c = $class->prepare(@arguments);
d41516b2 404 $c->{stats} = \@stats;
63b763c5 405 $c->dispatch;
fc7ec1d9 406 return $c->finalize;
407 };
99fe1710 408
fc7ec1d9 409 if ( $class->debug ) {
410 my $elapsed;
411 ( $elapsed, $status ) = $class->benchmark($handler);
412 $elapsed = sprintf '%f', $elapsed;
e88fa058 413 my $av = sprintf '%.3f',
414 ( $elapsed == 0 ? '??' : ( 1 / $elapsed ) );
0f7ecc53 415 my $t = Text::ASCIITable->new;
416 $t->setCols( 'Action', 'Time' );
3f36a3a3 417 $t->setColWidth( 'Action', 64, 1 );
418 $t->setColWidth( 'Time', 9, 1 );
0822f9a4 419
cd677e12 420 for my $stat (@stats) { $t->addRow( $stat->[0], $stat->[1] ) }
0f7ecc53 421 $class->log->info( "Request took $elapsed" . "s ($av/s)",
422 $t->draw );
fc7ec1d9 423 }
424 else { $status = &$handler }
99fe1710 425
fc7ec1d9 426 };
99fe1710 427
fc7ec1d9 428 if ( my $error = $@ ) {
429 chomp $error;
430 $class->log->error(qq/Caught exception in engine "$error"/);
431 }
99fe1710 432
fc7ec1d9 433 $COUNT++;
434 return $status;
435}
436
e2fd5b5f 437=item $c->prepare(@arguments)
fc7ec1d9 438
a554cc3b 439Turns the engine-specific request( Apache, CGI ... )
440into a Catalyst context .
fc7ec1d9 441
442=cut
443
444sub prepare {
e2fd5b5f 445 my ( $class, @arguments ) = @_;
99fe1710 446
fc7ec1d9 447 my $c = bless {
e88fa058 448 counter => {},
6ef62eb2 449 depth => 0,
fc7ec1d9 450 request => Catalyst::Request->new(
451 {
452 arguments => [],
453 cookies => {},
454 headers => HTTP::Headers->new,
455 parameters => {},
bfde09a2 456 secure => 0,
fc7ec1d9 457 snippets => [],
458 uploads => {}
459 }
460 ),
461 response => Catalyst::Response->new(
bfde09a2 462 {
03222156 463 body => '',
bfde09a2 464 cookies => {},
d290eee8 465 headers => HTTP::Headers->new( 'Content-Length' => 0 ),
03222156 466 status => 200
bfde09a2 467 }
fc7ec1d9 468 ),
b768faa3 469 stash => {},
470 state => 0
fc7ec1d9 471 }, $class;
99fe1710 472
fc7ec1d9 473 if ( $c->debug ) {
474 my $secs = time - $START || 1;
475 my $av = sprintf '%.3f', $COUNT / $secs;
1a0250cb 476 $c->log->debug('**********************************');
fc7ec1d9 477 $c->log->debug("* Request $COUNT ($av/s) [$$]");
1a0250cb 478 $c->log->debug('**********************************');
fc7ec1d9 479 $c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION );
480 }
99fe1710 481
e2fd5b5f 482 $c->prepare_request(@arguments);
bfde09a2 483 $c->prepare_connection;
ac733264 484 $c->prepare_headers;
1a80619d 485 $c->prepare_cookies;
bfde09a2 486 $c->prepare_path;
06e1b616 487 $c->prepare_action;
99fe1710 488
6ef62eb2 489 my $method = $c->req->method || '';
490 my $path = $c->req->path || '';
491 my $address = $c->req->address || '';
06e1b616 492
b4ca0ee8 493 $c->log->debug(qq/"$method" request for "$path" from $address/)
0556eb49 494 if $c->debug;
99fe1710 495
06e1b616 496 if ( $c->request->method eq 'POST' and $c->request->content_length ) {
497
498 if ( $c->req->content_type eq 'application/x-www-form-urlencoded' ) {
499 $c->prepare_parameters;
500 }
501 elsif ( $c->req->content_type eq 'multipart/form-data' ) {
502 $c->prepare_parameters;
503 $c->prepare_uploads;
504 }
505 else {
506 $c->prepare_body;
507 }
508 }
509
510 if ( $c->request->method eq 'GET' ) {
511 $c->prepare_parameters;
512 }
c85ff642 513
514 if ( $c->debug && keys %{ $c->req->params } ) {
0f7ecc53 515 my $t = Text::ASCIITable->new;
516 $t->setCols( 'Key', 'Value' );
0822f9a4 517 $t->setColWidth( 'Key', 37, 1 );
518 $t->setColWidth( 'Value', 36, 1 );
f78172f1 519 for my $key ( sort keys %{ $c->req->params } ) {
6d1ab915 520 my $param = $c->req->params->{$key};
521 my $value = defined($param) ? $param : '';
cd677e12 522 $t->addRow( $key, $value );
c85ff642 523 }
0f7ecc53 524 $c->log->debug( 'Parameters are', $t->draw );
c85ff642 525 }
99fe1710 526
fc7ec1d9 527 return $c;
528}
529
23f9d934 530=item $c->prepare_action
fc7ec1d9 531
ca39d576 532Prepare action.
fc7ec1d9 533
534=cut
535
536sub prepare_action {
537 my $c = shift;
538 my $path = $c->req->path;
539 my @path = split /\//, $c->req->path;
540 $c->req->args( \my @args );
99fe1710 541
fc7ec1d9 542 while (@path) {
7833fdfc 543 $path = join '/', @path;
0169d3a8 544 if ( my $result = ${ $c->get_action($path) }[0] ) {
fc7ec1d9 545
546 # It's a regex
547 if ($#$result) {
7e5adedd 548 my $match = $result->[1];
549 my @snippets = @{ $result->[2] };
81f6fc50 550 $c->log->debug(
551 qq/Requested action is "$path" and matched "$match"/)
fc7ec1d9 552 if $c->debug;
553 $c->log->debug(
554 'Snippets are "' . join( ' ', @snippets ) . '"' )
555 if ( $c->debug && @snippets );
556 $c->req->action($match);
557 $c->req->snippets( \@snippets );
558 }
99fe1710 559
fc7ec1d9 560 else {
561 $c->req->action($path);
81f6fc50 562 $c->log->debug(qq/Requested action is "$path"/) if $c->debug;
fc7ec1d9 563 }
99fe1710 564
fc7ec1d9 565 $c->req->match($path);
fc7ec1d9 566 last;
567 }
568 unshift @args, pop @path;
569 }
99fe1710 570
fc7ec1d9 571 unless ( $c->req->action ) {
ac733264 572 $c->req->action('default');
87e67021 573 $c->req->match('');
fc7ec1d9 574 }
99fe1710 575
5783a9a5 576 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
577 if ( $c->debug && @args );
fc7ec1d9 578}
579
06e1b616 580=item $c->prepare_body
581
582Prepare message body.
583
584=cut
585
586sub prepare_body { }
587
c9afa5fc 588=item $c->prepare_connection
0556eb49 589
ca39d576 590Prepare connection.
0556eb49 591
592=cut
593
594sub prepare_connection { }
595
c9afa5fc 596=item $c->prepare_cookies
fc7ec1d9 597
ca39d576 598Prepare cookies.
fc7ec1d9 599
600=cut
601
6dc87a0f 602sub prepare_cookies {
603 my $c = shift;
604
605 if ( my $header = $c->request->header('Cookie') ) {
606 $c->req->cookies( { CGI::Cookie->parse($header) } );
607 }
608}
fc7ec1d9 609
23f9d934 610=item $c->prepare_headers
fc7ec1d9 611
ca39d576 612Prepare headers.
fc7ec1d9 613
614=cut
615
616sub prepare_headers { }
617
23f9d934 618=item $c->prepare_parameters
fc7ec1d9 619
ca39d576 620Prepare parameters.
fc7ec1d9 621
622=cut
623
624sub prepare_parameters { }
625
23f9d934 626=item $c->prepare_path
fc7ec1d9 627
ca39d576 628Prepare path and base.
fc7ec1d9 629
630=cut
631
632sub prepare_path { }
633
23f9d934 634=item $c->prepare_request
fc7ec1d9 635
ca39d576 636Prepare the engine request.
fc7ec1d9 637
638=cut
639
640sub prepare_request { }
641
23f9d934 642=item $c->prepare_uploads
fc7ec1d9 643
ca39d576 644Prepare uploads.
fc7ec1d9 645
646=cut
647
648sub prepare_uploads { }
649
c9afa5fc 650=item $c->run
651
ca39d576 652Starts the engine.
c9afa5fc 653
654=cut
655
656sub run { }
657
61b1e958 658=item $c->request
fc7ec1d9 659
ca39d576 660=item $c->req
23f9d934 661
ca39d576 662Returns a C<Catalyst::Request> object.
fc7ec1d9 663
ca39d576 664 my $req = $c->req;
61b1e958 665
666=item $c->response
667
ca39d576 668=item $c->res
669
fc7ec1d9 670Returns a C<Catalyst::Response> object.
671
672 my $res = $c->res;
673
23f9d934 674=item $class->setup
fc7ec1d9 675
ca39d576 676Setup.
fc7ec1d9 677
678 MyApp->setup;
679
680=cut
681
682sub setup {
683 my $self = shift;
a268a011 684
685 # Initialize our data structure
6ef62eb2 686 $self->components( {} );
a268a011 687
fc7ec1d9 688 $self->setup_components;
a268a011 689
690 if ( $self->debug ) {
691 my $t = Text::ASCIITable->new;
6ef62eb2 692 $t->setOptions( 'hide_HeadRow', 1 );
a268a011 693 $t->setOptions( 'hide_HeadLine', 1 );
694 $t->setCols('Class');
695 $t->setColWidth( 'Class', 75, 1 );
696 $t->addRow($_) for sort keys %{ $self->components };
697 $self->log->debug( 'Loaded components', $t->draw )
698 if ( @{ $t->{tbl_rows} } );
699 }
6ef62eb2 700
a268a011 701 # Add our self to components, since we are also a component
6ef62eb2 702 $self->components->{$self} = $self;
a268a011 703
704 $self->setup_actions;
705
fc7ec1d9 706 if ( $self->debug ) {
707 my $name = $self->config->{name} || 'Application';
708 $self->log->info("$name powered by Catalyst $Catalyst::VERSION");
709 }
710}
711
23f9d934 712=item $class->setup_components
fc7ec1d9 713
ca39d576 714Setup components.
fc7ec1d9 715
716=cut
717
718sub setup_components {
719 my $self = shift;
6ef62eb2 720
a268a011 721 my $callback = sub {
722 my ( $component, $context ) = @_;
6ef62eb2 723
a268a011 724 unless ( $component->isa('Catalyst::Base') ) {
725 return $component;
726 }
727
728 my $suffix = Catalyst::Utils::class2classsuffix($component);
729 my $config = $self->config->{$suffix} || {};
730
731 my $instance;
732
6ef62eb2 733 eval { $instance = $component->new( $context, $config ); };
a268a011 734
846772b7 735 if ( my $error = $@ ) {
a2f2cde9 736
846772b7 737 chomp $error;
a2f2cde9 738
739 Catalyst::Exception->throw(
740 message => qq/Couldn't instantiate component "$component", "$error"/
741 );
a268a011 742 }
743
744 return $instance;
745 };
746
747 eval {
748 Module::Pluggable::Fast->import(
6ef62eb2 749 name => '_components',
750 search => [
a268a011 751 "$self\::Controller", "$self\::C",
752 "$self\::Model", "$self\::M",
753 "$self\::View", "$self\::V"
754 ],
755 callback => $callback
756 );
757 };
3245f607 758
b18987fe 759 if ( my $error = $@ ) {
a2f2cde9 760
b18987fe 761 chomp $error;
a2f2cde9 762
763 Catalyst::Exception->throw(
764 message => qq/Couldn't load components "$error"/
765 );
b18987fe 766 }
99fe1710 767
a268a011 768 for my $component ( $self->_components($self) ) {
769 $self->components->{ ref $component || $component } = $component;
3245f607 770 }
fc7ec1d9 771}
772
63b763c5 773=item $c->state
774
775Contains the return value of the last executed action.
776
23f9d934 777=item $c->stash
fc7ec1d9 778
ca39d576 779Returns a hashref containing all your data.
fc7ec1d9 780
781 $c->stash->{foo} ||= 'yada';
782 print $c->stash->{foo};
783
784=cut
785
786sub stash {
787 my $self = shift;
e88fa058 788 if (@_) {
c19e2f4a 789 my $stash = @_ > 1 ? {@_} : $_[0];
fc7ec1d9 790 while ( my ( $key, $val ) = each %$stash ) {
791 $self->{stash}->{$key} = $val;
792 }
793 }
794 return $self->{stash};
795}
796
23f9d934 797=back
798
fc7ec1d9 799=head1 AUTHOR
800
801Sebastian Riedel, C<sri@cpan.org>
802
803=head1 COPYRIGHT
804
805This program is free software, you can redistribute it and/or modify it under
806the same terms as Perl itself.
807
808=cut
809
8101;