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->benchmark($coderef)
53 Takes a coderef with arguments and returns elapsed time as float.
55 my ( $elapsed, $status ) = $c->benchmark( sub { return 1 } );
56 $c->log->info( sprintf "Processing took %f seconds", $elapsed );
63 my $time = [gettimeofday];
64 my @return = &$code(@_);
65 my $elapsed = tv_interval $time;
66 return wantarray ? ( $elapsed, @return ) : $elapsed;
71 =item $c->component($name)
73 Get a component object by name.
75 $c->comp('MyApp::Model::MyModel')->do_stuff;
77 Regex search for a component.
79 $c->comp('mymodel')->do_stuff;
84 my ( $c, $name ) = @_;
85 if ( my $component = $c->components->{$name} ) {
89 for my $component ( keys %{ $c->components } ) {
90 return $c->components->{$component} if $component =~ /$name/i;
97 =item $c->error($error, ...)
99 =item $c->error($arrayref)
101 Returns an arrayref containing error messages.
103 my @error = @{ $c->error };
107 $c->error('Something bad happened');
113 my $error = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
114 push @{ $c->{error} }, @$error;
127 if ( my $location = $c->res->redirect ) {
128 $c->log->debug(qq/Redirecting to "$location"/) if $c->debug;
129 $c->res->headers->header( Location => $location );
130 $c->res->status(302);
133 if ( !$c->res->output || $#{ $c->error } >= 0 ) {
134 $c->res->headers->content_type('text/html');
135 my $name = $c->config->{name} || 'Catalyst Application';
136 my ( $title, $error, $infos );
138 $error = join '<br/>', @{ $c->error };
139 $error ||= 'No output';
140 $title = $name = "$name on Catalyst $Catalyst::VERSION";
141 my $req = encode_entities Dumper $c->req;
142 my $res = encode_entities Dumper $c->res;
143 my $stash = encode_entities Dumper $c->stash;
146 <b><u>Request</u></b><br/>
148 <b><u>Response</u></b><br/>
150 <b><u>Stash</u></b><br/>
159 (en) Please come back later
160 (de) Bitte versuchen sie es spaeter nocheinmal
161 (nl) Gelieve te komen later terug
162 (no) Vennligst prov igjen senere
163 (fr) Veuillez revenir plus tard
164 (es) Vuelto por favor mas adelante
165 (pt) Voltado por favor mais tarde
166 (it) Ritornato prego piĆ¹ successivamente
171 $c->res->{output} = <<"";
174 <title>$title</title>
175 <style type="text/css">
177 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
178 Tahoma, Arial, helvetica, sans-serif;
180 background-color: #eee;
185 background-color: #ccc;
186 border: 1px solid #aaa;
189 -moz-border-radius: 10px;
192 background-color: #977;
193 border: 1px solid #755;
197 -moz-border-radius: 10px;
200 background-color: #797;
201 border: 1px solid #575;
205 -moz-border-radius: 10px;
208 background-color: #779;
209 border: 1px solid #557;
212 -moz-border-radius: 10px;
218 <div class="error">$error</div>
219 <div class="infos">$infos</div>
220 <div class="name">$name</div>
226 $c->res->headers->content_length( length $c->res->output );
227 my $status = $c->finalize_headers;
232 =item $c->finalize_headers
238 sub finalize_headers { }
240 =item $c->finalize_output
246 sub finalize_output { }
248 =item $c->forward($command)
250 Forward processing to a private action or a method from a class.
251 If you define a class without method it will default to process().
254 $c->forward('index');
255 $c->forward(qw/MyApp::Model::CDBI::Foo do_stuff/);
256 $c->forward('MyApp::View::TT');
264 $c->log->debug('Nothing to forward to') if $c->debug;
267 my $caller = caller(0);
269 if ( $command =~ /^\/(.*)$/ ) { $command = $1 }
270 else { $namespace = _class2prefix($caller) || '/' }
271 my $results = $c->get_action( $command, $namespace );
272 unless ( @{$results} ) {
273 my $class = $command;
274 if ( $class =~ /[^\w\:]/ ) {
275 $c->log->debug(qq/Couldn't forward to "$class"/) if $c->debug;
278 my $method = shift || 'process';
279 if ( my $code = $class->can($method) ) {
280 $c->actions->{reverse}->{"$code"} = "$class->$method";
281 $results = [ [ [ $class, $code ] ] ];
284 $c->log->debug(qq/Couldn't forward to "$class->$method"/)
289 for my $result ( @{$results} ) {
290 $c->state( $c->execute( @{ $result->[0] } ) );
295 =item $c->get_action( $action, $namespace )
297 Get an action in a given namespace.
302 my ( $c, $action, $namespace ) = @_;
305 $namespace = '' if $namespace eq '/';
306 my $parent = $c->tree;
308 my $result = $c->actions->{private}->{ $parent->getUID }->{$action};
309 push @results, [$result] if $result;
310 my $visitor = Tree::Simple::Visitor::FindByPath->new;
311 for my $part ( split '/', $namespace ) {
312 $visitor->setSearchPath($part);
313 $parent->accept($visitor);
314 my $child = $visitor->getResult;
315 my $uid = $child->getUID if $child;
316 my $match = $c->actions->{private}->{$uid}->{$action} if $uid;
318 $action eq 'end' ? unshift @results, [$match] : push @results,
321 $parent = $child if $child;
325 elsif ( my $p = $c->actions->{plain}->{$action} ) { return [ [$p] ] }
326 elsif ( my $r = $c->actions->{regex}->{$action} ) { return [ [$r] ] }
328 for my $i ( 0 .. $#{ $c->actions->{compiled} } ) {
329 my $name = $c->actions->{compiled}->[$i]->[0];
330 my $regex = $c->actions->{compiled}->[$i]->[1];
331 if ( $action =~ $regex ) {
333 for my $i ( 1 .. 9 ) {
336 push @snippets, ${$i};
338 return [ [ $c->actions->{regex}->{$name}, $name, \@snippets ] ];
345 =item $c->handler( $class, $r )
352 my ( $class, $r ) = @_;
354 # Always expect worst case!
358 my $c = $class->prepare($r);
359 my $action = $c->req->action;
361 $namespace = ( join( '/', @{ $c->req->args } ) || '/' )
362 if $action eq 'default';
363 unless ($namespace) {
364 if ( my $result = $c->get_action($action) ) {
365 $namespace = _class2prefix( $result->[0]->[0]->[0] );
368 my $default = $action eq 'default' ? $namespace : undef;
369 my $results = $c->get_action( $action, $default );
372 for my $begin ( @{ $c->get_action( 'begin', $namespace ) } ) {
373 $c->state( $c->execute( @{ $begin->[0] } ) );
375 for my $result ( @{ $c->get_action( $action, $default ) } ) {
376 $c->state( $c->execute( @{ $result->[0] } ) );
377 last unless $default;
379 for my $end ( @{ $c->get_action( 'end', $namespace ) } ) {
380 $c->state( $c->execute( @{ $end->[0] } ) );
384 my $path = $c->req->path;
386 ? qq/Unknown resource "$path"/
387 : "No default action defined";
388 $c->log->error($error) if $c->debug;
393 if ( $class->debug ) {
395 ( $elapsed, $status ) = $class->benchmark($handler);
396 $elapsed = sprintf '%f', $elapsed;
397 my $av = sprintf '%.3f', 1 / $elapsed;
398 $class->log->info( "Request took $elapsed" . "s ($av/s)" );
400 else { $status = &$handler }
402 if ( my $error = $@ ) {
404 $class->log->error(qq/Caught exception in engine "$error"/);
410 =item $c->prepare($r)
412 Turns the engine-specific request( Apache, CGI ... )
413 into a Catalyst context .
418 my ( $class, $r ) = @_;
420 request => Catalyst::Request->new(
424 headers => HTTP::Headers->new,
430 response => Catalyst::Response->new(
431 { cookies => {}, headers => HTTP::Headers->new, status => 200 }
437 my $secs = time - $START || 1;
438 my $av = sprintf '%.3f', $COUNT / $secs;
439 $c->log->debug('********************************');
440 $c->log->debug("* Request $COUNT ($av/s) [$$]");
441 $c->log->debug('********************************');
442 $c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION );
444 $c->prepare_request($r);
448 $c->prepare_connection;
449 my $method = $c->req->method || '';
450 my $path = $c->req->path || '';
451 my $hostname = $c->req->hostname || '';
452 my $address = $c->req->address || '';
453 $c->log->debug(qq/"$method" request for "$path" from $hostname($address)/)
456 $c->prepare_parameters;
458 if ( $c->debug && keys %{ $c->req->params } ) {
460 for my $key ( keys %{ $c->req->params } ) {
461 my $value = $c->req->params->{$key} || '';
462 push @params, "$key=$value";
464 $c->log->debug( 'Parameters are "' . join( ' ', @params ) . '"' );
470 =item $c->prepare_action
478 my $path = $c->req->path;
479 my @path = split /\//, $c->req->path;
480 $c->req->args( \my @args );
482 $path = join '/', @path;
483 if ( my $result = ${ $c->get_action($path) }[0] ) {
487 my $match = $result->[1];
488 my @snippets = @{ $result->[2] };
489 $c->log->debug(qq/Requested action "$path" matched "$match"/)
492 'Snippets are "' . join( ' ', @snippets ) . '"' )
493 if ( $c->debug && @snippets );
494 $c->req->action($match);
495 $c->req->snippets( \@snippets );
498 $c->req->action($path);
499 $c->log->debug(qq/Requested action "$path"/) if $c->debug;
501 $c->req->match($path);
504 unshift @args, pop @path;
506 unless ( $c->req->action ) {
507 $c->req->action('default');
510 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
511 if ( $c->debug && @args );
514 =item $c->prepare_connection
520 sub prepare_connection { }
522 =item $c->prepare_cookies
528 sub prepare_cookies { }
530 =item $c->prepare_headers
536 sub prepare_headers { }
538 =item $c->prepare_parameters
544 sub prepare_parameters { }
546 =item $c->prepare_path
548 Prepare path and base.
554 =item $c->prepare_request
556 Prepare the engine request.
560 sub prepare_request { }
562 =item $c->prepare_uploads
568 sub prepare_uploads { }
570 =item $c->execute($class, $coderef)
572 Execute a coderef in given class and catch exceptions.
573 Errors are available via $c->error.
578 my ( $c, $class, $code ) = @_;
579 $class = $c->comp($class) || $class;
584 my $action = $c->actions->{reverse}->{"$code"} || "$code";
585 my ( $elapsed, @state ) =
586 $c->benchmark( $code, $class, $c, @{ $c->req->args } );
587 $c->log->info( sprintf qq/Processing "$action" took %fs/, $elapsed )
591 else { $c->state( &$code( $class, $c, @{ $c->req->args } ) ) }
593 if ( my $error = $@ ) {
595 $error = qq/Caught exception "$error"/;
596 $c->log->error($error);
597 $c->error($error) if $c->debug;
615 Returns a C<Catalyst::Request> object.
623 Returns a C<Catalyst::Response> object.
627 =item $c->set_action( $action, $code, $namespace, $attrs )
629 Set an action in a given namespace.
634 my ( $c, $method, $code, $namespace, $attrs ) = @_;
636 my $prefix = _class2prefix($namespace) || '';
643 for my $attr ( @{$attrs} ) {
644 if ( $attr =~ /^Action$/ ) {
648 elsif ( $attr =~ /^Path\((.+)\)$/i ) {
652 elsif ( $attr =~ /^Public$/i ) {
655 elsif ( $attr =~ /^Private$/i ) {
658 elsif ( $attr =~ /Regex(?:\((.+)\))?$/i ) {
663 elsif ( $attr =~ /Absolute(?:\((.+)\))?$/i ) {
669 elsif ( $attr =~ /Relative(?:\((.+)\))?$/i ) {
676 return unless $action;
678 my $parent = $c->tree;
679 my $visitor = Tree::Simple::Visitor::FindByPath->new;
680 for my $part ( split '/', $prefix ) {
681 $visitor->setSearchPath($part);
682 $parent->accept($visitor);
683 my $child = $visitor->getResult;
685 $child = $parent->addChild( Tree::Simple->new($part) );
686 $visitor->setSearchPath($part);
687 $parent->accept($visitor);
688 $child = $visitor->getResult;
692 my $uid = $parent->getUID;
693 $c->actions->{private}->{$uid}->{$method} = [ $namespace, $code ];
694 my $forward = $prefix ? "$prefix/$method" : $method;
695 $c->log->debug(qq|Private "/$forward" is "$namespace->$method"|)
700 if ( $arg =~ /^'(.*)'$/ ) { $arg = $1 }
701 if ( $arg =~ /^"(.*)"$/ ) { $arg = $1 }
703 my $reverse = $prefix ? "$method ($prefix)" : $method;
707 $is_absolute = 1 if $absolute;
708 if ( $arg =~ /^\/(.+)/ ) {
713 $is_absolute ? ( $arg || $method ) : "$prefix/" . ( $arg || $method );
714 $c->actions->{plain}->{$name} = [ $namespace, $code ];
715 $c->log->debug(qq|Public "/$name" is "/$forward"|) if $c->debug;
718 push @{ $c->actions->{compiled} }, [ $arg, qr#$arg# ];
719 $c->actions->{regex}->{$arg} = [ $namespace, $code ];
720 $c->log->debug(qq|Public "$arg" is "/$forward"|) if $c->debug;
723 $c->actions->{reverse}->{"$code"} = $reverse;
736 $self->setup_components;
737 if ( $self->debug ) {
738 my $name = $self->config->{name} || 'Application';
739 $self->log->info("$name powered by Catalyst $Catalyst::VERSION");
743 =item $class->setup_actions($component)
745 Setup actions for a component.
750 my ( $self, $comp ) = @_;
751 $comp = ref $comp || $comp;
752 for my $action ( @{ $comp->_cache } ) {
753 my ( $code, $attrs ) = @{$action};
756 for my $sym ( values %{ $comp . '::' } ) {
757 if ( *{$sym}{CODE} && *{$sym}{CODE} == $code ) {
758 $name = *{$sym}{NAME};
759 $self->set_action( $name, $code, $comp, $attrs );
765 =item $class->setup_components
771 sub setup_components {
775 my $class = ref $self || $self;
778 import Module::Pluggable::Fast
779 name => '_components',
781 '$class\::Controller', '$class\::C',
782 '$class\::Model', '$class\::M',
783 '$class\::View', '$class\::V'
786 if ( my $error = $@ ) {
789 qq/Couldn't initialize "Module::Pluggable::Fast", "$error"/);
791 $self->setup_actions($self);
792 $self->components( {} );
793 for my $comp ( $self->_components($self) ) {
794 $self->components->{ ref $comp } = $comp;
795 $self->setup_actions($comp);
797 $self->log->debug( 'Initialized components "'
798 . join( ' ', keys %{ $self->components } )
805 Returns a hashref containing all your data.
807 $c->stash->{foo} ||= 'yada';
808 print $c->stash->{foo};
815 my $stash = $_[1] ? {@_} : $_[0];
816 while ( my ( $key, $val ) = each %$stash ) {
817 $self->{stash}->{$key} = $val;
820 return $self->{stash};
824 my ( $class, $name ) = @_;
825 my $prefix = _class2prefix($class);
826 $name = "$prefix/$name" if $prefix;
831 my $class = shift || '';
832 $class =~ /^.*::([MVC]|Model|View|Controller)?::(.*)$/;
833 my $prefix = lc $2 || '';
834 $prefix =~ s/\:\:/\//g;
842 Sebastian Riedel, C<sri@cpan.org>
846 This program is free software, you can redistribute it and/or modify it under
847 the same terms as Perl itself.