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');
291 $c->log->debug('Nothing to forward to') if $c->debug;
294 my $caller = caller(0);
295 if ( $command =~ /^\?(.*)$/ ) {
297 $command = _prefix( $caller, $command );
300 if ( $command =~ /^\!/ ) {
301 $namespace = _class2prefix($caller);
303 my $results = $c->get_action( $command, $namespace );
305 unless ( $command =~ /^\!/ ) {
306 $results = [ pop @{$results} ];
307 if ( $results->[0]->[2] ) {
308 $c->log->debug(qq/Couldn't forward "$command" to regex action/)
315 my $class = $command;
316 if ( $class =~ /[^\w\:]/ ) {
317 $c->log->debug(qq/Couldn't forward to "$class"/) if $c->debug;
320 my $method = shift || 'process';
321 if ( my $code = $class->can($method) ) {
322 $c->actions->{reverse}->{"$code"} = "$class->$method";
323 $results = [ [ [ $class, $code ] ] ];
326 $c->log->debug(qq/Couldn't forward to "$class->$method"/)
331 for my $result ( @{$results} ) {
332 my ( $class, $code ) = @{ $result->[0] };
333 $class = $c->comp->{$class} || $class;
334 $c->state( $c->process( $class, $code ) );
339 =item $c->get_action( $action, $namespace )
341 Get an action in a given namespace.
346 my ( $c, $action, $namespace ) = @_;
348 if ( $action =~ /^\!(.*)/ ) {
350 my $parent = $c->tree;
352 my $result = $c->actions->{private}->{ $parent->getUID }->{$action};
353 push @results, [$result] if $result;
354 my $visitor = Tree::Simple::Visitor::FindByPath->new;
356 for my $part ( split '/', $namespace ) {
358 $visitor->setSearchPath($part);
359 $parent->accept($visitor);
360 my $child = $visitor->getResult;
361 my $uid = $child->getUID if $child;
362 my $match = $c->actions->{private}->{$uid}->{$action} if $uid;
363 return [ [$match] ] if ( $match && $match =~ /^?.*/ );
364 $local = $c->actions->{private}->{$uid}->{"?$action"} if $uid;
365 push @results, [$match] if $match;
366 $parent = $child if $child;
368 return [ [$local] ] if $local;
371 elsif ( my $p = $c->actions->{plain}->{$action} ) { return [ [$p] ] }
372 elsif ( my $r = $c->actions->{regex}->{$action} ) { return [ [$r] ] }
374 for my $regex ( keys %{ $c->actions->{compiled} } ) {
375 my $name = $c->actions->{compiled}->{$regex};
376 if ( $action =~ $regex ) {
378 for my $i ( 1 .. 9 ) {
381 push @snippets, ${$i};
383 return [ [ $c->actions->{regex}->{$name}, $name, \@snippets ] ];
390 =item $c->handler( $class, $r )
397 my ( $class, $r ) = @_;
399 # Always expect worst case!
403 my $c = $class->prepare($r);
404 my $action = $c->req->action;
406 $namespace = join '/', @{ $c->req->args } if $action eq '!default';
407 unless ($namespace) {
408 if ( my $result = $c->get_action($action) ) {
409 $namespace = _class2prefix( $result->[0]->[0]->[0] );
412 my $results = $c->get_action( $action, $namespace );
414 for my $begin ( @{ $c->get_action( '!begin', $namespace ) } ) {
415 $c->state( $c->process( @{ $begin->[0] } ) );
417 for my $result ( @{ $c->get_action( $action, $namespace ) } ) {
418 $c->state( $c->process( @{ $result->[0] } ) );
420 for my $end ( @{ $c->get_action( '!end', $namespace ) } ) {
421 $c->state( $c->process( @{ $end->[0] } ) );
425 my $path = $c->req->path;
427 ? qq/Unknown resource "$path"/
428 : "No default action defined";
429 $c->log->error($error) if $c->debug;
434 if ( $class->debug ) {
436 ( $elapsed, $status ) = $class->benchmark($handler);
437 $elapsed = sprintf '%f', $elapsed;
438 my $av = sprintf '%.3f', 1 / $elapsed;
439 $class->log->info( "Request took $elapsed" . "s ($av/s)" );
441 else { $status = &$handler }
443 if ( my $error = $@ ) {
445 $class->log->error(qq/Caught exception in engine "$error"/);
451 =item $c->prepare($r)
453 Turns the engine-specific request (Apache, CGI...) into a Catalyst context.
458 my ( $class, $r ) = @_;
460 request => Catalyst::Request->new(
464 headers => HTTP::Headers->new,
470 response => Catalyst::Response->new(
471 { cookies => {}, headers => HTTP::Headers->new, status => 200 }
477 my $secs = time - $START || 1;
478 my $av = sprintf '%.3f', $COUNT / $secs;
479 $c->log->debug('********************************');
480 $c->log->debug("* Request $COUNT ($av/s) [$$]");
481 $c->log->debug('********************************');
482 $c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION );
484 $c->prepare_request($r);
488 $c->prepare_connection;
489 my $method = $c->req->method || '';
490 my $path = $c->req->path || '';
491 my $hostname = $c->req->hostname || '';
492 my $address = $c->req->address || '';
493 $c->log->debug(qq/"$method" request for "$path" from $hostname($address)/)
496 $c->prepare_parameters;
498 if ( $c->debug && keys %{ $c->req->params } ) {
500 for my $key ( keys %{ $c->req->params } ) {
501 my $value = $c->req->params->{$key} || '';
502 push @params, "$key=$value";
504 $c->log->debug( 'Parameters are "' . join( ' ', @params ) . '"' );
510 =item $c->prepare_action
518 my $path = $c->req->path;
519 my @path = split /\//, $c->req->path;
520 $c->req->args( \my @args );
522 $path = join '/', @path;
523 if ( my $result = ${ $c->get_action($path) }[0] ) {
527 my $match = $result->[1];
528 my @snippets = @{ $result->[2] };
529 $c->log->debug(qq/Requested action "$path" matched "$match"/)
532 'Snippets are "' . join( ' ', @snippets ) . '"' )
533 if ( $c->debug && @snippets );
534 $c->req->action($match);
535 $c->req->snippets( \@snippets );
538 $c->req->action($path);
539 $c->log->debug(qq/Requested action "$path"/) if $c->debug;
541 $c->req->match($path);
544 unshift @args, pop @path;
546 unless ( $c->req->action ) {
547 $c->req->action('!default');
550 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
551 if ( $c->debug && @args );
554 =item $c->prepare_connection
560 sub prepare_connection { }
562 =item $c->prepare_cookies
568 sub prepare_cookies { }
570 =item $c->prepare_headers
576 sub prepare_headers { }
578 =item $c->prepare_parameters
584 sub prepare_parameters { }
586 =item $c->prepare_path
588 Prepare path and base.
594 =item $c->prepare_request
596 Prepare the engine request.
600 sub prepare_request { }
602 =item $c->prepare_uploads
608 sub prepare_uploads { }
610 =item $c->process($class, $coderef)
612 Process a coderef in given class and catch exceptions.
613 Errors are available via $c->errors.
618 my ( $c, $class, $code ) = @_;
623 my $action = $c->actions->{reverse}->{"$code"} || "$code";
625 ( $elapsed, $status ) =
626 $c->benchmark( $code, $class, $c, @{ $c->req->args } );
627 $c->log->info( sprintf qq/Processing "$action" took %fs/, $elapsed )
630 else { $status = &$code( $class, $c, @{ $c->req->args } ) }
632 if ( my $error = $@ ) {
634 $error = qq/Caught exception "$error"/;
635 $c->log->error($error);
636 $c->errors($error) if $c->debug;
654 Returns a C<Catalyst::Request> object.
662 Returns a C<Catalyst::Response> object.
666 =item $c->set_action( $action, $code, $namespace )
668 Set an action in a given namespace.
673 my ( $c, $action, $code, $namespace ) = @_;
675 if ( $action =~ /^\?(.*)$/ ) {
676 my $prefix = $1 || '';
678 $action = $prefix . _prefix( $namespace, $action );
679 $c->actions->{plain}->{$action} = [ $namespace, $code ];
681 elsif ( $action =~ /^\/(.*)\/$/ ) {
683 $c->actions->{compiled}->{qr#$regex#} = $action;
684 $c->actions->{regex}->{$action} = [ $namespace, $code ];
686 elsif ( $action =~ /^\!(.*)$/ ) {
688 my $parent = $c->tree;
689 my $visitor = Tree::Simple::Visitor::FindByPath->new;
690 $prefix = _class2prefix($namespace);
691 for my $part ( split '/', $prefix ) {
692 $visitor->setSearchPath($part);
693 $parent->accept($visitor);
694 my $child = $visitor->getResult;
696 $child = $parent->addChild( Tree::Simple->new($part) );
697 $visitor->setSearchPath($part);
698 $parent->accept($visitor);
699 $child = $visitor->getResult;
703 my $uid = $parent->getUID;
704 $c->actions->{private}->{$uid}->{$action} = [ $namespace, $code ];
705 $action = "!$action";
707 else { $c->actions->{plain}->{$action} = [ $namespace, $code ] }
708 my $reverse = $prefix ? "$action ($prefix)" : $action;
709 $c->actions->{reverse}->{"$code"} = $reverse;
710 $c->log->debug(qq/"$namespace" defined "$action" as "$code"/) if $c->debug;
723 $self->setup_components;
724 if ( $self->debug ) {
725 my $name = $self->config->{name} || 'Application';
726 $self->log->info("$name powered by Catalyst $Catalyst::VERSION");
730 =item $class->setup_components
736 sub setup_components {
740 my $class = ref $self || $self;
743 import Module::Pluggable::Fast
744 name => '_components',
746 '$class\::Controller', '$class\::C',
747 '$class\::Model', '$class\::M',
748 '$class\::View', '$class\::V'
751 if ( my $error = $@ ) {
754 qq/Couldn't initialize "Module::Pluggable::Fast", "$error"/);
756 $self->components( {} );
757 for my $component ( $self->_components($self) ) {
758 $self->components->{ ref $component } = $component;
760 $self->log->debug( 'Initialized components "'
761 . join( ' ', keys %{ $self->components } )
768 Returns a hashref containing all your data.
770 $c->stash->{foo} ||= 'yada';
771 print $c->stash->{foo};
778 my $stash = $_[1] ? {@_} : $_[0];
779 while ( my ( $key, $val ) = each %$stash ) {
780 $self->{stash}->{$key} = $val;
783 return $self->{stash};
787 my ( $class, $name ) = @_;
788 my $prefix = _class2prefix($class);
789 warn "$class - $name - $prefix";
790 $name = "$prefix/$name" if $prefix;
795 my $class = shift || '';
796 $class =~ /^.*::([MVC]|Model|View|Controller)?::(.*)$/;
797 my $prefix = lc $2 || '';
798 $prefix =~ s/\:\:/\//g;
806 Sebastian Riedel, C<sri@cpan.org>
810 This program is free software, you can redistribute it and/or modify it under
811 the same terms as Perl itself.