X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Runtime.git;a=blobdiff_plain;f=lib%2FCatalyst.pm;h=8480fca3450b072bbcd3c819277b1c21f2f3af49;hp=53cf38689e6fbe9ab2a4586b7512ca6de7471fca;hb=3fb903fd7b2a932d4a124dbb3c364275b665858d;hpb=228f9466174380edff95b369034a04fd4b9cbb82 diff --git a/lib/Catalyst.pm b/lib/Catalyst.pm index 53cf386..8480fca 100644 --- a/lib/Catalyst.pm +++ b/lib/Catalyst.pm @@ -1,7 +1,12 @@ package Catalyst; -use strict; -use base 'Catalyst::Component'; +# we don't need really need this, but if we load it before MRO::Compat gets +# loaded (via Moose and Class::MOP), we can avoid some nasty warnings +use Class::C3; + +use Moose; +use Class::MOP::Object (); +extends 'Catalyst::Component'; use bytes; use Catalyst::Exception; use Catalyst::Log; @@ -13,7 +18,6 @@ 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 (); @@ -30,40 +34,54 @@ use Carp qw/croak carp/; BEGIN { require 5.008001; } -__PACKAGE__->mk_accessors( - qw/counter request response state action stack namespace stats/ -); - -attributes->import( __PACKAGE__, \&namespace, 'lvalue' ); +has stack => (is => 'rw', default => sub { [] }); +has stash => (is => 'rw', default => sub { {} }); +has state => (is => 'rw', default => 0); +has stats => (is => 'rw'); +has action => (is => 'rw'); +has counter => (is => 'rw', default => sub { {} }); +has request => (is => 'rw', default => sub { $_[0]->request_class->new({}) }, required => 1, lazy => 1); +has response => (is => 'rw', default => sub { $_[0]->response_class->new({}) }, required => 1, lazy => 1); +has namespace => (is => 'rw'); sub depth { scalar @{ shift->stack || [] }; } +sub comp { shift->component(@_) } -# Laziness++ -*comp = \&component; -*req = \&request; -*res = \&response; +sub req { + # carp "the use of req() is deprecated in favour of request()"; + my $self = shift; return $self->request(@_); +} +sub res { + # carp "the use of res() is deprecated in favour of response()"; + my $self = shift; return $self->response(@_); +} # For backwards compatibility -*finalize_output = \&finalize_body; +sub finalize_output { shift->finalize_body(@_) }; # For statistics our $COUNT = 1; our $START = time; our $RECURSION = 1000; our $DETACH = "catalyst_detach\n"; +our $GO = "catalyst_go\n"; +#I imagine that very few of these really need to be class variables. if any. +#maybe we should just make them attributes with a default? __PACKAGE__->mk_classdata($_) for qw/components arguments dispatcher engine log dispatcher_class - engine_class context_class request_class response_class setup_finished/; + engine_class context_class request_class response_class stats_class + setup_finished/; __PACKAGE__->dispatcher_class('Catalyst::Dispatcher'); __PACKAGE__->engine_class('Catalyst::Engine::CGI'); __PACKAGE__->request_class('Catalyst::Request'); __PACKAGE__->response_class('Catalyst::Response'); +__PACKAGE__->stats_class('Catalyst::Stats'); # Remember to update this in Catalyst::Runtime as well! -our $VERSION = '5.7011'; +our $VERSION = '5.8000_04'; sub import { my ( $class, @arguments ) = @_; @@ -72,11 +90,17 @@ sub import { # callers @ISA. return unless $class eq 'Catalyst'; - my $caller = caller(0); + my $caller = caller(); + return if $caller eq 'main'; + my $meta = Moose::Meta::Class->initialize($caller); + #Moose->import({ into => $caller }); #do we want to do this? unless ( $caller->isa('Catalyst') ) { - no strict 'refs'; - push @{"$caller\::ISA"}, $class, 'Catalyst::Controller'; + my @superclasses = ($meta->superclasses, $class, 'Catalyst::Controller'); + $meta->superclasses(@superclasses); + } + unless( $meta->has_method('meta') ){ + $meta->add_method(meta => sub { Moose::Meta::Class->initialize("${caller}") } ); } $caller->arguments( [@arguments] ); @@ -97,7 +121,7 @@ documentation and tutorials. catalyst.pl MyApp # add models, views, controllers - script/myapp_create.pl model MyDatabase DBIC::Schema create=dynamic dbi:SQLite:/path/to/db + script/myapp_create.pl model MyDatabase DBIC::Schema create=static dbi:SQLite:/path/to/db script/myapp_create.pl view MyTemplate TT script/myapp_create.pl controller Search @@ -240,6 +264,17 @@ MYAPP_WEB_HOME. If both variables are set, the MYAPP_HOME one will be used. Specifies log level. +=head2 -Stats + +Enables statistics collection and reporting. You can also force this setting +from the system environment with CATALYST_STATS or _STATS. The +environment settings override the application, with _STATS having the +highest priority. + +e.g. + + use Catalyst qw/-Stats=1/ + =head1 METHODS =head2 INFORMATION ABOUT THE CURRENT REQUEST @@ -299,7 +334,7 @@ your code like this: =cut -sub forward { my $c = shift; $c->dispatcher->forward( $c, @_ ) } +sub forward { my $c = shift; no warnings 'recursion'; $c->dispatcher->forward( $c, @_ ) } =head2 $c->detach( $action [, \@arguments ] ) @@ -316,6 +351,40 @@ When called with no arguments it escapes the processing chain entirely. sub detach { my $c = shift; $c->dispatcher->detach( $c, @_ ) } +=head2 $c->visit( $action [, \@arguments ] ) + +=head2 $c->visit( $class, $method, [, \@arguments ] ) + +Almost the same as C, but does a full dispatch, instead of just +calling the new C<$action> / C<$class-E$method>. This means that C, +C and the method you go to are called, just like a new request. + +C<$c-Estash> is kept unchanged. + +In effect, C allows you to "wrap" another action, just as it +would have been called by dispatching from a URL, while the analogous +C allows you to transfer control to another action as if it had +been reached directly from a URL. + +=cut + +sub visit { my $c = shift; $c->dispatcher->visit( $c, @_ ) } + +=head2 $c->go( $action [, \@arguments ] ) + +=head2 $c->go( $class, $method, [, \@arguments ] ) + +Almost the same as C, but does a full dispatch like C, +instead of just calling the new C<$action> / +C<$class-E$method>. This means that C, C and the +method you visit are called, just like a new request. + +C<$c-Estash> is kept unchanged. + +=cut + +sub go { my $c = shift; $c->dispatcher->go( $c, @_ ) } + =head2 $c->response =head2 $c->res @@ -341,17 +410,21 @@ Catalyst). =cut -sub stash { +around stash => sub { + my $orig = shift; my $c = shift; + my $stash = $orig->($c); 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}; + my $new_stash = @_ > 1 ? {@_} : $_[0]; + croak('stash takes a hash or hashref') unless ref $new_stash; + foreach my $key ( keys %$new_stash ) { + $stash->{$key} = $new_stash->{$key}; } } - return $c->{stash}; -} + + return $stash; +}; + =head2 $c->error @@ -403,87 +476,66 @@ sub clear_errors { $c->error(0); } +# search components given a name and some prefixes +sub _comp_search_prefixes { + my ( $c, $name, @prefixes ) = @_; + my $appclass = ref $c || $c; + my $filter = "^${appclass}::(" . join( '|', @prefixes ) . ')::'; -# search via regex -sub _comp_search { - my ( $c, @names ) = @_; - - foreach my $name (@names) { - foreach my $component ( keys %{ $c->components } ) { - return $c->components->{$component} if $component =~ /$name/i; - } - } + # map the original component name to the sub part that we will search against + my %eligible = map { my $n = $_; $n =~ s{^$appclass\::[^:]+::}{}; $_ => $n; } + grep { /$filter/ } keys %{ $c->components }; - return undef; -} + # undef for a name will return all + return keys %eligible if !defined $name; -# try explicit component names -sub _comp_explicit { - my ( $c, @names ) = @_; + my $query = ref $name ? $name : qr/^$name$/i; + my @result = grep { $eligible{$_} =~ m{$query} } keys %eligible; - foreach my $try (@names) { - return $c->components->{$try} if ( exists $c->components->{$try} ); - } + return map { $c->components->{ $_ } } @result if @result; - return undef; -} + # if we were given a regexp to search against, we're done. + return if ref $name; -# like component, but try just these prefixes before regex searching, -# and do not try to return "sort keys %{ $c->components }" -sub _comp_prefixes { - my ( $c, $name, @prefixes ) = @_; + # regexp fallback + $query = qr/$name/i; + @result = map { $c->components->{ $_ } } grep { $eligible{ $_ } =~ m{$query} } keys %eligible; - my $appclass = ref $c || $c; + # no results? try against full names + if( !@result ) { + @result = map { $c->components->{ $_ } } grep { m{$query} } keys %eligible; + } - my @names = map { "${appclass}::${_}::${name}" } @prefixes; + # don't warn if we didn't find any results, it just might not exist + if( @result ) { + $c->log->warn( qq(Found results for "${name}" using regexp fallback.) ); + $c->log->warn( 'Relying on the regexp fallback behavior for component resolution is unreliable and unsafe.' ); + $c->log->warn( 'If you really want to search, pass in a regexp as the argument.' ); + } - my $comp = $c->_comp_explicit(@names); - return $comp if defined($comp); - $comp = $c->_comp_search($name); - return $comp; + return @result; } # 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; - } - } - } + my $filter = "^${appclass}::(" . join( '|', @prefixes ) . ')::'; + my @names = map { s{$filter}{}; $_; } $c->_comp_search_prefixes( undef, @prefixes ); return @names; } -# Return a component if only one matches. -sub _comp_singular { - my ( $c, @prefixes ) = @_; - - my $appclass = ref $c || $c; - - my ( $comp, $rest ) = - map { $c->_comp_search("^${appclass}::${_}::") } @prefixes; - return $comp unless $rest; -} - # Filter a component before returning by calling ACCEPT_CONTEXT if available sub _filter_component { my ( $c, $comp, @args ) = @_; + if ( eval { $comp->can('ACCEPT_CONTEXT'); } ) { return $comp->ACCEPT_CONTEXT( $c, @args ); } - else { return $comp } + + return $comp; } =head2 COMPONENT ACCESSORS @@ -497,13 +549,23 @@ Gets a L instance by name. If the name is omitted, will return the controller for the dispatched action. +If you want to search for controllers, pass in a regexp as the argument. + + # find all controllers that start with Foo + my @foo_controllers = $c->controller(qr{^Foo}); + + =cut sub controller { my ( $c, $name, @args ) = @_; - return $c->_filter_component( $c->_comp_prefixes( $name, qw/Controller C/ ), - @args ) - if ($name); + + if( $name ) { + my @result = $c->_comp_search_prefixes( $name, qw/Controller C/ ); + return map { $c->_filter_component( $_, @args ) } @result if ref $name; + return $c->_filter_component( $result[ 0 ], @args ); + } + return $c->component( $c->action->class ); } @@ -516,18 +578,27 @@ Gets a L instance by name. Any extra arguments are directly passed to ACCEPT_CONTEXT. If the name is omitted, it will look for - - a model object in $c->stash{current_model_instance}, then + - 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. +If you want to search for models, pass in a regexp as the argument. + + # find all models that start with Foo + my @foo_models = $c->model(qr{^Foo}); + =cut sub model { my ( $c, $name, @args ) = @_; - return $c->_filter_component( $c->_comp_prefixes( $name, qw/Model M/ ), - @args ) - if $name; + + if( $name ) { + my @result = $c->_comp_search_prefixes( $name, qw/Model M/ ); + return map { $c->_filter_component( $_, @args ) } @result if ref $name; + return $c->_filter_component( $result[ 0 ], @args ); + } + if (ref $c) { return $c->stash->{current_model_instance} if $c->stash->{current_model_instance}; @@ -536,19 +607,18 @@ sub model { } return $c->model( $c->config->{default_model} ) if $c->config->{default_model}; - return $c->_filter_component( $c->_comp_singular(qw/Model M/) ); - -} -=head2 $c->controllers - -Returns the available names which can be passed to $c->controller + my( $comp, $rest ) = $c->_comp_search_prefixes( undef, qw/Model M/); -=cut + if( $rest ) { + $c->log->warn( 'Calling $c->model() will return a random model unless you specify one of:' ); + $c->log->warn( '* $c->config->{default_model} # the name of the default model to use' ); + $c->log->warn( '* $c->stash->{current_model} # the name of the model to use for this request' ); + $c->log->warn( '* $c->stash->{current_model_instance} # the instance of the model to use for this request' ); + $c->log->warn( 'NB: in version 5.80, the "random" behavior will not work at all.' ); + } -sub controllers { - my ( $c ) = @_; - return $c->_comp_names(qw/Controller C/); + return $c->_filter_component( $comp ); } @@ -561,18 +631,27 @@ Gets a L instance by name. Any extra arguments are directly passed to ACCEPT_CONTEXT. If the name is omitted, it will look for - - a view object in $c->stash{current_view_instance}, then + - 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. +If you want to search for views, pass in a regexp as the argument. + + # find all views that start with Foo + my @foo_views = $c->view(qr{^Foo}); + =cut sub view { my ( $c, $name, @args ) = @_; - return $c->_filter_component( $c->_comp_prefixes( $name, qw/View V/ ), - @args ) - if $name; + + if( $name ) { + my @result = $c->_comp_search_prefixes( $name, qw/View V/ ); + return map { $c->_filter_component( $_, @args ) } @result if ref $name; + return $c->_filter_component( $result[ 0 ], @args ); + } + if (ref $c) { return $c->stash->{current_view_instance} if $c->stash->{current_view_instance}; @@ -581,7 +660,29 @@ sub view { } return $c->view( $c->config->{default_view} ) if $c->config->{default_view}; - return $c->_filter_component( $c->_comp_singular(qw/View V/) ); + + my( $comp, $rest ) = $c->_comp_search_prefixes( undef, qw/View V/); + + if( $rest ) { + $c->log->warn( 'Calling $c->view() will return a random view unless you specify one of:' ); + $c->log->warn( '* $c->config->{default_view} # the name of the default view to use' ); + $c->log->warn( '* $c->stash->{current_view} # the name of the view to use for this request' ); + $c->log->warn( '* $c->stash->{current_view_instance} # the instance of the view to use for this request' ); + $c->log->warn( 'NB: in version 5.80, the "random" behavior will not work at all.' ); + } + + return $c->_filter_component( $comp ); +} + +=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->models @@ -616,35 +717,52 @@ 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. +If C<$name> is a regexp, a list of components matched against the full +component name will be returned. + =cut sub component { - my $c = shift; + my ( $c, $name, @args ) = @_; - if (@_) { + if( $name ) { + my $comps = $c->components; - my $name = shift; + if( !ref $name ) { + # is it the exact name? + return $c->_filter_component( $comps->{ $name }, @args ) + if exists $comps->{ $name }; - my $appclass = ref $c || $c; + # perhaps we just omitted "MyApp"? + my $composed = ( ref $c || $c ) . "::${name}"; + return $c->_filter_component( $comps->{ $composed }, @args ) + if exists $comps->{ $composed }; - my @names = ( - $name, "${appclass}::${name}", - map { "${appclass}::${_}::${name}" } - qw/Model M Controller C View V/ - ); + # search all of the models, views and controllers + my( $comp ) = $c->_comp_search_prefixes( $name, qw/Model M Controller C View V/ ); + return $c->_filter_component( $comp, @args ) if $comp; + } + + # This is here so $c->comp( '::M::' ) works + my $query = ref $name ? $name : qr{$name}i; - my $comp = $c->_comp_explicit(@names); - return $c->_filter_component( $comp, @_ ) if defined($comp); + my @result = grep { m{$query} } keys %{ $c->components }; + return map { $c->_filter_component( $_, @args ) } @result if ref $name; - $comp = $c->_comp_search($name); - return $c->_filter_component( $comp, @_ ) if defined($comp); + if( $result[ 0 ] ) { + $c->log->warn( qq(Found results for "${name}" using regexp fallback.) ); + $c->log->warn( 'Relying on the regexp fallback behavior for component resolution' ); + $c->log->warn( 'is unreliable and unsafe. You have been warned' ); + return $c->_filter_component( $result[ 0 ], @args ); + } + + # I would expect to return an empty list here, but that breaks back-compat } + # fallback return sort keys %{ $c->components }; } - - =head2 CLASS DATA AND HELPER CLASSES =head2 $c->config @@ -663,14 +781,15 @@ L. =cut -sub config { +around config => sub { + my $orig = shift; my $c = shift; $c->log->warn("Setting config after setup has been run is not a good idea.") if ( @_ and $c->setup_finished ); - $c->NEXT::config(@_); -} + $c->$orig(@_); +}; =head2 $c->log @@ -776,7 +895,6 @@ Catalyst> line. sub setup { my ( $class, @arguments ) = @_; - $class->log->warn("Running setup twice is not a good idea.") if ( $class->setup_finished ); @@ -813,6 +931,7 @@ sub setup { $class->setup_plugins( delete $flags->{plugins} ); $class->setup_dispatcher( delete $flags->{dispatcher} ); $class->setup_engine( delete $flags->{engine} ); + $class->setup_stats( delete $flags->{stats} ); for my $flag ( sort keys %{$flags} ) { @@ -884,7 +1003,9 @@ EOF } # Add our self to components, since we are also a component - $class->components->{$class} = $class; + if( $class->isa('Catalyst::Controller') ){ + $class->components->{$class} = $class; + } $class->setup_actions; @@ -897,20 +1018,30 @@ EOF $class->setup_finished(1); } +=head2 $c->uri_for( $action, \@captures?, @args?, \%query_values? ) + =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. +=over + +=item $action -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. +A Catalyst::Action object representing the Catalyst action you want to +create a URI for. To get one for an action in the current controller, +use C<< $c->action('someactionname') >>. To get one from different +controller, fetch the controller using C<< $c->controller() >>, then +call C on it. + +You can maintain the arguments captured by an action (e.g.: Regex, Chained) +using C<< $c->req->captures >>. + + # For the current action + $c->uri_for($c->action, $c->req->captures); + + # For the Foo action in the Bar controller + $c->uri_for($c->controller->('Bar')->action_for('Foo'), $c->req->captures); + +=back =cut @@ -947,7 +1078,7 @@ sub uri_for { # join args with '/', or a blank string my $args = join('/', grep { defined($_) } @args); $args =~ s/\?/%3F/g; # STUPID STUPID SPECIAL CASE - $args =~ s!^/!!; + $args =~ s!^/+!!; my $base = $c->req->base; my $class = ref($base); $base =~ s{(?{$_}; s/([;\/?:@&=+,\$\[\]%])/$URI::Escape::escapes{$1}/go; s/ /+/g; my $key = $_; - my $val = $params->{$_}; $val = '' unless defined $val; (map { $_ = "$_"; - utf8::encode( $_ ); + utf8::encode( $_ ) if utf8::is_utf8($_); # using the URI::Escape pattern here so utf8 chars survive s/([^A-Za-z0-9\-_.!~*'() ])/$URI::Escape::escapes{$1}/go; s/ /+/g; @@ -1079,7 +1210,7 @@ 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 want to start with a tutorial.

+ you might 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.

@@ -1107,7 +1238,7 @@ sub welcome_message { Wiki
  • - Mailing-List + Mailing-List
  • IRC channel #catalyst on irc.perl.org @@ -1189,27 +1320,32 @@ sub execute { $c->state(0); if ( $c->depth >= $RECURSION ) { - my $action = "$code"; + my $action = $code->reverse(); $action = "/$action" unless $action =~ /->/; - my $error = qq/Deep recursion detected calling "$action"/; + my $error = qq/Deep recursion detected calling "${action}"/; $c->log->error($error); $c->error($error); $c->state(0); return $c->state; } - my $stats_info = $c->_stats_start_execute( $code ) if $c->debug; + my $stats_info = $c->_stats_start_execute( $code ) if $c->use_stats; push( @{ $c->stack }, $code ); - eval { $c->state( &$code( $class, $c, @{ $c->req->args } ) || 0 ) }; + eval { $c->state( $code->execute( $class, $c, @{ $c->req->args } ) || 0 ) }; - $c->_stats_finish_execute( $stats_info ) if $c->debug and $stats_info; + $c->_stats_finish_execute( $stats_info ) if $c->use_stats and $stats_info; my $last = pop( @{ $c->stack } ); if ( my $error = $@ ) { - if ( !ref($error) and $error eq $DETACH ) { die $DETACH if $c->depth > 1 } + if ( !ref($error) and $error eq $DETACH ) { + die $DETACH if($c->depth > 1); + } + elsif ( !ref($error) and $error eq $GO ) { + die $GO if($c->depth > 0); + } else { unless ( ref $error ) { no warnings 'uninitialized'; @@ -1231,9 +1367,10 @@ sub _stats_start_execute { return if ( ( $code->name =~ /^_.*/ ) && ( !$c->config->{show_internal_actions} ) ); - $c->counter->{"$code"}++; + my $action_name = $code->reverse(); + $c->counter->{$action_name}++; - my $action = "$code"; + my $action = $action_name; $action = "/$action" unless $action =~ /->/; # determine if the call was the result of a forward @@ -1252,57 +1389,51 @@ sub _stats_start_execute { } } - my $node = Tree::Simple->new( - { - action => $action, - elapsed => undef, # to be filled in later - comment => "", - } - ); - $node->setUID( "$code" . $c->counter->{"$code"} ); + my $uid = $action_name . $c->counter->{$action_name}; # 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); - } + $c->stats->profile( + begin => $action, + parent => "$parent" . $c->counter->{"$parent"}, + uid => $uid, + ); } else { # forward with no caller may come from a plugin - $c->stats->addChild($node); + $c->stats->profile( + begin => $action, + uid => $uid, + ); } } else { - + # root-level call - $c->stats->addChild($node); + $c->stats->profile( + begin => $action, + uid => $uid, + ); } + return $action; - 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 ); + $c->stats->profile( end => $info ); } =head2 $c->_localize_fields( sub { }, \%keys ); =cut +#Why does this exist? This is no longer safe and WILL NOT WORK. +# it doesnt seem to be used anywhere. can we remove it? sub _localize_fields { my ( $c, $localized, $code ) = ( @_ ); @@ -1330,8 +1461,9 @@ sub finalize { } # Allow engine to handle finalize flow (for POE) - if ( $c->engine->can('finalize') ) { - $c->engine->finalize($c); + my $engine = $c->engine; + if ( my $code = $engine->can('finalize') ) { + $engine->$code($c); } else { @@ -1352,22 +1484,11 @@ sub finalize { $c->finalize_body; } - if ($c->debug) { - my $elapsed = tv_interval($c->stats->getNodeValue); + if ($c->use_stats) { + my $elapsed = sprintf '%f', $c->stats->elapsed; my $av = $elapsed == 0 ? '??' : sprintf '%.3f', 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" ); + "Request took ${elapsed}s ($av/s)\n" . $c->stats->report . "\n" ); } return $c->response->status; @@ -1406,45 +1527,48 @@ Finalizes headers. sub finalize_headers { my $c = shift; + my $response = $c->response; #accessor calls can add up? + # Check if we already finalized headers - return if $c->response->{_finalized_headers}; + return if $response->finalized_headers; # Handle redirects - if ( my $location = $c->response->redirect ) { + if ( my $location = $response->redirect ) { $c->log->debug(qq/Redirecting to "$location"/) if $c->debug; - $c->response->header( Location => $location ); - - if ( !$c->response->body ) { + $response->header( Location => $location ); + + if ( !$response->has_body ) { # Add a default body if none is already present - $c->response->body( + $response->body( qq{

    This item has moved here.

    } ); } } # Content-Length - if ( $c->response->body && !$c->response->content_length ) { + if ( $response->body && !$response->content_length ) { # get the length from a filehandle - if ( blessed( $c->response->body ) && $c->response->body->can('read') ) + if ( blessed( $response->body ) && $response->body->can('read') ) { - my $stat = stat $c->response->body; + my $stat = stat $response->body; if ( $stat && $stat->size > 0 ) { - $c->response->content_length( $stat->size ); + $response->content_length( $stat->size ); } else { $c->log->warn('Serving filehandle without a content-length'); } } else { - $c->response->content_length( bytes::length( $c->response->body ) ); + # everything should be bytes at this point, but just in case + $response->content_length( bytes::length( $response->body ) ); } } # Errors - if ( $c->response->status =~ /^(1\d\d|[23]04)$/ ) { - $c->response->headers->remove_header("Content-Length"); - $c->response->body(''); + if ( $response->status =~ /^(1\d\d|[23]04)$/ ) { + $response->headers->remove_header("Content-Length"); + $response->body(''); } $c->finalize_cookies; @@ -1452,7 +1576,7 @@ sub finalize_headers { $c->engine->finalize_headers( $c, @_ ); # Done - $c->response->{_finalized_headers} = 1; + $response->finalized_headers(1); } =head2 $c->finalize_output @@ -1522,7 +1646,10 @@ sub handle_request { } $COUNT++; - $class->log->_flush() if $class->log->can('_flush'); + + if(my $coderef = $class->log->can('_flush')){ + $class->log->$coderef(); + } return $status; } @@ -1536,48 +1663,24 @@ etc.). sub prepare { my ( $class, @arguments ) = @_; + # XXX + # After the app/ctxt split, this should become an attribute based on something passed + # into the application. $class->context_class( ref $class || $class ) unless $class->context_class; - my $c = $class->context_class->new( - { - counter => {}, - stack => [], - request => $class->request_class->new( - { - arguments => [], - body_parameters => {}, - cookies => {}, - headers => HTTP::Headers->new, - parameters => {}, - query_parameters => {}, - secure => 0, - captures => [], - uploads => {} - } - ), - response => $class->response_class->new( - { - body => '', - cookies => {}, - headers => HTTP::Headers->new(), - status => 200 - } - ), - stash => {}, - state => 0 - } - ); + + my $c = $class->context_class->new({}); + + # For on-demand data + $c->request->_context($c); + $c->response->_context($c); + #surely this is not the most efficient way to do things... + $c->stats($class->stats_class->new)->enable($c->use_stats); 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} ); - + #XXX reuse coderef from can # Allow engine to direct the prepare flow (for POE) if ( $c->engine->can('prepare') ) { $c->engine->prepare( $c, @arguments ); @@ -1601,7 +1704,8 @@ sub prepare { } my $method = $c->req->method || ''; - my $path = $c->req->path || '/'; + my $path = $c->req->path; + $path = '/' unless length $path; my $address = $c->req->address || ''; $c->log->debug(qq/"$method" request for "$path" from "$address"/) @@ -1629,6 +1733,7 @@ Prepares message body. sub prepare_body { my $c = shift; + #Moose TODO: what is _body ?? # Do we run for the first time? return if defined $c->request->{_body}; @@ -1852,6 +1957,11 @@ 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. +All components found will also have any +L loaded and set up as components. +Note, that modules which are B an I of the main +file namespace loaded will not be instantiated as components. + =cut sub setup_components { @@ -1878,6 +1988,7 @@ sub setup_components { # we know M::P::O found a file on disk so this is safe Catalyst::Utils::ensure_class_loaded( $component, { ignore_loaded => 1 } ); + #Class::MOP::load_class($component); my $module = $class->setup_component( $component ); my %modules = ( @@ -1921,7 +2032,7 @@ sub setup_component { Catalyst::Exception->throw( message => qq/Couldn't instantiate component "$component", "COMPONENT() didn't return an object-like value"/ - ) unless eval { $instance->can( 'can' ) }; + ) unless blessed($instance); return $instance; } @@ -1947,9 +2058,7 @@ sub setup_dispatcher { $dispatcher = $class->dispatcher_class; } - unless (Class::Inspector->loaded($dispatcher)) { - require Class::Inspector->filename($dispatcher); - } + Class::MOP::load_class($dispatcher); # dispatcher instance $class->dispatcher( $dispatcher->new ); @@ -1973,12 +2082,10 @@ sub setup_engine { } if ( $ENV{MOD_PERL} ) { - + my $meta = $class->Class::MOP::Object::meta(); + # create the apache method - { - no strict 'refs'; - *{"$class\::apache"} = sub { shift->engine->apache }; - } + $meta->add_method('apache' => sub { shift->engine->apache }); my ( $software, $version ) = $ENV{MOD_PERL} =~ /^(\S+)\/(\d+(?:[\.\_]\d+)+)/; @@ -2035,9 +2142,7 @@ sub setup_engine { $engine = $class->engine_class; } - unless (Class::Inspector->loaded($engine)) { - require Class::Inspector->filename($engine); - } + Class::MOP::load_class($engine); # check for old engines that are no longer compatible my $old_engine; @@ -2088,11 +2193,10 @@ sub setup_home { $home = $env; } - unless ($home) { - $home = Catalyst::Utils::home($class); - } + $home ||= Catalyst::Utils::home($class); if ($home) { + #I remember recently being scolded for assigning config values like this $class->config->{home} ||= $home; $class->config->{root} ||= Path::Class::Dir->new($home)->subdir('root'); } @@ -2113,8 +2217,7 @@ sub setup_log { my $env_debug = Catalyst::Utils::env_value( $class, 'DEBUG' ); if ( defined($env_debug) ? $env_debug : $debug ) { - no strict 'refs'; - *{"$class\::debug"} = sub { 1 }; + $class->Class::MOP::Object::meta()->add_method('debug' => sub { 1 }); $class->log->debug('Debug messages enabled'); } } @@ -2125,6 +2228,25 @@ Sets up plugins. =cut +=head2 $c->setup_stats + +Sets up timing statistics class. + +=cut + +sub setup_stats { + my ( $class, $stats ) = @_; + + Catalyst::Utils::ensure_class_loaded($class->stats_class); + + my $env = Catalyst::Utils::env_value( $class, 'STATS' ); + if ( defined($env) ? $env : ($stats || $class->debug ) ) { + $class->Class::MOP::Object::meta()->add_method('use_stats' => sub { 1 }); + $class->log->debug('Statistics enabled'); + } +} + + =head2 $c->registered_plugins Returns a sorted list of the plugins which have either been stated in the @@ -2157,12 +2279,17 @@ the plugin name does not begin with C. # no ignore_loaded here, the plugin may already have been # defined in memory and we don't want to error on "no file" if so - Catalyst::Utils::ensure_class_loaded( $plugin ); + Class::MOP::load_class( $plugin ); $proto->_plugins->{$plugin} = 1; unless ($instant) { no strict 'refs'; - unshift @{"$class\::ISA"}, $plugin; + if ( my $meta = $class->Class::MOP::Object::meta() ) { + my @superclasses = ($plugin, $meta->superclasses ); + $meta->superclasses(@superclasses); + } else { + unshift @{"$class\::ISA"}, $plugin; + } } return $class; } @@ -2188,6 +2315,24 @@ the plugin name does not begin with C. Returns an arrayref of the internal execution stack (actions that are currently executing). +=head2 $c->stats_class + +Returns or sets the stats (timing statistics) class. + +=head2 $c->use_stats + +Returns 1 when stats collection is enabled. Stats collection is enabled +when the -Stats options is set, debug is on or when the _STATS +environment variable is set. + +Note that this is a static method, not an accessor and should be overloaded +by declaring "sub use_stats { 1 }" in your MyApp.pm, not by calling $c->use_stats(1). + +=cut + +sub use_stats { 0 } + + =head2 $c->write( $data ) Writes $data to the output stream. When using this method directly, you @@ -2290,8 +2435,8 @@ IRC: Mailing Lists: - http://lists.rawmode.org/mailman/listinfo/catalyst - http://lists.rawmode.org/mailman/listinfo/catalyst-dev + http://lists.scsys.co.uk/cgi-bin/mailman/listinfo/catalyst + http://lists.scsys.co.uk/cgi-bin/mailman/listinfo/catalyst-dev Web: @@ -2319,13 +2464,15 @@ Wiki: =head2 L - The test suite. -=head1 CREDITS +=head1 PROJECT FOUNDER -Andy Grundman +sri: Sebastian Riedel -Andy Wardley +=head1 CONTRIBUTORS -Andreas Marienborg +abw: Andy Wardley + +acme: Leon Brocard Andrew Bramble @@ -2333,65 +2480,73 @@ Andrew Ford Andrew Ruthven -Arthur Bergman +andyg: Andy Grundman -Autrijus Tang +audreyt: Audrey Tang -Brian Cassidy +bricas: Brian Cassidy -Carl Franks +Caelum: Rafael Kitover -Christian Hansen +chansen: Christian Hansen -Christopher Hicks +chicks: Christopher Hicks -Dan Sully +dkubb: Dan Kubb -Danijel Milicevic +Drew Taylor -David Kamholz +esskar: Sascha Kiefer -David Naughton +fireartist: Carl Franks -Drew Taylor +gabb: Danijel Milicevic Gary Ashton Jones Geoff Richards -Jesse Sheidlower +ilmari: Dagfinn Ilmari Mannsåker -Jesse Vincent +jcamacho: Juan Camacho Jody Belka Johan Lindstrom -Juan Camacho +jon: Jon Schutz -Leon Brocard +marcus: Marcus Ramberg -Marcus Ramberg +miyagawa: Tatsuhiko Miyagawa -Matt S Trout +mst: Matt S. Trout -Robert Sedlacek +mugwump: Sam Vilain -Sam Vilain +naughton: David Naughton -Sascha Kiefer +ningu: David Kamholz -Sebastian Willert +nothingmuch: Yuval Kogman -Tatsuhiko Miyagawa +numa: Dan Sully -Ulf Edvinsson +obra: Jesse Vincent + +omega: Andreas Marienborg + +phaylon: Robert Sedlacek -Yuval Kogman +rafl: Florian Ragwitz -=head1 AUTHOR +sky: Arthur Bergman -Sebastian Riedel, C +the_jester: Jesse Sheidlower + +Ulf Edvinsson + +willert: Sebastian Willert =head1 LICENSE @@ -2400,4 +2555,8 @@ the same terms as Perl itself. =cut +no Moose; + +__PACKAGE__->meta->make_immutable; + 1;