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/);
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 ) {
72 my $class = caller(0);
73 if ( $name =~ /^\?(.*)$/ ) {
74 my $prefix = $1 || '';
76 $name = $prefix . _prefix( $class, $name );
77 $self->actions->{plain}->{$name} = [ $class, $code ];
79 if ( $name =~ /^\/(.*)\/$/ ) {
81 $self->actions->{compiled}->{qr#$regex#} = $name;
82 $self->actions->{regex}->{$name} = [ $class, $code ];
84 elsif ( $name =~ /^\!(.*)$/ ) {
86 my $parent = $self->tree;
87 my $visitor = Tree::Simple::Visitor::FindByPath->new;
88 $prefix = _class2prefix($class);
89 for my $part ( split '/', $prefix ) {
90 $visitor->setSearchPath($part);
91 $parent->accept($visitor);
92 my $child = $visitor->getResult;
94 $child = $parent->addChild( Tree::Simple->new($part) );
95 $visitor->setSearchPath($part);
96 $parent->accept($visitor);
97 $child = $visitor->getResult;
101 my $uid = $parent->getUID;
102 $self->actions->{private}->{$uid}->{$name} = [ $class, $code ];
105 else { $self->actions->{plain}->{$name} = [ $class, $code ] }
106 my $reverse = $prefix ? "$name ($prefix)" : $name;
107 $self->actions->{reverse}->{"$code"} = $reverse;
108 $self->log->debug(qq/"$class" defined "$name" as "$code"/)
115 =item $c->find_action( $name, $namespace )
117 Find an action in a given namespace.
122 my ( $c, $action, $namespace ) = @_;
124 if ( $action =~ /^\!(.*)/ ) {
126 my $parent = $c->tree;
127 my $result = $c->actions->{private}->{ $parent->getUID }->{$action};
128 my $visitor = Tree::Simple::Visitor::FindByPath->new;
129 for my $part ( split '/', $namespace ) {
130 $visitor->setSearchPath($part);
131 $parent->accept($visitor);
132 my $child = $visitor->getResult;
133 my $uid = $child->getUID if $child;
134 my $match = $c->actions->{private}->{$uid}->{$action} if $uid;
135 $result = $match if $match;
136 $parent = $child if $child;
138 return [$result] if $result;
140 elsif ( my $p = $c->actions->{plain}->{$action} ) { return [$p] }
141 elsif ( my $r = $c->actions->{regex}->{$action} ) { return [$r] }
143 for my $regex ( keys %{ $c->actions->{compiled} } ) {
144 my $name = $c->actions->{compiled}->{$regex};
145 if ( $action =~ $regex ) {
147 for my $i ( 1 .. 9 ) {
150 push @snippets, ${$i};
152 return [ $c->actions->{regex}->{$name}, $name, \@snippets ];
159 =item $c->benchmark($coderef)
161 Takes a coderef with arguments and returns elapsed time as float.
163 my ( $elapsed, $status ) = $c->benchmark( sub { return 1 } );
164 $c->log->info( sprintf "Processing took %f seconds", $elapsed );
171 my $time = [gettimeofday];
172 my @return = &$code(@_);
173 my $elapsed = tv_interval $time;
174 return wantarray ? ( $elapsed, @return ) : $elapsed;
177 =item $c->comp($name)
179 =item $c->component($name)
181 Get a component object by name.
183 $c->comp('MyApp::Model::MyModel')->do_stuff;
185 Regex search for a component.
187 $c->comp('mymodel')->do_stuff;
192 my ( $c, $name ) = @_;
193 if ( my $component = $c->components->{$name} ) {
197 for my $component ( keys %{ $c->components } ) {
198 return $c->components->{$component} if $component =~ /$name/i;
205 =item $c->errors($error, ...)
207 =item $c->errors($arrayref)
209 Returns an arrayref containing errors messages.
211 my @errors = @{ $c->errors };
215 $c->errors('Something bad happened');
221 my $errors = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
222 push @{ $c->{errors} }, @$errors;
235 if ( my $location = $c->res->redirect ) {
236 $c->log->debug(qq/Redirecting to "$location"/) if $c->debug;
237 $c->res->headers->header( Location => $location );
238 $c->res->status(302);
241 if ( !$c->res->output || $#{ $c->errors } >= 0 ) {
242 $c->res->headers->content_type('text/html');
243 my $name = $c->config->{name} || 'Catalyst Application';
244 my ( $title, $errors, $infos );
246 $errors = join '<br/>', @{ $c->errors };
247 $errors ||= 'No output';
248 $title = $name = "$name on Catalyst $Catalyst::VERSION";
249 my $req = encode_entities Dumper $c->req;
250 my $res = encode_entities Dumper $c->res;
251 my $stash = encode_entities Dumper $c->stash;
254 <b><u>Request</u></b><br/>
256 <b><u>Response</u></b><br/>
258 <b><u>Stash</u></b><br/>
267 (en) Please come back later
268 (de) Bitte versuchen sie es spaeter nocheinmal
269 (nl) Gelieve te komen later terug
270 (no) Vennligst prov igjen senere
271 (fr) Veuillez revenir plus tard
272 (es) Vuelto por favor mas adelante
273 (pt) Voltado por favor mais tarde
274 (it) Ritornato prego piĆ¹ successivamente
279 $c->res->{output} = <<"";
282 <title>$title</title>
283 <style type="text/css">
285 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
286 Tahoma, Arial, helvetica, sans-serif;
288 background-color: #eee;
293 background-color: #ccc;
294 border: 1px solid #aaa;
297 -moz-border-radius: 10px;
300 background-color: #977;
301 border: 1px solid #755;
305 -moz-border-radius: 10px;
308 background-color: #797;
309 border: 1px solid #575;
313 -moz-border-radius: 10px;
316 background-color: #779;
317 border: 1px solid #557;
320 -moz-border-radius: 10px;
326 <div class="errors">$errors</div>
327 <div class="infos">$infos</div>
328 <div class="name">$name</div>
334 $c->res->headers->content_length( length $c->res->output );
335 my $status = $c->finalize_headers;
340 =item $c->finalize_headers
346 sub finalize_headers { }
348 =item $c->finalize_output
354 sub finalize_output { }
356 =item $c->forward($command)
358 Forward processing to a private/public action or a method from a class.
359 If you define a class without method it will default to process().
362 $c->forward('index.html');
363 $c->forward(qw/MyApp::Model::CDBI::Foo do_stuff/);
364 $c->forward('MyApp::View::TT');
372 $c->log->debug('Nothing to forward to') if $c->debug;
375 my $caller = caller(0);
376 if ( $command =~ /^\?(.*)$/ ) {
378 $command = _prefix( $caller, $command );
381 if ( $command =~ /^\!/ ) {
382 $namespace = _class2prefix($caller);
384 my ( $class, $code );
385 if ( my $action = $c->find_action( $command, $namespace ) ) {
386 if ( $action->[2] ) {
387 $c->log->debug(qq/Couldn't forward "$command" to regex action/)
391 ( $class, $code ) = @{ $action->[0] };
395 if ( $class =~ /[^\w\:]/ ) {
396 $c->log->debug(qq/Couldn't forward to "$class"/) if $c->debug;
399 my $method = shift || 'process';
400 if ( $code = $class->can($method) ) {
401 $c->actions->{reverse}->{"$code"} = "$class->$method";
404 $c->log->debug(qq/Couldn't forward to "$class->$method"/)
409 $class = $c->components->{$class} || $class;
410 return $c->process( $class, $code );
413 =item $c->handler($r)
420 my ( $class, $r ) = @_;
422 # Always expect worst case!
426 my $c = $class->prepare($r);
427 my $action = $c->req->action;
429 $namespace = join '/', @{ $c->req->args } if $action eq '!default';
430 unless ($namespace) {
431 if ( my $result = $c->find_action($action) ) {
432 $namespace = _class2prefix( $result->[0]->[0] );
435 if ( my $begin = $c->find_action( '!begin', $namespace ) ) {
436 $c->process( @{ $begin->[0] } );
438 if ( my $result = $c->find_action( $action, $namespace ) ) {
439 $c->process( @{ $result->[0] } );
442 my $path = $c->req->path;
444 ? qq/Unknown resource "$path"/
445 : "No default action defined";
446 $c->log->error($error) if $c->debug;
449 if ( my $end = $c->find_action( '!end', $namespace ) ) {
450 $c->process( @{ $end->[0] } );
454 if ( $class->debug ) {
456 ( $elapsed, $status ) = $class->benchmark($handler);
457 $elapsed = sprintf '%f', $elapsed;
458 my $av = sprintf '%.3f', 1 / $elapsed;
459 $class->log->info( "Request took $elapsed" . "s ($av/s)" );
461 else { $status = &$handler }
463 if ( my $error = $@ ) {
465 $class->log->error(qq/Caught exception in engine "$error"/);
471 =item $c->prepare($r)
473 Turns the engine-specific request (Apache, CGI...) into a Catalyst context.
478 my ( $class, $r ) = @_;
480 request => Catalyst::Request->new(
484 headers => HTTP::Headers->new,
490 response => Catalyst::Response->new(
491 { cookies => {}, headers => HTTP::Headers->new, status => 200 }
496 my $secs = time - $START || 1;
497 my $av = sprintf '%.3f', $COUNT / $secs;
498 $c->log->debug('********************************');
499 $c->log->debug("* Request $COUNT ($av/s) [$$]");
500 $c->log->debug('********************************');
501 $c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION );
503 $c->prepare_request($r);
507 $c->prepare_connection;
508 my $method = $c->req->method || '';
509 my $path = $c->req->path || '';
510 my $hostname = $c->req->hostname || '';
511 my $address = $c->req->address || '';
512 $c->log->debug(qq/"$method" request for "$path" from $hostname($address)/)
515 $c->prepare_parameters;
517 if ( $c->debug && keys %{ $c->req->params } ) {
519 for my $key ( keys %{ $c->req->params } ) {
520 my $value = $c->req->params->{$key} || '';
521 push @params, "$key=$value";
523 $c->log->debug( 'Parameters are "' . join( ' ', @params ) . '"' );
529 =item $c->prepare_action
537 my $path = $c->req->path;
538 my @path = split /\//, $c->req->path;
539 $c->req->args( \my @args );
541 $path = join '/', @path;
542 if ( my $result = $c->find_action($path) ) {
546 my $match = $result->[1];
547 my @snippets = @{ $result->[2] };
548 $c->log->debug(qq/Requested action "$path" matched "$match"/)
551 'Snippets are "' . join( ' ', @snippets ) . '"' )
552 if ( $c->debug && @snippets );
553 $c->req->action($match);
554 $c->req->snippets( \@snippets );
557 $c->req->action($path);
558 $c->log->debug(qq/Requested action "$path"/) if $c->debug;
560 $c->req->match($path);
563 unshift @args, pop @path;
565 unless ( $c->req->action ) {
566 $c->req->action('!default');
569 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
570 if ( $c->debug && @args );
573 =item $c->prepare_connection;
579 sub prepare_connection { }
581 =item $c->prepare_cookies;
587 sub prepare_cookies { }
589 =item $c->prepare_headers
595 sub prepare_headers { }
597 =item $c->prepare_parameters
603 sub prepare_parameters { }
605 =item $c->prepare_path
607 Prepare path and base.
613 =item $c->prepare_request
615 Prepare the engine request.
619 sub prepare_request { }
621 =item $c->prepare_uploads
627 sub prepare_uploads { }
629 =item $c->process($class, $coderef)
631 Process a coderef in given class and catch exceptions.
632 Errors are available via $c->errors.
637 my ( $c, $class, $code ) = @_;
642 my $action = $c->actions->{reverse}->{"$code"} || "$code";
644 ( $elapsed, $status ) =
645 $c->benchmark( $code, $class, $c, @{ $c->req->args } );
646 $c->log->info( sprintf qq/Processing "$action" took %fs/, $elapsed )
649 else { $status = &$code( $class, $c, @{ $c->req->args } ) }
651 if ( my $error = $@ ) {
653 $error = qq/Caught exception "$error"/;
654 $c->log->error($error);
655 $c->errors($error) if $c->debug;
665 Returns a C<Catalyst::Request> object.
673 Returns a C<Catalyst::Response> object.
687 $self->setup_components;
688 if ( $self->debug ) {
689 my $name = $self->config->{name} || 'Application';
690 $self->log->info("$name powered by Catalyst $Catalyst::VERSION");
694 =item $class->setup_components
700 sub setup_components {
704 my $class = ref $self || $self;
707 import Module::Pluggable::Fast
708 name => '_components',
710 '$class\::Controller', '$class\::C',
711 '$class\::Model', '$class\::M',
712 '$class\::View', '$class\::V'
715 if ( my $error = $@ ) {
718 qq/Couldn't initialize "Module::Pluggable::Fast", "$error"/);
720 $self->components( {} );
721 for my $component ( $self->_components($self) ) {
722 $self->components->{ ref $component } = $component;
724 $self->log->debug( 'Initialized components "'
725 . join( ' ', keys %{ $self->components } )
732 Returns a hashref containing all your data.
734 $c->stash->{foo} ||= 'yada';
735 print $c->stash->{foo};
742 my $stash = $_[1] ? {@_} : $_[0];
743 while ( my ( $key, $val ) = each %$stash ) {
744 $self->{stash}->{$key} = $val;
747 return $self->{stash};
751 my ( $class, $name ) = @_;
752 my $prefix = _class2prefix($class);
753 $name = "$prefix/$name" if $prefix;
759 $class =~ /^.*::([MVC]|Model|View|Controller)+::(.*)$/;
760 my $prefix = lc $2 || '';
761 $prefix =~ s/\:\:/\//g;
769 Sebastian Riedel, C<sri@cpan.org>
773 This program is free software, you can redistribute it and/or modify it under
774 the same terms as Perl itself.