X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Runtime.git;a=blobdiff_plain;f=lib%2FCatalyst.pm;h=4cf485d0e99a0685744f7dab227f139c86aef751;hp=6b519b444a218ba788526d41c5d3b64ca3cf5bbc;hb=cc95842fedcac58b2dc12c6ce547e22d3170351c;hpb=5050d7a770f5367adee391568ae7c79cefe8daae diff --git a/lib/Catalyst.pm b/lib/Catalyst.pm index 6b519b4..4cf485d 100644 --- a/lib/Catalyst.pm +++ b/lib/Catalyst.pm @@ -3,7 +3,6 @@ package Catalyst; use strict; use base 'Catalyst::Component'; use bytes; -use UNIVERSAL::require; use Catalyst::Exception; use Catalyst::Log; use Catalyst::Request; @@ -11,20 +10,26 @@ use Catalyst::Request::Upload; use Catalyst::Response; use Catalyst::Utils; use Catalyst::Controller; +use Devel::InnerPackage (); use File::stat; +use Module::Pluggable::Object (); use NEXT; -use Text::SimpleTable; -use Path::Class::Dir; -use Path::Class::File; +use Text::SimpleTable (); +use Path::Class::Dir (); +use Path::Class::File (); use Time::HiRes qw/gettimeofday tv_interval/; -use URI; +use URI (); use Scalar::Util qw/weaken blessed/; use Tree::Simple qw/use_weak_refs/; use Tree::Simple::Visitor::FindByUID; use attributes; +use utf8; +use Carp qw/croak/; + +BEGIN { require 5.008001; } __PACKAGE__->mk_accessors( - qw/counter request response state action stack namespace/ + qw/counter request response state action stack namespace stats/ ); attributes->import( __PACKAGE__, \&namespace, 'lvalue' ); @@ -45,11 +50,6 @@ our $START = time; our $RECURSION = 1000; our $DETACH = "catalyst_detach\n"; -require Module::Pluggable::Fast; - -# Helper script generation -our $CATALYST_SCRIPT_GEN = 27; - __PACKAGE__->mk_classdata($_) for qw/components arguments dispatcher engine log dispatcher_class engine_class context_class request_class response_class setup_finished/; @@ -59,7 +59,9 @@ __PACKAGE__->engine_class('Catalyst::Engine::CGI'); __PACKAGE__->request_class('Catalyst::Request'); __PACKAGE__->response_class('Catalyst::Response'); -our $VERSION = '5.66'; +# Remember to update this in Catalyst::Runtime as well! + +our $VERSION = '5.7006'; sub import { my ( $class, @arguments ) = @_; @@ -85,29 +87,32 @@ Catalyst - The Elegant MVC Web Application Framework =head1 SYNOPSIS - # use the helper to start a new application + # Install Catalyst::Devel for helpers and other development tools + # use the helper to create a new application catalyst.pl MyApp # add models, views, controllers - script/myapp_create.pl model Database DBIC dbi:SQLite:/path/to/db - script/myapp_create.pl view TT TT + script/myapp_create.pl model MyDatabase DBIC::Schema create=dynamic dbi:SQLite:/path/to/db + script/myapp_create.pl view MyTemplate TT script/myapp_create.pl controller Search # built in testserver -- use -r to restart automatically on changes + # --help to see all available options script/myapp_server.pl # command line testing interface script/myapp_test.pl /yada - ### in MyApp.pm + ### in lib/MyApp.pm use Catalyst qw/-Debug/; # include plugins here as well + ### In lib/MyApp/Controller/Root.pm (autocreated) sub foo : Global { # called for /foo, /foo/1, /foo/1/2, etc. my ( $self, $c, @args ) = @_; # args are qw/1 2/ for /foo/1/2 $c->stash->{template} = 'foo.tt'; # set the template # lookup something from db -- stash vars are passed to TT $c->stash->{data} = - MyApp::Model::Database::Foo->search( { country => $args[0] } ); + $c->model('Database::Foo')->search( { country => $args[0] } ); if ( $c->req->params->{bar} ) { # access GET or POST parameters $c->forward( 'bar' ); # process another action # do something else after forward returns @@ -125,7 +130,7 @@ Catalyst - The Elegant MVC Web Application Framework # called for all actions, from the top-most controller downwards sub auto : Private { my ( $self, $c ) = @_; - if ( !$c->user ) { + if ( !$c->user_exists ) { # Catalyst::Plugin::Authentication $c->res->redirect( '/login' ); # require login return 0; # abort request and go immediately to end() } @@ -154,21 +159,24 @@ Catalyst - The Elegant MVC Web Application Framework # called for /foo/bar/baz sub baz : Local { ... } - # first MyApp auto is called, then Foo auto, then this + # first Root auto is called, then Foo auto, then this sub auto : Private { ... } # powerful regular expression paths are also possible sub details : Regex('^product/(\w+)/details$') { my ( $self, $c ) = @_; # extract the (\w+) from the URI - my $product = $c->req->snippets->[0]; + my $product = $c->req->captures->[0]; } See L for additional information. =head1 DESCRIPTION -The key concept of Catalyst is DRY (Don't Repeat Yourself). +Catalyst is a modern framework for making web applications without the +pain usually associated with this process. This document is a reference +to the main Catalyst application. If you are a new user, we suggest you +start with L or L. See L for more documentation. @@ -193,13 +201,16 @@ arguments when Catalyst is loaded: use Catalyst qw/-Debug My::Module/; The position of plugins and flags in the chain is important, because -they are loaded in exactly the order in which they appear. +they are loaded in the order in which they appear. The following flags are supported: =head2 -Debug -Enables debug output. +Enables debug output. You can also force this setting from the system +environment with CATALYST_DEBUG or _DEBUG. The environment +settings override the application, with _DEBUG having the highest +priority. =head2 -Engine @@ -212,7 +223,13 @@ C prefix of the engine name, i.e.: Forces Catalyst to use a specific home directory, e.g.: - use Catalyst qw[-Home=/usr/sri]; + use Catalyst qw[-Home=/usr/mst]; + +This can also be done in the shell environment by setting either the +C environment variable or C; where C +is replaced with the uppercased name of your application, any "::" in +the name will be replaced with underscores, e.g. MyApp::Web should use +MYAPP_WEB_HOME. If both variables are set, the MYAPP_HOME one will be used. =head2 -Log @@ -229,7 +246,7 @@ stringifies to the action name. See L. =head2 $c->namespace -Returns the namespace of the current action, i.e., the uri prefix +Returns the namespace of the current action, i.e., the URI prefix corresponding to the controller of the current action. For example: # in Controller::Foo::Bar @@ -239,20 +256,21 @@ corresponding to the controller of the current action. For example: =head2 $c->req -Returns the current L object. See -L. +Returns the current L object, giving access to +information about the current client request (including parameters, +cookies, HTTP headers, etc.). See L. -=head2 PROCESSING AND RESPONSE TO THE CURRENT REQUEST +=head2 REQUEST FLOW HANDLING =head2 $c->forward( $action [, \@arguments ] ) =head2 $c->forward( $class, $method, [, \@arguments ] ) -Forwards processing to a private action. If you give a class name but no -method, C is called. You may also optionally pass arguments -in an arrayref. The action will receive the arguments in C<@_> and -C<$c-Ereq-Eargs>. Upon returning from the function, -C<$c-Ereq-Eargs> will be restored to the previous values. +Forwards processing to another action, by its private name. If you give a +class name but no method, C is called. You may also optionally +pass arguments in an arrayref. The action will receive the arguments in +C<@_> and C<< $c->req->args >>. Upon returning from the function, +C<< $c->req->args >> will be restored to the previous values. Any data Ced from the action forwarded to, will be returned by the call to forward. @@ -262,6 +280,18 @@ call to forward. $c->forward(qw/MyApp::Model::DBIC::Foo do_stuff/); $c->forward('MyApp::View::TT'); +Note that forward implies an C<> around the call (actually +C does), thus de-fatalizing all 'dies' within the called +action. If you want C to propagate you need to do something like: + + $c->forward('foo'); + die $c->error if $c->error; + +Or make sure to always return true values from your actions and write +your code like this: + + $c->forward('foo') || return; + =cut sub forward { my $c = shift; $c->dispatcher->forward( $c, @_ ) } @@ -270,12 +300,54 @@ sub forward { my $c = shift; $c->dispatcher->forward( $c, @_ ) } =head2 $c->detach( $class, $method, [, \@arguments ] ) -The same as C, but doesn't return. +=head2 $c->detach() + +The same as C, but doesn't return to the previous action when +processing is finished. + +When called with no arguments it escapes the processing chain entirely. =cut sub detach { my $c = shift; $c->dispatcher->detach( $c, @_ ) } +=head2 $c->response + +=head2 $c->res + +Returns the current L object, see there for details. + +=head2 $c->stash + +Returns a hashref to the stash, which may be used to store data and pass +it between components during a request. You can also set hash keys by +passing arguments. The stash is automatically sent to the view. The +stash is cleared at the end of a request; it cannot be used for +persistent storage (for this you must use a session; see +L for a complete system integrated with +Catalyst). + + $c->stash->{foo} = $bar; + $c->stash( { moose => 'majestic', qux => 0 } ); + $c->stash( bar => 1, gorch => 2 ); # equivalent to passing a hashref + + # stash is automatically passed to the view for use in a template + $c->forward( 'MyApp::View::TT' ); + +=cut + +sub stash { + my $c = shift; + if (@_) { + my $stash = @_ > 1 ? {@_} : $_[0]; + croak('stash takes a hash or hashref') unless ref $stash; + foreach my $key ( keys %$stash ) { + $c->{stash}->{$key} = $stash->{$key}; + } + } + return $c->{stash}; +} + =head2 $c->error =head2 $c->error($error, ...) @@ -298,12 +370,18 @@ sub error { my $c = shift; if ( $_[0] ) { my $error = ref $_[0] eq 'ARRAY' ? $_[0] : [@_]; + croak @$error unless ref $c; push @{ $c->{error} }, @$error; } elsif ( defined $_[0] ) { $c->{error} = undef } return $c->{error} || []; } + +=head2 $c->state + +Contains the return value of the last executed action. + =head2 $c->clear_errors Clear errors. You probably don't want to clear the errors unless you are @@ -320,45 +398,6 @@ sub clear_errors { $c->error(0); } -=head2 $c->response - -=head2 $c->res - -Returns the current L object. - -=head2 $c->stash - -Returns a hashref to the stash, which may be used to store data and pass -it between components during a request. You can also set hash keys by -passing arguments. The stash is automatically sent to the view. The -stash is cleared at the end of a request; it cannot be used for -persistent storage. - - $c->stash->{foo} = $bar; - $c->stash( { moose => 'majestic', qux => 0 } ); - $c->stash( bar => 1, gorch => 2 ); # equivalent to passing a hashref - - # stash is automatically passed to the view for use in a template - $c->forward( 'MyApp::V::TT' ); - -=cut - -sub stash { - my $c = shift; - if (@_) { - my $stash = @_ > 1 ? {@_} : $_[0]; - while ( my ( $key, $val ) = each %$stash ) { - $c->{stash}->{$key} = $val; - } - } - return $c->{stash}; -} - -=head2 $c->state - -Contains the return value of the last executed action. - -=cut # search via regex sub _comp_search { @@ -399,6 +438,29 @@ sub _comp_prefixes { return $comp; } +# Find possible names for a prefix + +sub _comp_names { + my ( $c, @prefixes ) = @_; + + my $appclass = ref $c || $c; + + my @pre = map { "${appclass}::${_}::" } @prefixes; + + my @names; + + COMPONENT: foreach my $comp ($c->component) { + foreach my $p (@pre) { + if ($comp =~ s/^$p//) { + push(@names, $comp); + next COMPONENT; + } + } + } + + return @names; +} + # Return a component if only one matches. sub _comp_singular { my ( $c, @prefixes ) = @_; @@ -421,49 +483,14 @@ sub _filter_component { =head2 COMPONENT ACCESSORS -=head2 $c->comp($name) - -=head2 $c->component($name) - -Gets a component object by name. This method is no longer recommended, -unless you want to get a specific component by full -class. C<$c-Econtroller>, C<$c-Emodel>, and C<$c-Eview> -should be used instead. - -=cut - -sub component { - my $c = shift; - - if (@_) { - - my $name = shift; - - my $appclass = ref $c || $c; - - my @names = ( - $name, "${appclass}::${name}", - map { "${appclass}::${_}::${name}" } - qw/Model M Controller C View V/ - ); - - my $comp = $c->_comp_explicit(@names); - return $c->_filter_component( $comp, @_ ) if defined($comp); - - $comp = $c->_comp_search($name); - return $c->_filter_component( $comp, @_ ) if defined($comp); - } - - return sort keys %{ $c->components }; -} - =head2 $c->controller($name) Gets a L instance by name. $c->controller('Foo')->do_stuff; -If name is omitted, will return the controller for the dispatched action. +If the name is omitted, will return the controller for the dispatched +action. =cut @@ -481,8 +508,11 @@ Gets a L instance by name. $c->model('Foo')->do_stuff; -If the name is omitted, it will look for a config setting 'default_model', -or check if there is only one model, and forward to it if that's the case. +If the name is omitted, it will look for + - a model object in $c->stash{current_model_instance}, then + - a model name in $c->stash->{current_model}, then + - a config setting 'default_model', or + - check if there is only one model, and return it if that's the case. =cut @@ -491,20 +521,41 @@ sub model { return $c->_filter_component( $c->_comp_prefixes( $name, qw/Model M/ ), @args ) if $name; - return $c->component( $c->config->{default_model} ) - if $c->config->{default_model}; + if (ref $c) { + return $c->stash->{current_model_instance} + if $c->stash->{current_model_instance}; + return $c->model( $c->stash->{current_model} ) + if $c->stash->{current_model}; + return $c->model( $c->config->{default_model} ) + if $c->config->{default_model}; + } return $c->_filter_component( $c->_comp_singular(qw/Model M/), @args ); } +=head2 $c->controllers + +Returns the available names which can be passed to $c->controller + +=cut + +sub controllers { + my ( $c ) = @_; + return $c->_comp_names(qw/Controller C/); +} + + =head2 $c->view($name) Gets a L instance by name. $c->view('Foo')->do_stuff; -If the name is omitted, it will look for a config setting 'default_view', -or check if there is only one view, and forward to it if that's the case. +If the name is omitted, it will look for + - a view object in $c->stash{current_view_instance}, then + - a view name in $c->stash->{current_view}, then + - a config setting 'default_view', or + - check if there is only one view, and return it if that's the case. =cut @@ -513,12 +564,79 @@ sub view { return $c->_filter_component( $c->_comp_prefixes( $name, qw/View V/ ), @args ) if $name; - return $c->component( $c->config->{default_view} ) - if $c->config->{default_view}; + if (ref $c) { + return $c->stash->{current_view_instance} + if $c->stash->{current_view_instance}; + return $c->view( $c->stash->{current_view} ) + if $c->stash->{current_view}; + return $c->view( $c->config->{default_view} ) + if $c->config->{default_view}; + } return $c->_filter_component( $c->_comp_singular(qw/View V/) ); } -=head2 Class data and helper classes +=head2 $c->models + +Returns the available names which can be passed to $c->model + +=cut + +sub models { + my ( $c ) = @_; + return $c->_comp_names(qw/Model M/); +} + + +=head2 $c->views + +Returns the available names which can be passed to $c->view + +=cut + +sub views { + my ( $c ) = @_; + return $c->_comp_names(qw/View V/); +} + +=head2 $c->comp($name) + +=head2 $c->component($name) + +Gets a component object by name. This method is not recommended, +unless you want to get a specific component by full +class. C<< $c->controller >>, C<< $c->model >>, and C<< $c->view >> +should be used instead. + +=cut + +sub component { + my $c = shift; + + if (@_) { + + my $name = shift; + + my $appclass = ref $c || $c; + + my @names = ( + $name, "${appclass}::${name}", + map { "${appclass}::${_}::${name}" } + qw/Model M Controller C View V/ + ); + + my $comp = $c->_comp_explicit(@names); + return $c->_filter_component( $comp, @_ ) if defined($comp); + + $comp = $c->_comp_search($name); + return $c->_filter_component( $comp, @_ ) if defined($comp); + } + + return sort keys %{ $c->components }; +} + + + +=head2 CLASS DATA AND HELPER CLASSES =head2 $c->config @@ -526,12 +644,14 @@ Returns or takes a hashref containing the application's configuration. __PACKAGE__->config( { db => 'dsn:SQLite:foo.db' } ); -You can also use a L config file like myapp.yml in your -applications home directory. +You can also use a C, C or C config file +like myapp.yml in your applications home directory. See +L. --- db: dsn:SQLite:foo.db + =cut sub config { @@ -543,10 +663,31 @@ sub config { $c->NEXT::config(@_); } +=head2 $c->log + +Returns the logging object instance. Unless it is already set, Catalyst +sets this up with a L object. To use your own log class, +set the logger with the C<< __PACKAGE__->log >> method prior to calling +C<< __PACKAGE__->setup >>. + + __PACKAGE__->log( MyLogger->new ); + __PACKAGE__->setup; + +And later: + + $c->log->info( 'Now logging with my own logger!' ); + +Your log class should implement the methods described in +L. + + =head2 $c->debug Overload to enable debug messages (same as -Debug option). +Note that this is a static method, not an accessor and should be overloaded +by declaring "sub debug { 1 }" in your MyApp.pm, not by calling $c->debug(1). + =cut sub debug { 0 } @@ -561,31 +702,13 @@ L. Returns the engine instance. Stringifies to the class name. See L. -=head2 $c->log - -Returns the logging object instance. Unless it is already set, Catalyst sets -this up with a L object. To use your own log class, set the -logger with the C<< __PACKAGE__->log >> method prior to calling -C<< __PACKAGE__->setup >>. - - __PACKAGE__->log( MyLogger->new ); - __PACKAGE__->setup; - -And later: - - $c->log->info( 'Now logging with my own logger!' ); - -Your log class should implement the methods described in the -L man page. - -=cut =head2 UTILITY METHODS =head2 $c->path_to(@path) -Merges C<@path> with C<$c-Econfig-E{home}> and returns a -L object. +Merges C<@path> with C<< $c->config->{home} >> and returns a +L object. For example: @@ -645,6 +768,9 @@ Catalyst> line. sub setup { my ( $class, @arguments ) = @_; + $class->log->warn("Running setup twice is not a good idea.") + if ( $class->setup_finished ); + unless ( $class->isa('Catalyst') ) { Catalyst::Exception->throw( @@ -689,8 +815,9 @@ sub setup { } } - $class->log->warn( - <<"EOF") if ( $ENV{CATALYST_SCRIPT_GEN} && ( $ENV{CATALYST_SCRIPT_GEN} < $Catalyst::CATALYST_SCRIPT_GEN ) ); + eval { require Catalyst::Devel; }; + if( !$@ && $ENV{CATALYST_SCRIPT_GEN} && ( $ENV{CATALYST_SCRIPT_GEN} < $Catalyst::Devel::CATALYST_SCRIPT_GEN ) ) { + $class->log->warn(<<"EOF"); You are running an old script! Please update by running (this will overwrite existing files): @@ -698,23 +825,17 @@ You are running an old script! or (this will not overwrite existing files): catalyst.pl -scripts $class -EOF +EOF + } + if ( $class->debug ) { - - my @plugins = (); - - { - no strict 'refs'; - @plugins = - map { $_ . ' ' . ( $_->VERSION || '' ) } - grep { /^Catalyst::Plugin/ } @{"$class\::ISA"}; - } + my @plugins = map { "$_ " . ( $_->VERSION || '' ) } $class->registered_plugins; if (@plugins) { - my $t = Text::SimpleTable->new(76); + my $t = Text::SimpleTable->new(74); $t->row($_) for @plugins; - $class->log->debug( "Loaded plugins:\n" . $t->draw ); + $class->log->debug( "Loaded plugins:\n" . $t->draw . "\n" ); } my $dispatcher = $class->dispatcher; @@ -744,12 +865,12 @@ EOF $class->setup_components; if ( $class->debug ) { - my $t = Text::SimpleTable->new( [ 65, 'Class' ], [ 8, 'Type' ] ); + my $t = Text::SimpleTable->new( [ 63, 'Class' ], [ 8, 'Type' ] ); for my $comp ( sort keys %{ $class->components } ) { my $type = ref $class->components->{$comp} ? 'instance' : 'class'; $t->row( $comp, $type ); } - $class->log->debug( "Loaded components:\n" . $t->draw ) + $class->log->debug( "Loaded components:\n" . $t->draw . "\n" ) if ( keys %{ $class->components } ); } @@ -767,14 +888,20 @@ EOF $class->setup_finished(1); } -=head2 $c->uri_for( $path, [ @args ] ) +=head2 $c->uri_for( $path, @args?, \%query_values? ) + +Merges path with C<< $c->request->base >> for absolute URIs and with +C<< $c->namespace >> for relative URIs, then returns a normalized L +object. If any args are passed, they are added at the end of the path. +If the last argument to C is a hash reference, it is assumed to +contain GET parameter key/value pairs, which will be appended to the URI +in standard fashion. -Merges path with C<$c-Erequest-Ebase> for absolute uri's and -with C<$c-Enamespace> for relative uri's, then returns a -normalized L object. If any args are passed, they are added at the -end of the path. If the last argument to uri_for is a hash reference, -it is assumed to contain GET parameter key/value pairs, which will be -appended to the URI in standard fashion. +Instead of C<$path>, you can also optionally pass a C<$action> object +which will be resolved to a path using +C<< $c->dispatcher->uri_for_action >>; if the first element of +C<@args> is an arrayref it is treated as a list of captures to be passed +to C. =cut @@ -784,20 +911,38 @@ sub uri_for { my $basepath = $base->path; $basepath =~ s/\/$//; $basepath .= '/'; - my $namespace = $c->namespace; + my $namespace = $c->namespace || ''; + + if ( Scalar::Util::blessed($path) ) { # action object + my $captures = ( scalar @args && ref $args[0] eq 'ARRAY' + ? shift(@args) + : [] ); + $path = $c->dispatcher->uri_for_action($path, $captures); + return undef unless defined($path); + $path = '/' if $path eq ''; + } # massage namespace, empty if absolute path - $namespace =~ s/^\///; + $namespace =~ s/^\/// if $namespace; $namespace .= '/' if $namespace; $path ||= ''; $namespace = '' if $path =~ /^\//; $path =~ s/^\///; + $path =~ s/\?/%3F/g; my $params = ( scalar @args && ref $args[$#args] eq 'HASH' ? pop @args : {} ); + for my $value ( values %$params ) { + next unless defined $value; + for ( ref $value eq 'ARRAY' ? @$value : $value ) { + $_ = "$_"; + utf8::encode( $_ ); + } + }; + # join args with '/', or a blank string - my $args = ( scalar @args ? '/' . join( '/', @args ) : '' ); + my $args = ( scalar @args ? '/' . join( '/', map {s/\?/%3F/g; $_} @args ) : '' ); $args =~ s/^\/// unless $path; my $res = URI->new_abs( URI->new_abs( "$path$args", "$basepath$namespace" ), $base ) @@ -840,7 +985,6 @@ sub welcome_message { text-align: left; background-color: #ccc; border: 1px solid #aaa; - -moz-border-radius: 10px; } p, h1, h2 { margin-left: 20px; @@ -870,7 +1014,6 @@ sub welcome_message { margin: 10px; background-color: #fff; border: 1px solid #aaa; - -moz-border-radius: 10px; } h1 { font-size: 0.9em; @@ -903,7 +1046,7 @@ sub welcome_message {

Catalyst Logo

-

Welcome to the wonderful world of Catalyst. +

Welcome to the world of Catalyst. This MVC framework will make web development something you had never expected it to be: Fun, rewarding, and quick.

@@ -911,10 +1054,14 @@ sub welcome_message {

That really depends on what you want to do. We do, however, provide you with a few starting points.

If you want to jump right into web development with Catalyst - you might want to check out the documentation.

-
perldoc Catalyst::Manual::Intro
-perldoc Catalyst::Manual::Tutorial
-perldoc Catalyst::Manual
+ you might want want to start with a tutorial.

+
perldoc Catalyst::Manual::Tutorial
+
+

Afterwards you can go on to check out a more complete look at our features.

+
+perldoc Catalyst::Manual::Intro
+
+

What to do next?

Next it's time to write an actual application. Use the helper scripts to generate controllers, @@ -994,7 +1141,10 @@ that will be dumped on the error page in debug mode. sub dump_these { my $c = shift; - [ Request => $c->req ], [ Response => $c->res ], [ Stash => $c->stash ],; + [ Request => $c->req ], + [ Response => $c->res ], + [ Stash => $c->stash ], + [ Config => $c->config ]; } =head2 $c->engine_class @@ -1015,7 +1165,7 @@ sub execute { if ( $c->depth >= $RECURSION ) { my $action = "$code"; - $action = "/$action" unless $action =~ /\-\>/; + $action = "/$action" unless $action =~ /->/; my $error = qq/Deep recursion detected calling "$action"/; $c->log->error($error); $c->error($error); @@ -1023,112 +1173,122 @@ sub execute { return $c->state; } - if ( $c->debug ) { - my $action = "$code"; - $action = "/$action" unless $action =~ /\-\>/; - $c->counter->{"$code"}++; - - # determine if the call was the result of a forward - # this is done by walking up the call stack and looking for a calling - # sub of Catalyst::forward before the eval - my $callsub = q{}; - for my $index ( 1 .. 10 ) { - last - if ( ( caller($index) )[0] eq 'Catalyst' - && ( caller($index) )[3] eq '(eval)' ); - - if ( ( caller($index) )[3] =~ /forward$/ ) { - $callsub = ( caller($index) )[3]; - $action = "-> $action"; - last; + my $stats_info = $c->_stats_start_execute( $code ) if $c->debug; + + push( @{ $c->stack }, $code ); + + eval { $c->state( &$code( $class, $c, @{ $c->req->args } ) || 0 ) }; + + $c->_stats_finish_execute( $stats_info ) if $c->debug and $stats_info; + + my $last = pop( @{ $c->stack } ); + + if ( my $error = $@ ) { + if ( !ref($error) and $error eq $DETACH ) { die $DETACH if $c->depth > 1 } + else { + unless ( ref $error ) { + no warnings 'uninitialized'; + chomp $error; + my $class = $last->class; + my $name = $last->name; + $error = qq/Caught exception in $class->$name "$error"/; } + $c->error($error); + $c->state(0); } + } + return $c->state; +} - my $node = Tree::Simple->new( - { - action => $action, - elapsed => undef, # to be filled in later - } - ); - $node->setUID( "$code" . $c->counter->{"$code"} ); +sub _stats_start_execute { + my ( $c, $code ) = @_; - unless ( ( $code->name =~ /^_.*/ ) - && ( !$c->config->{show_internal_actions} ) ) - { + return if ( ( $code->name =~ /^_.*/ ) + && ( !$c->config->{show_internal_actions} ) ); - # is this a root-level call or a forwarded call? - if ( $callsub =~ /forward$/ ) { - - # forward, locate the caller - if ( my $parent = $c->stack->[-1] ) { - my $visitor = Tree::Simple::Visitor::FindByUID->new; - $visitor->searchForUID( - "$parent" . $c->counter->{"$parent"} ); - $c->{stats}->accept($visitor); - if ( my $result = $visitor->getResult ) { - $result->addChild($node); - } - } - else { + $c->counter->{"$code"}++; - # forward with no caller may come from a plugin - $c->{stats}->addChild($node); - } - } - else { + my $action = "$code"; + $action = "/$action" unless $action =~ /->/; - # root-level call - $c->{stats}->addChild($node); - } + # determine if the call was the result of a forward + # this is done by walking up the call stack and looking for a calling + # sub of Catalyst::forward before the eval + my $callsub = q{}; + for my $index ( 2 .. 11 ) { + last + if ( ( caller($index) )[0] eq 'Catalyst' + && ( caller($index) )[3] eq '(eval)' ); + + if ( ( caller($index) )[3] =~ /forward$/ ) { + $callsub = ( caller($index) )[3]; + $action = "-> $action"; + last; } } - push( @{ $c->stack }, $code ); - my $elapsed = 0; - my $start = 0; - $start = [gettimeofday] if $c->debug; - eval { $c->state( &$code( $class, $c, @{ $c->req->args } ) || 0 ) }; - $elapsed = tv_interval($start) if $c->debug; - - if ( $c->debug ) { - unless ( ( $code->name =~ /^_.*/ ) - && ( !$c->config->{show_internal_actions} ) ) + my $node = Tree::Simple->new( { + action => $action, + elapsed => undef, # to be filled in later + comment => "", + } + ); + $node->setUID( "$code" . $c->counter->{"$code"} ); - # FindByUID uses an internal die, so we save the existing error - my $error = $@; + # is this a root-level call or a forwarded call? + if ( $callsub =~ /forward$/ ) { - # locate the node in the tree and update the elapsed time + # forward, locate the caller + if ( my $parent = $c->stack->[-1] ) { my $visitor = Tree::Simple::Visitor::FindByUID->new; - $visitor->searchForUID( "$code" . $c->counter->{"$code"} ); - $c->{stats}->accept($visitor); + $visitor->searchForUID( + "$parent" . $c->counter->{"$parent"} ); + $c->stats->accept($visitor); if ( my $result = $visitor->getResult ) { - my $value = $result->getNodeValue; - $value->{elapsed} = sprintf( '%fs', $elapsed ); - $result->setNodeValue($value); + $result->addChild($node); } + } + else { - # restore error - $@ = $error || undef; + # forward with no caller may come from a plugin + $c->stats->addChild($node); } } - my $last = ${ $c->stack }[-1]; - pop( @{ $c->stack } ); + else { - if ( my $error = $@ ) { - if ( $error eq $DETACH ) { die $DETACH if $c->depth > 1 } - else { - unless ( ref $error ) { - chomp $error; - my $class = $last->class; - my $name = $last->name; - $error = qq/Caught exception in $class->$name "$error"/; - } - $c->error($error); - $c->state(0); - } + # root-level call + $c->stats->addChild($node); } - return $c->state; + + return { + start => [gettimeofday], + node => $node, + }; +} + +sub _stats_finish_execute { + my ( $c, $info ) = @_; + my $elapsed = tv_interval $info->{start}; + my $value = $info->{node}->getNodeValue; + $value->{elapsed} = sprintf( '%fs', $elapsed ); +} + +=head2 $c->_localize_fields( sub { }, \%keys ); + +=cut + +sub _localize_fields { + my ( $c, $localized, $code ) = ( @_ ); + + my $request = delete $localized->{request} || {}; + my $response = delete $localized->{response} || {}; + + local @{ $c }{ keys %$localized } = values %$localized; + local @{ $c->request }{ keys %$request } = values %$request; + local @{ $c->response }{ keys %$response } = values %$response; + + $code->(); } =head2 $c->finalize @@ -1146,7 +1306,7 @@ sub finalize { # Allow engine to handle finalize flow (for POE) if ( $c->engine->can('finalize') ) { - $c->engine->finalize( $c ); + $c->engine->finalize($c); } else { @@ -1166,6 +1326,24 @@ sub finalize { $c->finalize_body; } + + if ($c->debug) { + my $elapsed = sprintf '%f', tv_interval($c->stats->getNodeValue); + my $av = sprintf '%.3f', ( $elapsed == 0 ? '??' : ( 1 / $elapsed ) ); + + my $t = Text::SimpleTable->new( [ 62, 'Action' ], [ 9, 'Time' ] ); + $c->stats->traverse( + sub { + my $action = shift; + my $stat = $action->getNodeValue; + $t->row( ( q{ } x $action->getDepth ) . $stat->{action} . $stat->{comment}, + $stat->{elapsed} || '??' ); + } + ); + + $c->log->info( + "Request took ${elapsed}s ($av/s)\n" . $t->draw . "\n" ); + } return $c->response->status; } @@ -1293,38 +1471,16 @@ sub handle_request { # Always expect worst case! my $status = -1; eval { - my $stats = ( $class->debug ) ? Tree::Simple->new: q{}; - - my $handler = sub { - my $c = $class->prepare(@arguments); - $c->{stats} = $stats; - $c->dispatch; - return $c->finalize; - }; - - if ( $class->debug ) { - my $start = [gettimeofday]; - $status = &$handler; - my $elapsed = tv_interval $start; - $elapsed = sprintf '%f', $elapsed; - my $av = sprintf '%.3f', - ( $elapsed == 0 ? '??' : ( 1 / $elapsed ) ); - my $t = Text::SimpleTable->new( [ 64, 'Action' ], [ 9, 'Time' ] ); - - $stats->traverse( - sub { - my $action = shift; - my $stat = $action->getNodeValue; - $t->row( ( q{ } x $action->getDepth ) . $stat->{action}, - $stat->{elapsed} || '??' ); - } - ); - - $class->log->info( - "Request took ${elapsed}s ($av/s)\n" . $t->draw ); + if ($class->debug) { + my $secs = time - $START || 1; + my $av = sprintf '%.3f', $COUNT / $secs; + my $time = localtime time; + $class->log->info("*** Request $COUNT ($av/s) [$$] [$time] ***"); } - else { $status = &$handler } + my $c = $class->prepare(@arguments); + $c->dispatch; + $status = $c->finalize; }; if ( my $error = $@ ) { @@ -1361,7 +1517,7 @@ sub prepare { parameters => {}, query_parameters => {}, secure => 0, - snippets => [], + captures => [], uploads => {} } ), @@ -1378,21 +1534,17 @@ sub prepare { } ); + if ( $c->debug ) { + $c->stats(Tree::Simple->new([gettimeofday])); + $c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION ); + } + # For on-demand data $c->request->{_context} = $c; $c->response->{_context} = $c; weaken( $c->request->{_context} ); weaken( $c->response->{_context} ); - if ( $c->debug ) { - my $secs = time - $START || 1; - my $av = sprintf '%.3f', $COUNT / $secs; - $c->log->debug('**********************************'); - $c->log->debug("* Request $COUNT ($av/s) [$$]"); - $c->log->debug('**********************************'); - $c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION ); - } - # Allow engine to direct the prepare flow (for POE) if ( $c->engine->can('prepare') ) { $c->engine->prepare( $c, @arguments ); @@ -1410,7 +1562,7 @@ sub prepare { } my $method = $c->req->method || ''; - my $path = $c->req->path || ''; + my $path = $c->req->path || '/'; my $address = $c->req->address || ''; $c->log->debug(qq/"$method" request for "$path" from "$address"/) @@ -1423,7 +1575,7 @@ sub prepare { =head2 $c->prepare_action -Prepares action. +Prepares action. See L. =cut @@ -1447,7 +1599,7 @@ sub prepare_body { $c->prepare_uploads; if ( $c->debug && keys %{ $c->req->body_parameters } ) { - my $t = Text::SimpleTable->new( [ 37, 'Key' ], [ 36, 'Value' ] ); + my $t = Text::SimpleTable->new( [ 35, 'Parameter' ], [ 36, 'Value' ] ); for my $key ( sort keys %{ $c->req->body_parameters } ) { my $param = $c->req->body_parameters->{$key}; my $value = defined($param) ? $param : ''; @@ -1462,6 +1614,8 @@ sub prepare_body { Prepares a chunk of data before sending it to L. +See L. + =cut sub prepare_body_chunk { @@ -1539,7 +1693,7 @@ sub prepare_query_parameters { $c->engine->prepare_query_parameters( $c, @_ ); if ( $c->debug && keys %{ $c->request->query_parameters } ) { - my $t = Text::SimpleTable->new( [ 37, 'Key' ], [ 36, 'Value' ] ); + my $t = Text::SimpleTable->new( [ 35, 'Parameter' ], [ 36, 'Value' ] ); for my $key ( sort keys %{ $c->req->query_parameters } ) { my $param = $c->req->query_parameters->{$key}; my $value = defined($param) ? $param : ''; @@ -1579,8 +1733,8 @@ sub prepare_uploads { if ( $c->debug && keys %{ $c->request->uploads } ) { my $t = Text::SimpleTable->new( - [ 12, 'Key' ], - [ 28, 'Filename' ], + [ 12, 'Parameter' ], + [ 26, 'Filename' ], [ 18, 'Type' ], [ 9, 'Size' ] ); @@ -1616,7 +1770,7 @@ Reads a chunk of data from the request body. This method is designed to be used in a while loop, reading C<$maxlength> bytes on every call. C<$maxlength> defaults to the size of the request if not specified. -You have to set Cconfig-E{parse_on_demand}> to use this +You have to set C<< MyApp->config->{parse_on_demand} >> to use this directly. =cut @@ -1649,64 +1803,74 @@ sub setup_actions { my $c = shift; $c->dispatcher->setup_actions( $c, @_ ) } =head2 $c->setup_components -Sets up components. +Sets up components. Specify a C config option to pass +additional options directly to L. To add additional +search paths, specify a key named C as an array +reference. Items in the array beginning with C<::> will have the +application class name prepended to them. =cut sub setup_components { my $class = shift; - my $callback = sub { - my ( $component, $context ) = @_; - - unless ( $component->can('COMPONENT') ) { - return $component; + my @paths = qw( ::Controller ::C ::Model ::M ::View ::V ); + my $config = $class->config->{ setup_components }; + my $extra = delete $config->{ search_extra } || []; + + push @paths, @$extra; + + my $locator = Module::Pluggable::Object->new( + search_path => [ map { s/^(?=::)/$class/; $_; } @paths ], + %$config + ); + + for my $component ( sort { length $a <=> length $b } $locator->plugins ) { + Catalyst::Utils::ensure_class_loaded( $component, { ignore_loaded => 1 } ); + + my $module = $class->setup_component( $component ); + my %modules = ( + $component => $module, + map { + $_ => $class->setup_component( $_ ) + } Devel::InnerPackage::list_packages( $component ) + ); + + for my $key ( keys %modules ) { + $class->components->{ $key } = $modules{ $key }; } + } +} - my $suffix = Catalyst::Utils::class2classsuffix($component); - my $config = $class->config->{$suffix} || {}; - - my $instance; - - eval { $instance = $component->COMPONENT( $context, $config ); }; +=head2 $c->setup_component - if ( my $error = $@ ) { +=cut - chomp $error; +sub setup_component { + my( $class, $component ) = @_; - Catalyst::Exception->throw( message => - qq/Couldn't instantiate component "$component", "$error"/ ); - } + unless ( $component->can( 'COMPONENT' ) ) { + return $component; + } - Catalyst::Exception->throw( message => -qq/Couldn't instantiate component "$component", "COMPONENT() didn't return a object"/ - ) - unless ref $instance; - return $instance; - }; + my $suffix = Catalyst::Utils::class2classsuffix( $component ); + my $config = $class->config->{ $suffix } || {}; - eval "package $class;\n" . q!Module::Pluggable::Fast->import( - name => '_catalyst_components', - search => [ - "$class\::Controller", "$class\::C", - "$class\::Model", "$class\::M", - "$class\::View", "$class\::V" - ], - callback => $callback - ); - !; + my $instance = eval { $component->COMPONENT( $class, $config ); }; if ( my $error = $@ ) { - chomp $error; - Catalyst::Exception->throw( - message => qq/Couldn't load components "$error"/ ); + message => qq/Couldn't instantiate component "$component", "$error"/ + ); } - for my $component ( $class->_catalyst_components($class) ) { - $class->components->{ ref $component || $component } = $component; - } + Catalyst::Exception->throw( + message => + qq/Couldn't instantiate component "$component", "COMPONENT() didn't return an object-like value"/ + ) unless eval { $instance->can( 'can' ) }; + + return $instance; } =head2 $c->setup_dispatcher @@ -1735,11 +1899,8 @@ sub setup_dispatcher { $dispatcher = $class->dispatcher_class; } - $dispatcher->require; - - if ($@) { - Catalyst::Exception->throw( - message => qq/Couldn't load dispatcher "$dispatcher", "$@"/ ); + unless (Class::Inspector->loaded($dispatcher)) { + require Class::Inspector->filename($dispatcher); } # dispatcher instance @@ -1830,12 +1991,8 @@ sub setup_engine { $engine = $class->engine_class; } - $engine->require; - - if ($@) { - Catalyst::Exception->throw( message => -qq/Couldn't load engine "$engine" (maybe you forgot to install it?), "$@"/ - ); + unless (Class::Inspector->loaded($engine)) { + require Class::Inspector->filename($engine); } # check for old engines that are no longer compatible @@ -1888,6 +2045,7 @@ sub setup_home { } if ( $ENV{ uc($class) . '_HOME' } ) { + $class =~ s/::/_/g; $home = $ENV{ uc($class) . '_HOME' }; } @@ -1963,12 +2121,8 @@ the plugin name does not begin with C. my ( $proto, $plugin, $instant ) = @_; my $class = ref $proto || $proto; - $plugin->require; - - if ( my $error = $@ ) { - my $type = $instant ? "instant " : ''; - Catalyst::Exception->throw( - message => qq/Couldn't load ${type}plugin "$plugin", $error/ ); + unless (Class::Inspector->loaded($plugin)) { + require Class::Inspector->filename($plugin); } $proto->_plugins->{$plugin} = 1; @@ -1997,8 +2151,8 @@ the plugin name does not begin with C. =head2 $c->stack -Returns an arrayref of the internal execution stack (actions that are currently -executing). +Returns an arrayref of the internal execution stack (actions that are +currently executing). =head2 $c->write( $data ) @@ -2086,9 +2240,9 @@ If you do not wish to use the proxy support at all, you may set: =head1 THREAD SAFETY -Catalyst has been tested under Apache 2's threading mpm_worker, mpm_winnt, -and the standalone forking HTTP server on Windows. We believe the Catalyst -core to be thread-safe. +Catalyst has been tested under Apache 2's threading C, +C, and the standalone forking HTTP server on Windows. We +believe the Catalyst core to be thread-safe. If you plan to operate in a threaded environment, remember that all other modules you are using must also be thread-safe. Some modules, most notably