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 $local = $c->actions->{private}->{$uid}->{"?$action"} if $uid;
368 push @results, [$match] if $match;
369 $parent = $child if $child;
371 return [ [$local] ] if $local;
374 elsif ( my $p = $c->actions->{plain}->{$action} ) { return [ [$p] ] }
375 elsif ( my $r = $c->actions->{regex}->{$action} ) { return [ [$r] ] }
377 for my $regex ( keys %{ $c->actions->{compiled} } ) {
378 my $name = $c->actions->{compiled}->{$regex};
379 if ( $action =~ $regex ) {
381 for my $i ( 1 .. 9 ) {
384 push @snippets, ${$i};
386 return [ [ $c->actions->{regex}->{$name}, $name, \@snippets ] ];
393 =item $c->handler( $class, $r )
400 my ( $class, $r ) = @_;
402 # Always expect worst case!
406 my $c = $class->prepare($r);
407 my $action = $c->req->action;
409 $namespace = join '/', @{ $c->req->args } if $action eq '!default';
410 unless ($namespace) {
411 if ( my $result = $c->get_action($action) ) {
412 $namespace = _class2prefix( $result->[0]->[0]->[0] );
415 my $results = $c->get_action( $action, $namespace );
417 for my $begin ( @{ $c->get_action( '!begin', $namespace ) } ) {
418 $c->state( $c->process( @{ $begin->[0] } ) );
420 for my $result ( @{ $c->get_action( $action, $namespace ) } ) {
421 $c->state( $c->process( @{ $result->[0] } ) );
423 for my $end ( @{ $c->get_action( '!end', $namespace ) } ) {
424 $c->state( $c->process( @{ $end->[0] } ) );
428 my $path = $c->req->path;
430 ? qq/Unknown resource "$path"/
431 : "No default action defined";
432 $c->log->error($error) if $c->debug;
437 if ( $class->debug ) {
439 ( $elapsed, $status ) = $class->benchmark($handler);
440 $elapsed = sprintf '%f', $elapsed;
441 my $av = sprintf '%.3f', 1 / $elapsed;
442 $class->log->info( "Request took $elapsed" . "s ($av/s)" );
444 else { $status = &$handler }
446 if ( my $error = $@ ) {
448 $class->log->error(qq/Caught exception in engine "$error"/);
454 =item $c->prepare($r)
456 Turns the engine-specific request (Apache, CGI...) into a Catalyst context.
461 my ( $class, $r ) = @_;
463 request => Catalyst::Request->new(
467 headers => HTTP::Headers->new,
473 response => Catalyst::Response->new(
474 { cookies => {}, headers => HTTP::Headers->new, status => 200 }
480 my $secs = time - $START || 1;
481 my $av = sprintf '%.3f', $COUNT / $secs;
482 $c->log->debug('********************************');
483 $c->log->debug("* Request $COUNT ($av/s) [$$]");
484 $c->log->debug('********************************');
485 $c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION );
487 $c->prepare_request($r);
491 $c->prepare_connection;
492 my $method = $c->req->method || '';
493 my $path = $c->req->path || '';
494 my $hostname = $c->req->hostname || '';
495 my $address = $c->req->address || '';
496 $c->log->debug(qq/"$method" request for "$path" from $hostname($address)/)
499 $c->prepare_parameters;
501 if ( $c->debug && keys %{ $c->req->params } ) {
503 for my $key ( keys %{ $c->req->params } ) {
504 my $value = $c->req->params->{$key} || '';
505 push @params, "$key=$value";
507 $c->log->debug( 'Parameters are "' . join( ' ', @params ) . '"' );
513 =item $c->prepare_action
521 my $path = $c->req->path;
522 my @path = split /\//, $c->req->path;
523 $c->req->args( \my @args );
525 $path = join '/', @path;
526 if ( my $result = ${ $c->get_action($path) }[0] ) {
530 my $match = $result->[1];
531 my @snippets = @{ $result->[2] };
532 $c->log->debug(qq/Requested action "$path" matched "$match"/)
535 'Snippets are "' . join( ' ', @snippets ) . '"' )
536 if ( $c->debug && @snippets );
537 $c->req->action($match);
538 $c->req->snippets( \@snippets );
541 $c->req->action($path);
542 $c->log->debug(qq/Requested action "$path"/) if $c->debug;
544 $c->req->match($path);
547 unshift @args, pop @path;
549 unless ( $c->req->action ) {
550 $c->req->action('!default');
553 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
554 if ( $c->debug && @args );
557 =item $c->prepare_connection
563 sub prepare_connection { }
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;
657 Returns a C<Catalyst::Request> object.
665 Returns a C<Catalyst::Response> object.
669 =item $c->set_action( $action, $code, $namespace )
671 Set an action in a given namespace.
676 my ( $c, $action, $code, $namespace ) = @_;
678 if ( $action =~ /^\?(.*)$/ ) {
679 my $prefix = $1 || '';
681 $action = $prefix . _prefix( $namespace, $action );
682 $c->actions->{plain}->{$action} = [ $namespace, $code ];
684 elsif ( $action =~ /^\/(.*)\/$/ ) {
686 $c->actions->{compiled}->{qr#$regex#} = $action;
687 $c->actions->{regex}->{$action} = [ $namespace, $code ];
689 elsif ( $action =~ /^\!(.*)$/ ) {
691 my $parent = $c->tree;
692 my $visitor = Tree::Simple::Visitor::FindByPath->new;
693 $prefix = _class2prefix($namespace);
694 for my $part ( split '/', $prefix ) {
695 $visitor->setSearchPath($part);
696 $parent->accept($visitor);
697 my $child = $visitor->getResult;
699 $child = $parent->addChild( Tree::Simple->new($part) );
700 $visitor->setSearchPath($part);
701 $parent->accept($visitor);
702 $child = $visitor->getResult;
706 my $uid = $parent->getUID;
707 $c->actions->{private}->{$uid}->{$action} = [ $namespace, $code ];
708 $action = "!$action";
710 else { $c->actions->{plain}->{$action} = [ $namespace, $code ] }
711 my $reverse = $prefix ? "$action ($prefix)" : $action;
712 $c->actions->{reverse}->{"$code"} = $reverse;
713 $c->log->debug(qq/"$namespace" defined "$action" as "$code"/) if $c->debug;
726 $self->setup_components;
727 if ( $self->debug ) {
728 my $name = $self->config->{name} || 'Application';
729 $self->log->info("$name powered by Catalyst $Catalyst::VERSION");
733 =item $class->setup_components
739 sub setup_components {
743 my $class = ref $self || $self;
746 import Module::Pluggable::Fast
747 name => '_components',
749 '$class\::Controller', '$class\::C',
750 '$class\::Model', '$class\::M',
751 '$class\::View', '$class\::V'
754 if ( my $error = $@ ) {
757 qq/Couldn't initialize "Module::Pluggable::Fast", "$error"/);
759 $self->components( {} );
760 for my $component ( $self->_components($self) ) {
761 $self->components->{ ref $component } = $component;
763 $self->log->debug( 'Initialized components "'
764 . join( ' ', keys %{ $self->components } )
771 Returns a hashref containing all your data.
773 $c->stash->{foo} ||= 'yada';
774 print $c->stash->{foo};
781 my $stash = $_[1] ? {@_} : $_[0];
782 while ( my ( $key, $val ) = each %$stash ) {
783 $self->{stash}->{$key} = $val;
786 return $self->{stash};
790 my ( $class, $name ) = @_;
791 my $prefix = _class2prefix($class);
792 warn "$class - $name - $prefix";
793 $name = "$prefix/$name" if $prefix;
798 my $class = shift || '';
799 $class =~ /^.*::([MVC]|Model|View|Controller)?::(.*)$/;
800 my $prefix = lc $2 || '';
801 $prefix =~ s/\:\:/\//g;
809 Sebastian Riedel, C<sri@cpan.org>
813 This program is free software, you can redistribute it and/or modify it under
814 the same terms as Perl itself.