1 package Catalyst::Engine;
4 use base qw/Class::Data::Inheritable Class::Accessor::Fast/;
5 use UNIVERSAL::require;
9 use Time::HiRes qw/gettimeofday tv_interval/;
10 use Catalyst::Request;
11 use Catalyst::Response;
13 require Module::Pluggable::Fast;
15 $Data::Dumper::Terse = 1;
17 __PACKAGE__->mk_classdata($_) for qw/actions components/;
18 __PACKAGE__->mk_accessors(qw/request response/);
21 { plain => {}, regex => {}, compiled => {}, reverse => {} } );
32 Catalyst::Engine - The Catalyst Engine
44 =item $c->action( $name => $coderef, ... )
46 =item $c->action( $name )
50 Add one or more actions.
52 $c->action( '!foo' => sub { $_[1]->res->output('Foo!') } );
54 Get an action's class and coderef.
56 my ($class, $code) = @{ $c->action('foo') };
58 Get a list of available actions.
60 my @actions = $c->action;
62 It also automatically calls setup() if needed.
64 See L<Catalyst::Manual::Intro> for more informations about actions.
70 $self->setup unless $self->components;
71 $self->actions( {} ) unless $self->actions;
73 $_[1] ? ( $action = {@_} ) : ( $action = shift );
74 if ( ref $action eq 'HASH' ) {
75 while ( my ( $name, $code ) = each %$action ) {
76 my $class = caller(0);
77 if ( $name =~ /^\/(.*)\/$/ ) {
79 $self->actions->{compiled}->{qr/$regex/} = $name;
80 $self->actions->{regex}->{$name} = [ $class, $code ];
82 elsif ( $name =~ /^\?(.*)$/ ) {
84 $name = _prefix( $class, $name );
85 $self->actions->{plain}->{$name} = [ $class, $code ];
87 elsif ( $name =~ /^\!\?(.*)$/ ) {
89 $name = _prefix( $class, $name );
91 $self->actions->{plain}->{$name} = [ $class, $code ];
93 else { $self->actions->{plain}->{$name} = [ $class, $code ] }
94 $self->actions->{reverse}->{"$code"} = $name;
95 $self->log->debug(qq/"$class" defined "$name" as "$code"/)
100 if ( my $p = $self->actions->{plain}->{$action} ) { return [$p] }
101 elsif ( my $r = $self->actions->{regex}->{$action} ) { return [$r] }
103 for my $regex ( keys %{ $self->actions->{compiled} } ) {
104 my $name = $self->actions->{compiled}->{$regex};
105 if ( $action =~ $regex ) {
107 for my $i ( 1 .. 9 ) {
110 push @snippets, ${$i};
112 return [ $self->actions->{regex}->{$name},
121 keys %{ $self->actions->{plain} },
122 keys %{ $self->actions->{regex} }
128 =item $c->benchmark($coderef)
130 Takes a coderef with arguments and returns elapsed time as float.
132 my ( $elapsed, $status ) = $c->benchmark( sub { return 1 } );
133 $c->log->info( sprintf "Processing took %f seconds", $elapsed );
140 my $time = [gettimeofday];
141 my @return = &$code(@_);
142 my $elapsed = tv_interval $time;
143 return wantarray ? ( $elapsed, @return ) : $elapsed;
146 =item $c->comp($name)
148 =item $c->component($name)
150 Get a component object by name.
152 $c->comp('MyApp::Model::MyModel')->do_stuff;
154 Regex search for a component.
156 $c->comp('mymodel')->do_stuff;
161 my ( $c, $name ) = @_;
162 if ( my $component = $c->components->{$name} ) {
166 for my $component ( keys %{ $c->components } ) {
167 return $c->components->{$component} if $component =~ /$name/i;
174 =item $c->errors($error, ...)
176 =item $c->errors($arrayref)
178 Returns an arrayref containing errors messages.
180 my @errors = @{ $c->errors };
184 $c->errors('Something bad happened');
190 my $errors = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
191 push @{ $c->{errors} }, @$errors;
204 if ( my $location = $c->res->redirect ) {
205 $c->log->debug(qq/Redirecting to "$location"/) if $c->debug;
206 $c->res->headers->header( Location => $location );
207 $c->res->status(302);
210 if ( !$c->res->output || $#{ $c->errors } >= 0 ) {
211 $c->res->headers->content_type('text/html');
212 my $name = $c->config->{name} || 'Catalyst Application';
213 my ( $title, $errors, $infos );
215 $errors = join '<br/>', @{ $c->errors };
216 $errors ||= 'No output';
217 $title = $name = "$name on Catalyst $Catalyst::VERSION";
218 my $req = encode_entities Dumper $c->req;
219 my $res = encode_entities Dumper $c->res;
220 my $stash = encode_entities Dumper $c->stash;
223 <b><u>Request</u></b><br/>
225 <b><u>Response</u></b><br/>
227 <b><u>Stash</u></b><br/>
236 (en) Please come back later
237 (de) Bitte versuchen sie es spaeter nocheinmal
238 (nl) Gelieve te komen later terug
239 (no) Vennligst prov igjen senere
240 (fr) Veuillez revenir plus tard
241 (es) Vuelto por favor mas adelante
242 (pt) Voltado por favor mais tarde
243 (it) Ritornato prego piĆ¹ successivamente
248 $c->res->{output} = <<"";
251 <title>$title</title>
252 <style type="text/css">
254 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
255 Tahoma, Arial, helvetica, sans-serif;
257 background-color: #eee;
262 background-color: #ccc;
263 border: 1px solid #aaa;
266 -moz-border-radius: 10px;
269 background-color: #977;
270 border: 1px solid #755;
274 -moz-border-radius: 10px;
277 background-color: #797;
278 border: 1px solid #575;
282 -moz-border-radius: 10px;
285 background-color: #779;
286 border: 1px solid #557;
289 -moz-border-radius: 10px;
295 <div class="errors">$errors</div>
296 <div class="infos">$infos</div>
297 <div class="name">$name</div>
303 $c->res->headers->content_length( length $c->res->output );
304 my $status = $c->finalize_headers;
309 =item $c->finalize_headers
315 sub finalize_headers { }
317 =item $c->finalize_output
323 sub finalize_output { }
325 =item $c->forward($command)
327 Forward processing to a private/public action or a method from a class.
328 If you define a class without method it will default to process().
331 $c->forward('index.html');
332 $c->forward(qw/MyApp::Model::CDBI::Foo do_stuff/);
333 $c->forward('MyApp::View::TT');
341 $c->log->debug('Nothing to forward to') if $c->debug;
344 if ( $command =~ /^\?(.*)$/ ) {
346 my $caller = caller(0);
347 $command = _prefix( $caller, $command );
349 elsif ( $command =~ /^\!\?(.*)$/ ) {
351 my $caller = caller(0);
352 $command = _prefix( $caller, $command );
353 $command = "\!$command";
355 elsif ( $command =~ /^\!(.*)$/ ) {
357 my $caller = caller(0);
358 my $prefix = _class2prefix($caller);
359 $try = "!$prefix/$command";
360 $command = $try if $c->actions->{plain}->{$try};
362 my ( $class, $code );
363 if ( my $action = $c->action($command) ) {
364 if ( $action->[2] ) {
365 $c->log->debug(qq/Couldn't forward "$command" to regex action/)
369 ( $class, $code ) = @{ $action->[0] };
373 if ( $class =~ /[^\w\:]/ ) {
374 $c->log->debug(qq/Couldn't forward to "$class"/) if $c->debug;
377 my $method = shift || 'process';
378 if ( $code = $class->can($method) ) {
379 $c->actions->{reverse}->{"$code"} = "$class->$method";
382 $c->log->debug(qq/Couldn't forward to "$class->$method"/)
387 $class = $c->components->{$class} || $class;
388 return $c->process( $class, $code );
391 =item $c->handler($r)
398 my ( $class, $r ) = @_;
400 # Always expect worst case!
404 my $c = $class->prepare($r);
405 if ( my $action = $c->action( $c->req->action ) ) {
407 my $class = ${ $action->[0] }[0];
408 my $prefix = _class2prefix($class);
410 if ( $c->actions->{plain}->{"\!$prefix/begin"} ) {
411 $begin = "\!$prefix/begin";
413 elsif ( $c->actions->{plain}->{'!begin'} ) {
416 if ( $c->actions->{plain}->{"\!$prefix/end"} ) {
417 $end = "\!$prefix/end";
419 elsif ( $c->actions->{plain}->{'!end'} ) { $end = '!end' }
422 if ( $c->actions->{plain}->{'!begin'} ) {
425 if ( $c->actions->{plain}->{'!end'} ) { $end = '!end' }
427 $c->forward($begin) if $begin;
428 $c->forward( $c->req->action ) if $c->req->action;
429 $c->forward($end) if $end;
432 my $action = $c->req->path;
434 ? qq/Unknown resource "$action"/
435 : "No default action defined";
436 $c->log->error($error) if $c->debug;
441 if ( $class->debug ) {
443 ( $elapsed, $status ) = $class->benchmark($handler);
444 $elapsed = sprintf '%f', $elapsed;
445 my $av = sprintf '%.3f', 1 / $elapsed;
446 $class->log->info( "Request took $elapsed" . "s ($av/s)" );
448 else { $status = &$handler }
450 if ( my $error = $@ ) {
452 $class->log->error(qq/Caught exception in engine "$error"/);
458 =item $c->prepare($r)
460 Turns the engine-specific request (Apache, CGI...) into a Catalyst context.
465 my ( $class, $r ) = @_;
467 request => Catalyst::Request->new(
471 headers => HTTP::Headers->new,
477 response => Catalyst::Response->new(
478 { cookies => {}, headers => HTTP::Headers->new, status => 200 }
483 my $secs = time - $START || 1;
484 my $av = sprintf '%.3f', $COUNT / $secs;
485 $c->log->debug('********************************');
486 $c->log->debug("* Request $COUNT ($av/s) [$$]");
487 $c->log->debug('********************************');
488 $c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION );
490 $c->prepare_request($r);
494 my $method = $c->req->method;
495 my $path = $c->req->path;
496 $c->log->debug(qq/"$method" request for "$path"/) if $c->debug;
498 $c->prepare_parameters;
500 if ( $c->debug && keys %{ $c->req->params } ) {
502 for my $key ( keys %{ $c->req->params } ) {
503 my $value = $c->req->params->{$key} || '';
504 push @params, "$key=$value";
506 $c->log->debug( 'Parameters are "' . join( ' ', @params ) . '"' );
512 =item $c->prepare_action
520 my $path = $c->req->path;
521 my @path = split /\//, $c->req->path;
522 $c->req->args( \my @args );
524 $path = join '/', @path;
525 if ( my $result = $c->action($path) ) {
529 my $match = $result->[1];
530 my @snippets = @{ $result->[2] };
531 $c->log->debug(qq/Requested action "$path" matched "$match"/)
534 'Snippets are "' . join( ' ', @snippets ) . '"' )
535 if ( $c->debug && @snippets );
536 $c->req->action($match);
537 $c->req->snippets( \@snippets );
540 $c->req->action($path);
541 $c->log->debug(qq/Requested action "$path"/) if $c->debug;
543 $c->req->match($path);
546 unshift @args, pop @path;
548 unless ( $c->req->action ) {
549 my $prefix = $c->req->args->[0];
550 if ( $prefix && $c->actions->{plain}->{"\!$prefix/default"} ) {
552 $c->req->action("\!$prefix/default");
553 $c->log->debug('Using prefixed default action') if $c->debug;
555 elsif ( $c->actions->{plain}->{'!default'} ) {
557 $c->req->action('!default');
558 $c->log->debug('Using default action') if $c->debug;
561 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
562 if ( $c->debug && @args );
565 =item $c->prepare_cookies;
571 sub prepare_cookies { }
573 =item $c->prepare_headers
579 sub prepare_headers { }
581 =item $c->prepare_parameters
587 sub prepare_parameters { }
589 =item $c->prepare_path
591 Prepare path and base.
597 =item $c->prepare_request
599 Prepare the engine request.
603 sub prepare_request { }
605 =item $c->prepare_uploads
611 sub prepare_uploads { }
613 =item $c->process($class, $coderef)
615 Process a coderef in given class and catch exceptions.
616 Errors are available via $c->errors.
621 my ( $c, $class, $code ) = @_;
626 my $action = $c->actions->{reverse}->{"$code"} || "$code";
628 ( $elapsed, $status ) =
629 $c->benchmark( $code, $class, $c, @{ $c->req->args } );
630 $c->log->info( sprintf qq/Processing "$action" took %fs/, $elapsed )
633 else { $status = &$code( $class, $c, @{ $c->req->args } ) }
635 if ( my $error = $@ ) {
637 $error = qq/Caught exception "$error"/;
638 $c->log->error($error);
639 $c->errors($error) if $c->debug;
645 =item $c->remove_action($action)
649 $c->remove_action('!foo');
654 my ( $self, $action ) = @_;
655 if ( delete $self->actions->{regex}->{$action} ) {
656 while ( my ( $regex, $name ) = each %{ $self->actions->{compiled} } ) {
657 if ( $name eq $action ) {
658 delete $self->actions->{compiled}->{$regex};
664 delete $self->actions->{plain}->{$action};
672 Returns a C<Catalyst::Request> object.
680 Returns a C<Catalyst::Response> object.
694 $self->setup_components;
695 if ( $self->debug ) {
696 my $name = $self->config->{name} || 'Application';
697 $self->log->info("$name powered by Catalyst $Catalyst::VERSION");
701 =item $class->setup_components
707 sub setup_components {
711 my $class = ref $self || $self;
714 import Module::Pluggable::Fast
715 name => '_components',
717 '$class\::Controller', '$class\::C',
718 '$class\::Model', '$class\::M',
719 '$class\::View', '$class\::V'
722 if ( my $error = $@ ) {
725 qq/Couldn't initialize "Module::Pluggable::Fast", "$error"/);
727 $self->components( {} );
728 for my $component ( $self->_components($self) ) {
729 $self->components->{ ref $component } = $component;
731 $self->log->debug( 'Initialized components "'
732 . join( ' ', keys %{ $self->components } )
739 Returns a hashref containing all your data.
741 $c->stash->{foo} ||= 'yada';
742 print $c->stash->{foo};
749 my $stash = $_[1] ? {@_} : $_[0];
750 while ( my ( $key, $val ) = each %$stash ) {
751 $self->{stash}->{$key} = $val;
754 return $self->{stash};
758 my ( $class, $name ) = @_;
759 my $prefix = _class2prefix($class);
760 $name = "$prefix/$name" if $prefix;
766 $class =~ /^.*::[(M)(Model)(V)(View)(C)(Controller)]+::(.*)$/;
767 my $prefix = lc $1 || '';
768 $prefix =~ s/\:\:/_/g;
776 Sebastian Riedel, C<sri@cpan.org>
780 This program is free software, you can redistribute it and/or modify it under
781 the same terms as Perl itself.