Remove use of overload
[catagits/Catalyst-Runtime.git] / lib / Catalyst.pm
index 4cf485d..e22ff81 100644 (file)
@@ -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,36 +14,50 @@ 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 Time::HiRes qw/gettimeofday tv_interval/;
 use URI ();
+use URI::http;
+use URI::https;
 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/;
+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;
@@ -50,18 +65,22 @@ 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 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.7006';
+our $VERSION = '5.7013';
 
 sub import {
     my ( $class, @arguments ) = @_;
@@ -72,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] );
@@ -87,6 +116,9 @@ Catalyst - The Elegant MVC Web Application Framework
 
 =head1 SYNOPSIS
 
+See the L<Catalyst::Manual> distribution for comprehensive
+documentation and tutorials.
+
     # Install Catalyst::Devel for helpers and other development tools
     # use the helper to create a new application
     catalyst.pl MyApp
@@ -106,7 +138,7 @@ Catalyst - The Elegant MVC Web Application Framework
     ### in lib/MyApp.pm
     use Catalyst qw/-Debug/; # include plugins here as well
     
-       ### In lib/MyApp/Controller/Root.pm (autocreated)
+    ### 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
@@ -235,6 +267,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 <MYAPP>_STATS. The
+environment settings override the application, with <MYAPP>_STATS having the
+highest priority.
+
+e.g. 
+
+   use Catalyst qw/-Stats=1/
+
 =head1 METHODS
 
 =head2 INFORMATION ABOUT THE CURRENT REQUEST
@@ -294,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 ] )
 
@@ -340,14 +383,17 @@ sub stash {
     my $c = shift;
     if (@_) {
         my $stash = @_ > 1 ? {@_} : $_[0];
-       croak('stash takes a hash or hashref') unless ref $stash;
+        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, ...)
@@ -356,7 +402,7 @@ sub stash {
 
 Returns an arrayref containing error messages.  If Catalyst encounters an
 error while processing a request, it stores the error in $c->error.  This
-method should not be used to store non-fatal error messages.
+method should only be used to store fatal error messages.
 
     my @error = @{ $c->error };
 
@@ -508,6 +554,8 @@ Gets a L<Catalyst::Model> instance by name.
 
     $c->model('Foo')->do_stuff;
 
+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 name in $c->stash->{current_model}, then
@@ -526,10 +574,10 @@ sub model {
           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 );
+    return $c->model( $c->config->{default_model} )
+      if $c->config->{default_model};
+    return $c->_filter_component( $c->_comp_singular(qw/Model M/) );
 
 }
 
@@ -551,6 +599,8 @@ Gets a L<Catalyst::View> instance by name.
 
     $c->view('Foo')->do_stuff;
 
+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 name in $c->stash->{current_view}, then
@@ -569,9 +619,9 @@ sub view {
           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->view( $c->config->{default_view} )
+      if $c->config->{default_view};
     return $c->_filter_component( $c->_comp_singular(qw/View V/) );
 }
 
@@ -660,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
@@ -804,6 +854,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} ) {
 
@@ -897,6 +948,9 @@ If the last argument to C<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.
 
+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
@@ -907,11 +961,6 @@ to C<uri_for_action>.
 
 sub uri_for {
     my ( $c, $path, @args ) = @_;
-    my $base     = $c->request->base->clone;
-    my $basepath = $base->path;
-    $basepath =~ s/\/$//;
-    $basepath .= '/';
-    my $namespace = $c->namespace || '';
 
     if ( Scalar::Util::blessed($path) ) { # action object
         my $captures = ( scalar @args && ref $args[0] eq 'ARRAY'
@@ -922,32 +971,53 @@ sub uri_for {
         $path = '/' if $path eq '';
     }
 
-    # massage namespace, empty if absolute path
-    $namespace =~ s/^\/// if $namespace;
-    $namespace .= '/' if $namespace;
-    $path ||= '';
-    $namespace = '' if $path =~ /^\//;
-    $path =~ s/^\///;
-    $path =~ s/\?/%3F/g;
+    undef($path) if (defined $path && $path eq '');
 
     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( $_ );
+    carp "uri_for called with undef argument" if grep { ! defined $_ } @args;
+    s/([^$URI::uric])/$URI::Escape::escapes{$1}/go for @args;
+
+    unshift(@args, $path);
+
+    unless (defined $path && $path =~ s!^/!!) { # in-place strip
+        my $namespace = $c->namespace;
+        if (defined $path) { # cheesy hack to handle path '../foo'
+           $namespace =~ s{(?:^|/)[^/]+$}{} while $args[0] =~ s{^\.\./}{};
         }
-    };
+        unshift(@args, $namespace || '');
+    }
     
     # join args with '/', or a blank string
-    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 )
-      ->canonical;
-    $res->query_form(%$params);
+    my $args = join('/', grep { defined($_) } @args);
+    $args =~ s/\?/%3F/g; # STUPID STUPID SPECIAL CASE
+    $args =~ s!^/!!;
+    my $base = $c->req->base;
+    my $class = ref($base);
+    $base =~ s{(?<!/)$}{/};
+
+    my $query = '';
+
+    if (my @keys = keys %$params) {
+      # somewhat lifted from URI::_query's query_form
+      $query = '?'.join('&', map {
+          s/([;\/?:@&=+,\$\[\]%])/$URI::Escape::escapes{$1}/go;
+          s/ /+/g;
+          my $key = $_;
+          my $val = $params->{$_};
+          $val = '' unless defined $val;
+          (map {
+              $_ = "$_";
+              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;
+              "${key}=$_"; } ( ref $val eq 'ARRAY' ? @$val : $val ));
+      } @keys);
+    }
+
+    my $res = bless(\"${base}${args}${query}", $class);
     $res;
 }
 
@@ -968,8 +1038,8 @@ sub welcome_message {
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
     <head>
-       <meta http-equiv="Content-Language" content="en" />
-       <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
         <title>$name on Catalyst $VERSION</title>
         <style type="text/css">
             body {
@@ -1164,22 +1234,22 @@ 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 } );
 
@@ -1206,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
@@ -1227,57 +1298,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 ) = ( @_ );
 
@@ -1305,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 {
 
@@ -1327,22 +1393,11 @@ 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} || '??' );
-            }
-        );
-
+    if ($c->use_stats) {        
+        my $elapsed = sprintf '%f', $c->stats->elapsed;
+        my $av = $elapsed == 0 ? '??' : sprintf '%.3f', 1 / $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;
@@ -1381,37 +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 );
+        $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
+            $response->body(
+                qq{<html><body><p>This item has moved <a href="$location">here</a>.</p></body></html>}
+            );
+        }
     }
 
     # 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') )
         {
-            if ( my $stat = stat $c->response->body ) {
-                $c->response->content_length( $stat->size );
+            my $stat = stat $response->body;
+            if ( $stat && $stat->size > 0 ) {
+                $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;
@@ -1419,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
@@ -1489,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;
 }
 
@@ -1503,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->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 );
@@ -1557,8 +1603,14 @@ sub prepare {
         $c->prepare_cookies;
         $c->prepare_path;
 
-        # On-demand parsing
-        $c->prepare_body unless $c->config->{parse_on_demand};
+        # Prepare the body for reading, either by prepare_body
+        # or the user, if they are using $c->read
+        $c->prepare_read;
+        
+        # Parse the body unless the user wants it on-demand
+        unless ( $c->config->{parse_on_demand} ) {
+            $c->prepare_body;
+        }
     }
 
     my $method  = $c->req->method  || '';
@@ -1590,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};
 
@@ -1773,6 +1826,10 @@ C<$maxlength> defaults to the size of the request if not specified.
 You have to set C<< MyApp->config->{parse_on_demand} >> to use this
 directly.
 
+Warning: If you use read(), Catalyst will not process the body,
+so you will not be able to access POST parameters or file uploads via
+$c->request.  You must handle all body parsing yourself.
+
 =cut
 
 sub read { my $c = shift; return $c->engine->read( $c, @_ ) }
@@ -1824,15 +1881,26 @@ sub setup_components {
         search_path => [ map { s/^(?=::)/$class/; $_; } @paths ],
         %$config
     );
+
+    my @comps = sort { length $a <=> length $b } $locator->plugins;
+    my %comps = map { $_ => 1 } @comps;
     
-    for my $component ( sort { length $a <=> length $b } $locator->plugins ) {
+    for my $component ( @comps ) {
+
+        # We pass ignore_loaded here so that overlay files for (e.g.)
+        # Model::DBI::Schema sub-classes are loaded - if it's in @comps
+        # 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 = (
             $component => $module,
             map {
                 $_ => $class->setup_component( $_ )
+            } grep { 
+              not exists $comps{$_}
             } Devel::InnerPackage::list_packages( $component )
         );
         
@@ -1886,22 +1954,15 @@ sub setup_dispatcher {
         $dispatcher = 'Catalyst::Dispatcher::' . $dispatcher;
     }
 
-    if ( $ENV{CATALYST_DISPATCHER} ) {
-        $dispatcher = 'Catalyst::Dispatcher::' . $ENV{CATALYST_DISPATCHER};
-    }
-
-    if ( $ENV{ uc($class) . '_DISPATCHER' } ) {
-        $dispatcher =
-          'Catalyst::Dispatcher::' . $ENV{ uc($class) . '_DISPATCHER' };
+    if ( my $env = Catalyst::Utils::env_value( $class, 'DISPATCHER' ) ) {
+        $dispatcher = 'Catalyst::Dispatcher::' . $env;
     }
 
     unless ($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 );
@@ -1920,12 +1981,8 @@ sub setup_engine {
         $engine = 'Catalyst::Engine::' . $engine;
     }
 
-    if ( $ENV{CATALYST_ENGINE} ) {
-        $engine = 'Catalyst::Engine::' . $ENV{CATALYST_ENGINE};
-    }
-
-    if ( $ENV{ uc($class) . '_ENGINE' } ) {
-        $engine = 'Catalyst::Engine::' . $ENV{ uc($class) . '_ENGINE' };
+    if ( my $env = Catalyst::Utils::env_value( $class, 'ENGINE' ) ) {
+        $engine = 'Catalyst::Engine::' . $env;
     }
 
     if ( $ENV{MOD_PERL} ) {
@@ -1991,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;
@@ -2040,20 +2098,14 @@ Sets up the home directory.
 sub setup_home {
     my ( $class, $home ) = @_;
 
-    if ( $ENV{CATALYST_HOME} ) {
-        $home = $ENV{CATALYST_HOME};
-    }
-
-    if ( $ENV{ uc($class) . '_HOME' } ) {
-        $class =~ s/::/_/g;
-        $home = $ENV{ uc($class) . '_HOME' };
+    if ( my $env = Catalyst::Utils::env_value( $class, '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');
     }
@@ -2072,15 +2124,10 @@ sub setup_log {
         $class->log( Catalyst::Log->new );
     }
 
-    my $app_flag = Catalyst::Utils::class2env($class) . '_DEBUG';
-
-    if (
-          ( defined( $ENV{CATALYST_DEBUG} ) || defined( $ENV{$app_flag} ) )
-        ? ( $ENV{CATALYST_DEBUG} || $ENV{$app_flag} )
-        : $debug
-      )
-    {
+    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');
     }
@@ -2092,6 +2139,27 @@ 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 ) ) {
+        no strict 'refs';
+        #Moose todo: dying to be made a bool attribute
+        *{"$class\::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
@@ -2121,14 +2189,20 @@ the plugin name does not begin with C<Catalyst::Plugin::>.
         my ( $proto, $plugin, $instant ) = @_;
         my $class = ref $proto || $proto;
 
-        unless (Class::Inspector->loaded($plugin)) {
-            require Class::Inspector->filename($plugin);
-        }
+        # 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
+
+        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;
     }
@@ -2154,6 +2228,24 @@ the plugin name does not begin with C<Catalyst::Plugin::>.
 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 <MYAPP>_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
@@ -2201,8 +2293,8 @@ This causes C<MyApp::C::Foo::Bar> to map to C</Foo/Bar>.
 =head1 ON-DEMAND PARSER
 
 The request body is usually parsed at the beginning of a request,
-but if you want to handle input yourself or speed things up a bit,
-you can enable on-demand parsing with a config parameter.
+but if you want to handle input yourself, you can enable on-demand
+parsing with a config parameter.
 
     MyApp->config->{parse_on_demand} = 1;
     
@@ -2347,6 +2439,8 @@ Sam Vilain
 
 Sascha Kiefer
 
+Sebastian Willert
+
 Tatsuhiko Miyagawa
 
 Ulf Edvinsson