X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Runtime.git;a=blobdiff_plain;f=lib%2FCatalyst.pm;h=e22ff8167d90d72308021c7f0e2f2e21e77d93e3;hp=66021fa57a8567a3dd53dc1a1ab4a730cce4aefa;hb=f3414019f472b55682ef3af53f761b6db7955887;hpb=b01f0c69c9821968a297b36d64eed9193287455a diff --git a/lib/Catalyst.pm b/lib/Catalyst.pm index 66021fa..e22ff81 100644 --- a/lib/Catalyst.pm +++ b/lib/Catalyst.pm @@ -1,7 +1,8 @@ package Catalyst; -use strict; -use base 'Catalyst::Component'; +use Class::C3; +use Moose; +extends 'Catalyst::Component'; use bytes; use Catalyst::Exception; use Catalyst::Log; @@ -13,7 +14,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,21 +30,34 @@ use Carp qw/croak carp/; BEGIN { require 5.008001; } -__PACKAGE__->mk_accessors( - qw/counter request response state action stack namespace stats/ -); +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'); + +no Moose; attributes->import( __PACKAGE__, \&namespace, 'lvalue' ); 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; @@ -52,6 +65,8 @@ our $START = time; our $RECURSION = 1000; our $DETACH = "catalyst_detach\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 stats_class @@ -65,7 +80,7 @@ __PACKAGE__->stats_class('Catalyst::Stats'); # Remember to update this in Catalyst::Runtime as well! -our $VERSION = '5.7011'; +our $VERSION = '5.7013'; sub import { my ( $class, @arguments ) = @_; @@ -76,9 +91,19 @@ sub import { my $caller = caller(0); + #why does called have to ISA Catalyst and ISA Controller ? + #Convert test suite to not use the behavior where Myapp ISA Controller + # after that is done we can eliminate that little mess. unless ( $caller->isa('Catalyst') ) { no strict 'refs'; - push @{"$caller\::ISA"}, $class, 'Catalyst::Controller'; + if( $caller->can('meta') ){ + my @superclasses = ($caller->meta->superclasses, $class, 'Catalyst::Controller'); + #my @superclasses = ($caller->meta->superclasses, $class); + $caller->meta->superclasses(@superclasses); + } else { + push @{"$caller\::ISA"}, $class, 'Catalyst::Controller'; + #push @{"$caller\::ISA"}, $class; + } } $caller->arguments( [@arguments] ); @@ -312,7 +337,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 ] ) @@ -360,12 +385,15 @@ sub stash { my $stash = @_ > 1 ? {@_} : $_[0]; croak('stash takes a hash or hashref') unless ref $stash; foreach my $key ( keys %$stash ) { - $c->{stash}->{$key} = $stash->{$key}; + #shouldn't we hold this in a var and save ourselves the subcall? + $c->next::method->{$key} = $stash->{$key}; } } - return $c->{stash}; + + return $c->next::method; } + =head2 $c->error =head2 $c->error($error, ...) @@ -682,7 +710,7 @@ sub config { $c->log->warn("Setting config after setup has been run is not a good idea.") if ( @_ and $c->setup_finished ); - $c->NEXT::config(@_); + $c->next::method(@_); } =head2 $c->log @@ -920,6 +948,9 @@ 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. +Note that uri_for is destructive to the passed hashref. Subsequent calls +with the same hashref may have unintended results. + 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 @@ -978,7 +1009,7 @@ sub uri_for { $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; @@ -1203,9 +1234,9 @@ 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); @@ -1216,7 +1247,7 @@ sub execute { 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->use_stats and $stats_info; @@ -1245,9 +1276,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 @@ -1266,24 +1298,35 @@ sub _stats_start_execute { } } + 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] ) { - $c->stats->profile(begin => $action, - parent => "$parent" . $c->counter->{"$parent"}); + $c->stats->profile( + begin => $action, + parent => "$parent" . $c->counter->{"$parent"}, + uid => $uid, + ); } else { # forward with no caller may come from a plugin - $c->stats->profile(begin => $action); + $c->stats->profile( + begin => $action, + uid => $uid, + ); } } else { # root-level call - $c->stats->profile(begin => $action); + $c->stats->profile( + begin => $action, + uid => $uid, + ); } return $action; @@ -1291,13 +1334,15 @@ sub _stats_start_execute { sub _stats_finish_execute { my ( $c, $info ) = @_; - $c->stats->profile(end => $info); + $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 ) = ( @_ ); @@ -1325,8 +1370,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 { @@ -1390,45 +1436,49 @@ 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 ); + + #Moose TODO: we should probably be using a predicate method here ? + if ( !$response->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; @@ -1436,7 +1486,7 @@ sub finalize_headers { $c->engine->finalize_headers( $c, @_ ); # Done - $c->response->{_finalized_headers} = 1; + $response->finalized_headers(1); } =head2 $c->finalize_output @@ -1506,7 +1556,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; } @@ -1520,48 +1573,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->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 ); @@ -1613,6 +1642,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}; @@ -1862,6 +1892,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 = ( @@ -1931,9 +1962,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 ); @@ -2019,9 +2048,10 @@ sub setup_engine { $engine = $class->engine_class; } - unless (Class::Inspector->loaded($engine)) { - require Class::Inspector->filename($engine); - } + Class::MOP::load_class($engine); + #unless (Class::Inspector->loaded($engine)) { + # require Class::Inspector->filename($engine); + #} # check for old engines that are no longer compatible my $old_engine; @@ -2072,11 +2102,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'); } @@ -2098,6 +2127,7 @@ sub setup_log { my $env_debug = Catalyst::Utils::env_value( $class, 'DEBUG' ); if ( defined($env_debug) ? $env_debug : $debug ) { no strict 'refs'; + #Moose todo: dying to be made a bool attribute *{"$class\::debug"} = sub { 1 }; $class->log->debug('Debug messages enabled'); } @@ -2123,6 +2153,7 @@ sub setup_stats { my $env = Catalyst::Utils::env_value( $class, 'STATS' ); if ( defined($env) ? $env : ($stats || $class->debug ) ) { no strict 'refs'; + #Moose todo: dying to be made a bool attribute *{"$class\::use_stats"} = sub { 1 }; $class->log->debug('Statistics enabled'); } @@ -2161,12 +2192,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( $class->can('meta') ){ + my @superclasses = ($plugin, $class->meta->superclasses ); + $class->meta->superclasses(@superclasses); + } else { + unshift @{"$class\::ISA"}, $plugin; + } } return $class; }