1 package Catalyst::Engine;
4 use base qw/Class::Data::Inheritable Class::Accessor::Fast/;
5 use UNIVERSAL::require;
10 use Time::HiRes qw/gettimeofday tv_interval/;
12 use Tree::Simple::Visitor::FindByPath;
13 use Catalyst::Request;
14 use Catalyst::Response;
16 require Module::Pluggable::Fast;
18 $Data::Dumper::Terse = 1;
20 __PACKAGE__->mk_classdata($_) for qw/actions components tree/;
21 __PACKAGE__->mk_accessors(qw/request response state/);
24 { plain => {}, private => {}, regex => {}, compiled => {}, reverse => {} }
26 __PACKAGE__->tree( Tree::Simple->new( 0, Tree::Simple->ROOT ) );
35 memoize('_class2prefix');
39 Catalyst::Engine - The Catalyst Engine
51 =item $c->action( $name => $coderef, ... )
53 Add one or more actions.
55 $c->action( '!foo' => sub { $_[1]->res->output('Foo!') } );
57 It also automatically calls setup() if needed.
59 See L<Catalyst::Manual::Intro> for more informations about actions.
65 $self->setup unless $self->components;
66 $self->actions( {} ) unless $self->actions;
68 $_[1] ? ( $action = {@_} ) : ( $action = shift );
69 if ( ref $action eq 'HASH' ) {
70 while ( my ( $name, $code ) = each %$action ) {
71 $self->set_action( $name, $code, caller(0) );
77 =item $c->benchmark($coderef)
79 Takes a coderef with arguments and returns elapsed time as float.
81 my ( $elapsed, $status ) = $c->benchmark( sub { return 1 } );
82 $c->log->info( sprintf "Processing took %f seconds", $elapsed );
89 my $time = [gettimeofday];
90 my @return = &$code(@_);
91 my $elapsed = tv_interval $time;
92 return wantarray ? ( $elapsed, @return ) : $elapsed;
97 =item $c->component($name)
99 Get a component object by name.
101 $c->comp('MyApp::Model::MyModel')->do_stuff;
103 Regex search for a component.
105 $c->comp('mymodel')->do_stuff;
110 my ( $c, $name ) = @_;
111 if ( my $component = $c->components->{$name} ) {
115 for my $component ( keys %{ $c->components } ) {
116 return $c->components->{$component} if $component =~ /$name/i;
123 =item $c->errors($error, ...)
125 =item $c->errors($arrayref)
127 Returns an arrayref containing errors messages.
129 my @errors = @{ $c->errors };
133 $c->errors('Something bad happened');
139 my $errors = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
140 push @{ $c->{errors} }, @$errors;
153 if ( my $location = $c->res->redirect ) {
154 $c->log->debug(qq/Redirecting to "$location"/) if $c->debug;
155 $c->res->headers->header( Location => $location );
156 $c->res->status(302);
159 if ( !$c->res->output || $#{ $c->errors } >= 0 ) {
160 $c->res->headers->content_type('text/html');
161 my $name = $c->config->{name} || 'Catalyst Application';
162 my ( $title, $errors, $infos );
164 $errors = join '<br/>', @{ $c->errors };
165 $errors ||= 'No output';
166 $title = $name = "$name on Catalyst $Catalyst::VERSION";
167 my $req = encode_entities Dumper $c->req;
168 my $res = encode_entities Dumper $c->res;
169 my $stash = encode_entities Dumper $c->stash;
172 <b><u>Request</u></b><br/>
174 <b><u>Response</u></b><br/>
176 <b><u>Stash</u></b><br/>
185 (en) Please come back later
186 (de) Bitte versuchen sie es spaeter nocheinmal
187 (nl) Gelieve te komen later terug
188 (no) Vennligst prov igjen senere
189 (fr) Veuillez revenir plus tard
190 (es) Vuelto por favor mas adelante
191 (pt) Voltado por favor mais tarde
192 (it) Ritornato prego piĆ¹ successivamente
197 $c->res->{output} = <<"";
200 <title>$title</title>
201 <style type="text/css">
203 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
204 Tahoma, Arial, helvetica, sans-serif;
206 background-color: #eee;
211 background-color: #ccc;
212 border: 1px solid #aaa;
215 -moz-border-radius: 10px;
218 background-color: #977;
219 border: 1px solid #755;
223 -moz-border-radius: 10px;
226 background-color: #797;
227 border: 1px solid #575;
231 -moz-border-radius: 10px;
234 background-color: #779;
235 border: 1px solid #557;
238 -moz-border-radius: 10px;
244 <div class="errors">$errors</div>
245 <div class="infos">$infos</div>
246 <div class="name">$name</div>
252 $c->res->headers->content_length( length $c->res->output );
253 my $status = $c->finalize_headers;
258 =item $c->finalize_headers
264 sub finalize_headers { }
266 =item $c->finalize_output
272 sub finalize_output { }
274 =item $c->forward($command)
276 Forward processing to a private/public action or a method from a class.
277 If you define a class without method it will default to process().
280 $c->forward('index.html');
281 $c->forward(qw/MyApp::Model::CDBI::Foo do_stuff/);
282 $c->forward('MyApp::View::TT');
290 $c->log->debug('Nothing to forward to') if $c->debug;
293 my $caller = caller(0);
294 if ( $command =~ /^\?(.*)$/ ) {
296 $command = _prefix( $caller, $command );
299 if ( $command =~ /^\!/ ) {
300 $namespace = _class2prefix($caller);
302 my $results = $c->get_action( $command, $namespace );
304 if ( $command =~ /^\!/ ) {
305 for my $result ( @{$results} ) {
306 my ( $class, $code ) = @{ $result->[0] };
307 $c->state( $c->process( $class, $code ) );
311 return 0 unless my $result = $results->[0];
312 if ( $result->[2] ) {
313 $c->log->debug(qq/Couldn't forward "$command" to regex action/)
317 my ( $class, $code ) = @{ $result->[0] };
318 $class = $c->components->{$class} || $class;
319 $c->state( $c->process( $class, $code ) );
323 my $class = $command;
324 if ( $class =~ /[^\w\:]/ ) {
325 $c->log->debug(qq/Couldn't forward to "$class"/) if $c->debug;
328 my $method = shift || 'process';
329 if ( my $code = $class->can($method) ) {
330 $c->actions->{reverse}->{"$code"} = "$class->$method";
331 $class = $c->comp($class) || $class;
332 $c->state( $c->process( $class, $code ) );
335 $c->log->debug(qq/Couldn't forward to "$class->$method"/)
343 =item $c->get_action( $action, $namespace )
345 Get an action in a given namespace.
350 my ( $c, $action, $namespace ) = @_;
352 if ( $action =~ /^\!(.*)/ ) {
354 my $parent = $c->tree;
356 my $result = $c->actions->{private}->{ $parent->getUID }->{$action};
357 push @results, [$result] if $result;
358 my $visitor = Tree::Simple::Visitor::FindByPath->new;
360 for my $part ( split '/', $namespace ) {
362 $visitor->setSearchPath($part);
363 $parent->accept($visitor);
364 my $child = $visitor->getResult;
365 my $uid = $child->getUID if $child;
366 my $match = $c->actions->{private}->{$uid}->{$action} if $uid;
367 return [ [$match] ] if ( $match && $match =~ /^?.*/ );
368 $local = $c->actions->{private}->{$uid}->{"?$action"} if $uid;
369 push @results, [$match] if $match;
370 $parent = $child if $child;
372 return [ [$local] ] if $local;
375 elsif ( my $p = $c->actions->{plain}->{$action} ) { return [ [$p] ] }
376 elsif ( my $r = $c->actions->{regex}->{$action} ) { return [ [$r] ] }
378 for my $regex ( keys %{ $c->actions->{compiled} } ) {
379 my $name = $c->actions->{compiled}->{$regex};
380 if ( $action =~ $regex ) {
382 for my $i ( 1 .. 9 ) {
385 push @snippets, ${$i};
387 return [ [ $c->actions->{regex}->{$name}, $name, \@snippets ] ];
394 =item $c->handler( $class, $r )
401 my ( $class, $r ) = @_;
403 # Always expect worst case!
407 my $c = $class->prepare($r);
408 my $action = $c->req->action;
410 $namespace = join '/', @{ $c->req->args } if $action eq '!default';
411 unless ($namespace) {
412 if ( my $result = $c->get_action($action) ) {
413 $namespace = _class2prefix( $result->[0]->[0]->[0] );
416 my $results = $c->get_action( $action, $namespace );
418 for my $begin ( @{ $c->get_action( '!begin', $namespace ) } ) {
419 $c->state( $c->process( @{ $begin->[0] } ) );
421 for my $result ( @{ $c->get_action( $action, $namespace ) } ) {
422 $c->state( $c->process( @{ $result->[0] } ) );
424 for my $end ( @{ $c->get_action( '!end', $namespace ) } ) {
425 $c->state( $c->process( @{ $end->[0] } ) );
429 my $path = $c->req->path;
431 ? qq/Unknown resource "$path"/
432 : "No default action defined";
433 $c->log->error($error) if $c->debug;
438 if ( $class->debug ) {
440 ( $elapsed, $status ) = $class->benchmark($handler);
441 $elapsed = sprintf '%f', $elapsed;
442 my $av = sprintf '%.3f', 1 / $elapsed;
443 $class->log->info( "Request took $elapsed" . "s ($av/s)" );
445 else { $status = &$handler }
447 if ( my $error = $@ ) {
449 $class->log->error(qq/Caught exception in engine "$error"/);
455 =item $c->prepare($r)
457 Turns the engine-specific request (Apache, CGI...) into a Catalyst context.
462 my ( $class, $r ) = @_;
464 request => Catalyst::Request->new(
468 headers => HTTP::Headers->new,
474 response => Catalyst::Response->new(
475 { cookies => {}, headers => HTTP::Headers->new, status => 200 }
481 my $secs = time - $START || 1;
482 my $av = sprintf '%.3f', $COUNT / $secs;
483 $c->log->debug('********************************');
484 $c->log->debug("* Request $COUNT ($av/s) [$$]");
485 $c->log->debug('********************************');
486 $c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION );
488 $c->prepare_request($r);
492 $c->prepare_connection;
493 my $method = $c->req->method || '';
494 my $path = $c->req->path || '';
495 my $hostname = $c->req->hostname || '';
496 my $address = $c->req->address || '';
497 $c->log->debug(qq/"$method" request for "$path" from $hostname($address)/)
500 $c->prepare_parameters;
502 if ( $c->debug && keys %{ $c->req->params } ) {
504 for my $key ( keys %{ $c->req->params } ) {
505 my $value = $c->req->params->{$key} || '';
506 push @params, "$key=$value";
508 $c->log->debug( 'Parameters are "' . join( ' ', @params ) . '"' );
514 =item $c->prepare_action
522 my $path = $c->req->path;
523 my @path = split /\//, $c->req->path;
524 $c->req->args( \my @args );
526 $path = join '/', @path;
527 if ( my $result = ${ $c->get_action($path) }[0] ) {
531 my $match = $result->[1];
532 my @snippets = @{ $result->[2] };
533 $c->log->debug(qq/Requested action "$path" matched "$match"/)
536 'Snippets are "' . join( ' ', @snippets ) . '"' )
537 if ( $c->debug && @snippets );
538 $c->req->action($match);
539 $c->req->snippets( \@snippets );
542 $c->req->action($path);
543 $c->log->debug(qq/Requested action "$path"/) if $c->debug;
545 $c->req->match($path);
548 unshift @args, pop @path;
550 unless ( $c->req->action ) {
551 $c->req->action('!default');
554 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
555 if ( $c->debug && @args );
558 =item $c->prepare_connection
564 sub prepare_connection { }
566 =item $c->prepare_cookies
572 sub prepare_cookies { }
574 =item $c->prepare_headers
580 sub prepare_headers { }
582 =item $c->prepare_parameters
588 sub prepare_parameters { }
590 =item $c->prepare_path
592 Prepare path and base.
598 =item $c->prepare_request
600 Prepare the engine request.
604 sub prepare_request { }
606 =item $c->prepare_uploads
612 sub prepare_uploads { }
614 =item $c->process($class, $coderef)
616 Process a coderef in given class and catch exceptions.
617 Errors are available via $c->errors.
622 my ( $c, $class, $code ) = @_;
627 my $action = $c->actions->{reverse}->{"$code"} || "$code";
629 ( $elapsed, $status ) =
630 $c->benchmark( $code, $class, $c, @{ $c->req->args } );
631 $c->log->info( sprintf qq/Processing "$action" took %fs/, $elapsed )
634 else { $status = &$code( $class, $c, @{ $c->req->args } ) }
636 if ( my $error = $@ ) {
638 $error = qq/Caught exception "$error"/;
639 $c->log->error($error);
640 $c->errors($error) if $c->debug;
658 Returns a C<Catalyst::Request> object.
666 Returns a C<Catalyst::Response> object.
670 =item $c->set_action( $action, $code, $namespace )
672 Set an action in a given namespace.
677 my ( $c, $action, $code, $namespace ) = @_;
679 if ( $action =~ /^\?(.*)$/ ) {
680 my $prefix = $1 || '';
682 $action = $prefix . _prefix( $namespace, $action );
683 $c->actions->{plain}->{$action} = [ $namespace, $code ];
685 elsif ( $action =~ /^\/(.*)\/$/ ) {
687 $c->actions->{compiled}->{qr#$regex#} = $action;
688 $c->actions->{regex}->{$action} = [ $namespace, $code ];
690 elsif ( $action =~ /^\!(.*)$/ ) {
692 my $parent = $c->tree;
693 my $visitor = Tree::Simple::Visitor::FindByPath->new;
694 $prefix = _class2prefix($namespace);
695 for my $part ( split '/', $prefix ) {
696 $visitor->setSearchPath($part);
697 $parent->accept($visitor);
698 my $child = $visitor->getResult;
700 $child = $parent->addChild( Tree::Simple->new($part) );
701 $visitor->setSearchPath($part);
702 $parent->accept($visitor);
703 $child = $visitor->getResult;
707 my $uid = $parent->getUID;
708 $c->actions->{private}->{$uid}->{$action} = [ $namespace, $code ];
709 $action = "!$action";
711 else { $c->actions->{plain}->{$action} = [ $namespace, $code ] }
712 my $reverse = $prefix ? "$action ($prefix)" : $action;
713 $c->actions->{reverse}->{"$code"} = $reverse;
714 $c->log->debug(qq/"$namespace" defined "$action" as "$code"/) if $c->debug;
727 $self->setup_components;
728 if ( $self->debug ) {
729 my $name = $self->config->{name} || 'Application';
730 $self->log->info("$name powered by Catalyst $Catalyst::VERSION");
734 =item $class->setup_components
740 sub setup_components {
744 my $class = ref $self || $self;
747 import Module::Pluggable::Fast
748 name => '_components',
750 '$class\::Controller', '$class\::C',
751 '$class\::Model', '$class\::M',
752 '$class\::View', '$class\::V'
755 if ( my $error = $@ ) {
758 qq/Couldn't initialize "Module::Pluggable::Fast", "$error"/);
760 $self->components( {} );
761 for my $component ( $self->_components($self) ) {
762 $self->components->{ ref $component } = $component;
764 $self->log->debug( 'Initialized components "'
765 . join( ' ', keys %{ $self->components } )
772 Returns a hashref containing all your data.
774 $c->stash->{foo} ||= 'yada';
775 print $c->stash->{foo};
782 my $stash = $_[1] ? {@_} : $_[0];
783 while ( my ( $key, $val ) = each %$stash ) {
784 $self->{stash}->{$key} = $val;
787 return $self->{stash};
791 my ( $class, $name ) = @_;
792 my $prefix = _class2prefix($class);
793 warn "$class - $name - $prefix";
794 $name = "$prefix/$name" if $prefix;
799 my $class = shift || '';
800 $class =~ /^.*::([MVC]|Model|View|Controller)?::(.*)$/;
801 my $prefix = lc $2 || '';
802 $prefix =~ s/\:\:/\//g;
810 Sebastian Riedel, C<sri@cpan.org>
814 This program is free software, you can redistribute it and/or modify it under
815 the same terms as Perl itself.