X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FCatalyst.pm;h=3e298ec311eb853976b08e875f5bb2eb45bfaec4;hb=47990d3f8d6391073e165a529cd48a209fa34ec3;hp=eae0325e0e3c99f8ceee3a8ff865d14c895f0962;hpb=6df30f7ecebd48d912277888bbe79ef599b3cb99;p=catagits%2FCatalyst-Runtime.git
diff --git a/lib/Catalyst.pm b/lib/Catalyst.pm
index eae0325..3e298ec 100644
--- a/lib/Catalyst.pm
+++ b/lib/Catalyst.pm
@@ -4,7 +4,6 @@ use Moose;
use Moose::Meta::Class ();
extends 'Catalyst::Component';
use Moose::Util qw/find_meta/;
-use bytes;
use B::Hooks::EndOfScope ();
use Catalyst::Exception;
use Catalyst::Exception::Detach;
@@ -15,6 +14,7 @@ use Catalyst::Request::Upload;
use Catalyst::Response;
use Catalyst::Utils;
use Catalyst::Controller;
+use Data::OptList;
use Devel::InnerPackage ();
use File::stat;
use Module::Pluggable::Object ();
@@ -27,11 +27,12 @@ use URI::https;
use Tree::Simple qw/use_weak_refs/;
use Tree::Simple::Visitor::FindByUID;
use Class::C3::Adopt::NEXT;
+use List::MoreUtils qw/uniq/;
use attributes;
use utf8;
use Carp qw/croak carp shortmess/;
-BEGIN { require 5.008001; }
+BEGIN { require 5.008004; }
has stack => (is => 'ro', default => sub { [] });
has stash => (is => 'rw', default => sub { {} });
@@ -78,14 +79,7 @@ __PACKAGE__->stats_class('Catalyst::Stats');
# Remember to update this in Catalyst::Runtime as well!
-our $VERSION = '5.80007';
-
-{
- my $dev_version = $VERSION =~ /_\d{2}$/;
- *_IS_DEVELOPMENT_VERSION = sub () { $dev_version };
-}
-
-$VERSION = eval $VERSION;
+our $VERSION = '5.80029';
sub import {
my ( $class, @arguments ) = @_;
@@ -97,11 +91,6 @@ sub import {
my $caller = caller();
return if $caller eq 'main';
- # Kill Adopt::NEXT warnings if we're a non-RC version
- unless (_IS_DEVELOPMENT_VERSION()) {
- Class::C3::Adopt::NEXT->unimport(qr/^Catalyst::/);
- }
-
my $meta = Moose::Meta::Class->initialize($caller);
unless ( $caller->isa('Catalyst') ) {
my @superclasses = ($meta->superclasses, $class, 'Catalyst::Controller');
@@ -111,7 +100,12 @@ sub import {
$meta->superclasses(grep { $_ ne 'Moose::Object' } $meta->superclasses);
unless( $meta->has_method('meta') ){
- $meta->add_method(meta => sub { Moose::Meta::Class->initialize("${caller}") } );
+ if ($Moose::VERSION >= 1.15) {
+ $meta->_add_meta_method('meta');
+ }
+ else {
+ $meta->add_method(meta => sub { Moose::Meta::Class->initialize("${caller}") } );
+ }
}
$caller->arguments( [@arguments] );
@@ -254,6 +248,9 @@ environment with CATALYST_DEBUG or _DEBUG. The environment
settings override the application, with _DEBUG having the highest
priority.
+This sets the log level to 'debug' and enables full debug output on the
+error screen. If you only want the latter, see L<< $c->debug >>.
+
=head2 -Engine
Forces Catalyst to use a specific engine. Omit the
@@ -273,6 +270,14 @@ 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.
+If none of these are set, Catalyst will attempt to automatically detect the
+home directory. If you are working in a development envirnoment, Catalyst
+will try and find the directory containing either Makefile.PL, Build.PL or
+dist.ini. If the application has been installed into the system (i.e.
+you have done C), then Catalyst will use the path to your
+application module, without the .pm extension (ie, /foo/MyApp if your
+application was installed at /foo/MyApp.pm)
+
=head2 -Log
use Catalyst '-Log=warn,fatal,error';
@@ -281,14 +286,15 @@ Specifies a comma-delimited list of log levels.
=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.
+Enables statistics collection and reporting.
+
+ use Catalyst qw/-Stats=1/;
-e.g.
+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.
- use Catalyst qw/-Stats=1/
+Stats are also enabled if L<< debugging |/"-Debug" >> is enabled.
=head1 METHODS
@@ -332,21 +338,38 @@ call to forward.
my $foodata = $c->forward('/foo');
$c->forward('index');
- $c->forward(qw/MyApp::Model::DBIC::Foo do_stuff/);
- $c->forward('MyApp::View::TT');
+ $c->forward(qw/Model::DBIC::Foo do_stuff/);
+ $c->forward('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:
+Note that L<< forward|/"$c->forward( $action [, \@arguments ] )" >> implies
+an C<< eval { } >> around the call (actually
+L<< execute|/"$c->execute( $class, $coderef )" >> 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;
+ die join "\n", @{ $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;
+Another note is that C<< $c->forward >> always returns a scalar because it
+actually returns $c->state which operates in a scalar context.
+Thus, something like:
+
+ return @array;
+
+in an action that is forwarded to is going to return a scalar,
+i.e. how many items are in that array, which is probably not what you want.
+If you need to return an array then return a reference to it,
+or stash it like so:
+
+ $c->stash->{array} = \@array;
+
+and access it from the stash.
+
=cut
sub forward { my $c = shift; no warnings 'recursion'; $c->dispatcher->forward( $c, @_ ) }
@@ -357,8 +380,8 @@ sub forward { my $c = shift; no warnings 'recursion'; $c->dispatcher->forward( $
=head2 $c->detach()
-The same as C, but doesn't return to the previous action when
-processing is finished.
+The same as L<< forward|/"$c->forward( $action [, \@arguments ] )" >>, but
+doesn't return to the previous action when processing is finished.
When called with no arguments it escapes the processing chain entirely.
@@ -370,23 +393,27 @@ sub detach { my $c = shift; $c->dispatcher->detach( $c, @_ ) }
=head2 $c->visit( $class, $method, [, \@captures, \@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.
+Almost the same as L<< forward|/"$c->forward( $action [, \@arguments ] )" >>,
+but does a full dispatch, instead of just calling the new C<$action> /
+C<< $class->$method >>. This means that C, C and the method
+you go to are called, just like a new request.
In addition both C<< $c->action >> and C<< $c->namespace >> are localized.
-This means, for example, that $c->action methods such as C, C and
-C return information for the visited action when they are invoked
-within the visited action. This is different from the behavior of C
-which continues to use the $c->action object from the caller action even when
+This means, for example, that C<< $c->action >> methods such as
+L, L and
+L return information for the visited action
+when they are invoked within the visited action. This is different from the
+behavior of L<< forward|/"$c->forward( $action [, \@arguments ] )" >>, which
+continues to use the $c->action object from the caller action even when
invoked from the callee.
-C<$c-Estash> is kept unchanged.
+C<< $c->stash >> 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.
+In effect, L<< visit|/"$c->visit( $action [, \@captures, \@arguments ] )" >>
+allows you to "wrap" another action, just as it would have been called by
+dispatching from a URL, while the analogous
+L<< go|/"$c->go( $action [, \@captures, \@arguments ] )" >> allows you to
+transfer control to another action as if it had been reached directly from a URL.
=cut
@@ -396,12 +423,19 @@ sub visit { my $c = shift; $c->dispatcher->visit( $c, @_ ) }
=head2 $c->go( $class, $method, [, \@captures, \@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.
+The relationship between C and
+L<< visit|/"$c->visit( $action [, \@captures, \@arguments ] )" >> is the same as
+the relationship between
+L<< forward|/"$c->forward( $class, $method, [, \@arguments ] )" >> and
+L<< detach|/"$c->detach( $action [, \@arguments ] )" >>. Like C<< $c->visit >>,
+C<< $c->go >> will perform a full dispatch on the specified action or method,
+with localized C<< $c->action >> and C<< $c->namespace >>. Like C,
+C escapes the processing of the current request chain on completion, and
+does not return to its caller.
-C<$c-Estash> is kept unchanged.
+@arguments are arguments to the final destination of $action. @captures are
+arguments to the intermediate steps, if any, on the way to the final sub of
+$action.
=cut
@@ -481,6 +515,8 @@ sub error {
=head2 $c->state
Contains the return value of the last executed action.
+Note that << $c->state >> operates in a scalar context which means that all
+values it returns are scalar.
=head2 $c->clear_errors
@@ -525,6 +561,10 @@ sub _comp_names_search_prefixes {
# if we were given a regexp to search against, we're done.
return if ref $name;
+ # skip regexp fallback if configured
+ return
+ if $appclass->config->{disable_component_resolution_regex_fallback};
+
# regexp fallback
$query = qr/$name/i;
@result = grep { $eligible{ $_ } =~ m{$query} } keys %eligible;
@@ -542,7 +582,8 @@ sub _comp_names_search_prefixes {
(join '", "', @result) . "'. Relying on regexp fallback behavior for " .
"component resolution is unreliable and unsafe.";
my $short = $result[0];
- $short =~ s/.*?Model:://;
+ # remove the component namespace prefix
+ $short =~ s/.*?(Model|Controller|View):://;
my $shortmess = Carp::shortmess('');
if ($shortmess =~ m#Catalyst/Plugin#) {
$msg .= " You probably need to set '$short' instead of '${name}' in this " .
@@ -551,7 +592,7 @@ sub _comp_names_search_prefixes {
$msg .= " You probably need to set '$short' instead of '${name}' in this " .
"component's config";
} else {
- $msg .= " You probably meant \$c->${warn_for}('$short') instead of \$c->${warn_for}({'${name}'}), " .
+ $msg .= " You probably meant \$c->${warn_for}('$short') instead of \$c->${warn_for}('${name}'), " .
"but if you really wanted to search, pass in a regexp as the argument " .
"like so: \$c->${warn_for}(qr/${name}/)";
}
@@ -607,7 +648,13 @@ If you want to search for controllers, pass in a regexp as the argument.
sub controller {
my ( $c, $name, @args ) = @_;
+ my $appclass = ref($c) || $c;
if( $name ) {
+ unless ( ref($name) ) { # Direct component hash lookup to avoid costly regexps
+ my $comps = $c->components;
+ my $check = $appclass."::Controller::".$name;
+ return $c->_filter_component( $comps->{$check}, @args ) if exists $comps->{$check};
+ }
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 );
@@ -639,8 +686,13 @@ If you want to search for models, pass in a regexp as the argument.
sub model {
my ( $c, $name, @args ) = @_;
-
+ my $appclass = ref($c) || $c;
if( $name ) {
+ unless ( ref($name) ) { # Direct component hash lookup to avoid costly regexps
+ my $comps = $c->components;
+ my $check = $appclass."::Model::".$name;
+ return $c->_filter_component( $comps->{$check}, @args ) if exists $comps->{$check};
+ }
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 );
@@ -652,8 +704,8 @@ sub model {
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->model( $appclass->config->{default_model} )
+ if $appclass->config->{default_model};
my( $comp, $rest ) = $c->_comp_search_prefixes( undef, qw/Model M/);
@@ -693,7 +745,18 @@ If you want to search for views, pass in a regexp as the argument.
sub view {
my ( $c, $name, @args ) = @_;
+ my $appclass = ref($c) || $c;
if( $name ) {
+ unless ( ref($name) ) { # Direct component hash lookup to avoid costly regexps
+ my $comps = $c->components;
+ my $check = $appclass."::View::".$name;
+ if( exists $comps->{$check} ) {
+ return $c->_filter_component( $comps->{$check}, @args );
+ }
+ else {
+ $c->log->warn( "Attempted to use view '$check', but does not exist" );
+ }
+ }
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 );
@@ -705,8 +768,8 @@ sub view {
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( $appclass->config->{default_view} )
+ if $appclass->config->{default_view};
my( $comp, $rest ) = $c->_comp_search_prefixes( undef, qw/View V/);
@@ -767,6 +830,12 @@ should be used instead.
If C<$name> is a regexp, a list of components matched against the full
component name will be returned.
+If Catalyst can't find a component by name, it will fallback to regex
+matching by default. To disable this behaviour set
+disable_component_resolution_regex_fallback to a true value.
+
+ __PACKAGE__->config( disable_component_resolution_regex_fallback => 1 );
+
=cut
sub component {
@@ -818,8 +887,8 @@ Returns or takes a hashref containing the application's configuration.
__PACKAGE__->config( { db => 'dsn:SQLite:foo.db' } );
-You can also use a C, C or C config file
-like myapp.conf in your applications home directory. See
+You can also use a C, C or L config file
+like C in your applications home directory. See
L.
=head3 Cascading configuration
@@ -837,7 +906,7 @@ component is constructed.
For example:
MyApp->config({ 'Model::Foo' => { bar => 'baz', overrides => 'me' } });
- MyApp::Model::Foo->config({ quux => 'frob', 'overrides => 'this' });
+ MyApp::Model::Foo->config({ quux => 'frob', overrides => 'this' });
will mean that C receives the following data when
constructed:
@@ -848,6 +917,21 @@ constructed:
overrides => 'me',
});
+It's common practice to use a Moose attribute
+on the receiving component to access the config value.
+
+ package MyApp::Model::Foo;
+
+ use Moose;
+
+ # this attr will receive 'baz' at construction time
+ has 'bar' => (
+ is => 'rw',
+ isa => 'Str',
+ );
+
+You can then get the value 'baz' by calling $c->model('Foo')->bar
+
=cut
around config => sub {
@@ -896,6 +980,8 @@ You can enable debug mode in several ways:
=back
+The first three also set the log level to 'debug'.
+
Calling C<< $c->debug(1) >> has no effect.
=cut
@@ -918,7 +1004,7 @@ Returns the engine instance. See L.
Merges C<@path> with C<< $c->config->{home} >> and returns a
L object. Note you can usually use this object as
a filename, but sometimes you will have to explicitly stringify it
-yourself by calling the C<<->stringify>> method.
+yourself by calling the C<< ->stringify >> method.
For example:
@@ -1106,7 +1192,6 @@ EOF
my $name = $class->config->{name} || 'Application';
$class->log->info("$name powered by Catalyst $Catalyst::VERSION");
}
- $class->log->_flush() if $class->log->can('_flush');
# Make sure that the application class becomes immutable at this point,
B::Hooks::EndOfScope::on_scope_end {
@@ -1126,30 +1211,37 @@ EOF
. "Class::Accessor(::Fast)?\nPlease pass "
. "(replace_constructor => 1)\nwhen making your class immutable.\n";
}
- $meta->make_immutable(replace_constructor => 1)
- unless $meta->is_immutable;
+ $meta->make_immutable(
+ replace_constructor => 1,
+ ) unless $meta->is_immutable;
};
+ if ($class->config->{case_sensitive}) {
+ $class->log->warn($class . "->config->{case_sensitive} is set.");
+ $class->log->warn("This setting is deprecated and planned to be removed in Catalyst 5.81.");
+ }
+
$class->setup_finalize;
+ # Should be the last thing we do so that user things hooking
+ # setup_finalize can log..
+ $class->log->_flush() if $class->log->can('_flush');
+ return 1; # Explicit return true as people have __PACKAGE__->setup as the last thing in their class. HATE.
}
-
=head2 $app->setup_finalize
-A hook to attach modifiers to.
-Using C<< after setup => sub{}; >> doesn't work, because of quirky things done for plugin setup.
-Also better than C< setup_finished(); >, as that is a getter method.
+A hook to attach modifiers to. This method does not do anything except set the
+C accessor.
- sub setup_finalize {
+Applying method modifiers to the C method doesn't work, because of quirky thingsdone for plugin setup.
- my $app = shift;
-
- ## do stuff, i.e., determine a primary key column for sessions stored in a DB
-
- $app->next::method(@_);
+Example:
+ after setup_finalize => sub {
+ my $app = shift;
- }
+ ## do stuff here..
+ };
=cut
@@ -1158,40 +1250,83 @@ sub setup_finalize {
$class->setup_finished(1);
}
-=head2 $c->uri_for( $action, \@captures?, @args?, \%query_values? )
-
-=head2 $c->uri_for( $path, @args?, \%query_values? )
-
-=over
+=head2 $c->uri_for( $path?, @args?, \%query_values? )
-=item $action
-
-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 >>.
+=head2 $c->uri_for( $action, \@captures?, @args?, \%query_values? )
- # For the current action
- $c->uri_for($c->action, $c->req->captures);
+Constructs an absolute L object based on the application root, the
+provided path, and the additional arguments and query parameters provided.
+When used as a string, provides a textual URI. If you need more flexibility
+than this (i.e. the option to provide relative URIs etc.) see
+L.
+
+If no arguments are provided, the URI for the current action is returned.
+To return the current action and also provide @args, use
+C<< $c->uri_for( $c->action, @args ) >>.
+
+If the first argument is a string, it is taken as a public URI path relative
+to C<< $c->namespace >> (if it doesn't begin with a forward slash) or
+relative to the application root (if it does). It is then merged with
+C<< $c->request->base >>; any C<@args> are appended as additional path
+components; and any C<%query_values> are appended as C parameters.
+
+If the first argument is a L it represents an action which
+will have its path resolved using C<< $c->dispatcher->uri_for_action >>. The
+optional C<\@captures> argument (an arrayref) allows passing the captured
+variables that are needed to fill in the paths of Chained and Regex actions;
+once the path is resolved, C continues as though a path was
+provided, appending any arguments or parameters and creating an absolute
+URI.
+
+The captures for the current request can be found in
+C<< $c->request->captures >>, and actions can be resolved using
+C<< Catalyst::Controller->action_for($name) >>. If you have a private action
+path, use C<< $c->uri_for_action >> instead.
+
+ # Equivalent to $c->req->uri
+ $c->uri_for($c->action, $c->req->captures,
+ @{ $c->req->args }, $c->req->params);
# For the Foo action in the Bar controller
- $c->uri_for($c->controller('Bar')->action_for('Foo'), $c->req->captures);
+ $c->uri_for($c->controller('Bar')->action_for('Foo'));
-=back
+ # Path to a static resource
+ $c->uri_for('/static/images/logo.png');
=cut
sub uri_for {
my ( $c, $path, @args ) = @_;
+ if (blessed($path) && $path->isa('Catalyst::Controller')) {
+ $path = $path->path_prefix;
+ $path =~ s{/+\z}{};
+ $path .= '/';
+ }
+
+ undef($path) if (defined $path && $path eq '');
+
+ my $params =
+ ( scalar @args && ref $args[$#args] eq 'HASH' ? pop @args : {} );
+
+ carp "uri_for called with undef argument" if grep { ! defined $_ } @args;
+ foreach my $arg (@args) {
+ utf8::encode($arg) if utf8::is_utf8($arg);
+ $arg =~ s/([^$URI::uric])/$URI::Escape::escapes{$1}/go;
+ }
+
if ( blessed($path) ) { # action object
- my $captures = ( scalar @args && ref $args[0] eq 'ARRAY'
- ? shift(@args)
- : [] );
+ s|/|%2F|g for @args;
+ my $captures = [ map { s|/|%2F|g; $_; }
+ ( scalar @args && ref $args[0] eq 'ARRAY'
+ ? @{ shift(@args) }
+ : ()) ];
+
+ foreach my $capture (@$captures) {
+ utf8::encode($capture) if utf8::is_utf8($capture);
+ $capture =~ s/([^$URI::uric])/$URI::Escape::escapes{$1}/go;
+ }
+
my $action = $path;
$path = $c->dispatcher->uri_for_action($action, $captures);
if (not defined $path) {
@@ -1202,14 +1337,6 @@ sub uri_for {
$path = '/' if $path eq '';
}
- undef($path) if (defined $path && $path eq '');
-
- my $params =
- ( scalar @args && ref $args[$#args] eq 'HASH' ? pop @args : {} );
-
- 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
@@ -1269,6 +1396,20 @@ $c->uri_for >>.
You can also pass in a Catalyst::Action object, in which case it is passed to
C<< $c->uri_for >>.
+Note that although the path looks like a URI that dispatches to the wanted action, it is not a URI, but an internal path to that action.
+
+For example, if the action looks like:
+
+ package MyApp::Controller::Users;
+
+ sub lst : Path('the-list') {}
+
+You can use:
+
+ $c->uri_for_action('/users/lst')
+
+and it will create the URI /users/the-list.
+
=back
=cut
@@ -1401,7 +1542,7 @@ sub welcome_message {
models, and
views;
they can save you a lot of work.
- script/${prefix}_create.pl -help
+ script/${prefix}_create.pl --help
Also, be sure to check out the vast and growing
collection of plugins for Catalyst on CPAN;
you are likely to find what you need there.
@@ -1541,9 +1682,9 @@ sub execute {
sub _stats_start_execute {
my ( $c, $code ) = @_;
-
+ my $appclass = ref($c) || $c;
return if ( ( $code->name =~ /^_.*/ )
- && ( !$c->config->{show_internal_actions} ) );
+ && ( !$appclass->config->{show_internal_actions} ) );
my $action_name = $code->reverse();
$c->counter->{$action_name}++;
@@ -1571,9 +1712,10 @@ sub _stats_start_execute {
# is this a root-level call or a forwarded call?
if ( $callsub =~ /forward$/ ) {
+ my $parent = $c->stack->[-1];
# forward, locate the caller
- if ( my $parent = $c->stack->[-1] ) {
+ if ( defined $parent && exists $c->counter->{"$parent"} ) {
$c->stats->profile(
begin => $action,
parent => "$parent" . $c->counter->{"$parent"},
@@ -1606,25 +1748,6 @@ sub _stats_finish_execute {
$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 ) = ( @_ );
-
- 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
Finalizes the request.
@@ -1662,6 +1785,8 @@ sub finalize {
$c->finalize_body;
}
+ $c->log_response;
+
if ($c->use_stats) {
my $elapsed = sprintf '%f', $c->stats->elapsed;
my $av = $elapsed == 0 ? '??' : sprintf '%.3f', 1 / $elapsed;
@@ -1739,7 +1864,7 @@ sub finalize_headers {
}
else {
# everything should be bytes at this point, but just in case
- $response->content_length( bytes::length( $response->body ) );
+ $response->content_length( length( $response->body ) );
}
}
@@ -1794,7 +1919,7 @@ namespaces.
sub get_actions { my $c = shift; $c->dispatcher->get_actions( $c, @_ ) }
-=head2 $c->handle_request( $class, @arguments )
+=head2 $app->handle_request( @arguments )
Called to handle each HTTP request.
@@ -1854,7 +1979,7 @@ sub prepare {
#surely this is not the most efficient way to do things...
$c->stats($class->stats_class->new)->enable($c->use_stats);
- if ( $c->debug ) {
+ if ( $c->debug || $c->config->{enable_catalyst_header} ) {
$c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION );
}
@@ -1876,7 +2001,7 @@ sub prepare {
$c->prepare_read;
# Parse the body unless the user wants it on-demand
- unless ( $c->config->{parse_on_demand} ) {
+ unless ( ref($c)->config->{parse_on_demand} ) {
$c->prepare_body;
}
}
@@ -1886,8 +2011,7 @@ sub prepare {
$path = '/' unless length $path;
my $address = $c->req->address || '';
- $c->log->debug(qq/"$method" request for "$path" from "$address"/)
- if $c->debug;
+ $c->log_request;
$c->prepare_action;
@@ -1917,17 +2041,6 @@ sub prepare_body {
$c->engine->prepare_body( $c, @_ );
$c->prepare_parameters;
$c->prepare_uploads;
-
- if ( $c->debug && keys %{ $c->req->body_parameters } ) {
- 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 : '';
- $t->row( $key,
- ref $value eq 'ARRAY' ? ( join ', ', @$value ) : $value );
- }
- $c->log->debug( "Body Parameters are:\n" . $t->draw );
- }
}
=head2 $c->prepare_body_chunk( $chunk )
@@ -2011,55 +2124,156 @@ sub prepare_query_parameters {
my $c = shift;
$c->engine->prepare_query_parameters( $c, @_ );
+}
- if ( $c->debug && keys %{ $c->request->query_parameters } ) {
- 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 : '';
- $t->row( $key,
- ref $value eq 'ARRAY' ? ( join ', ', @$value ) : $value );
- }
- $c->log->debug( "Query Parameters are:\n" . $t->draw );
+=head2 $c->log_request
+
+Writes information about the request to the debug logs. This includes:
+
+=over 4
+
+=item * Request method, path, and remote IP address
+
+=item * Query keywords (see L)
+
+=item * Request parameters
+
+=item * File uploads
+
+=back
+
+=cut
+
+sub log_request {
+ my $c = shift;
+
+ return unless $c->debug;
+
+ my($dump) = grep {$_->[0] eq 'Request' } $c->dump_these;
+ my $request = $dump->[1];
+
+ my ( $method, $path, $address ) = ( $request->method, $request->path, $request->address );
+ $method ||= '';
+ $path = '/' unless length $path;
+ $address ||= '';
+ $c->log->debug(qq/"$method" request for "$path" from "$address"/);
+
+ $c->log_request_headers($request->headers);
+
+ if ( my $keywords = $request->query_keywords ) {
+ $c->log->debug("Query keywords are: $keywords");
}
+
+ $c->log_request_parameters( query => $request->query_parameters, $request->_has_body ? (body => $request->body_parameters) : () );
+
+ $c->log_request_uploads($request);
}
-=head2 $c->prepare_read
+=head2 $c->log_response
-Prepares the input for reading.
+Writes information about the response to the debug logs by calling
+C<< $c->log_response_status_line >> and C<< $c->log_response_headers >>.
=cut
-sub prepare_read { my $c = shift; $c->engine->prepare_read( $c, @_ ) }
+sub log_response {
+ my $c = shift;
-=head2 $c->prepare_request
+ return unless $c->debug;
-Prepares the engine request.
+ my($dump) = grep {$_->[0] eq 'Response' } $c->dump_these;
+ my $response = $dump->[1];
+
+ $c->log_response_status_line($response);
+ $c->log_response_headers($response->headers);
+}
+
+=head2 $c->log_response_status_line($response)
+
+Writes one line of information about the response to the debug logs. This includes:
+
+=over 4
+
+=item * Response status code
+
+=item * Content-Type header (if present)
+
+=item * Content-Length header (if present)
+
+=back
=cut
-sub prepare_request { my $c = shift; $c->engine->prepare_request( $c, @_ ) }
+sub log_response_status_line {
+ my ($c, $response) = @_;
-=head2 $c->prepare_uploads
+ $c->log->debug(
+ sprintf(
+ 'Response Code: %s; Content-Type: %s; Content-Length: %s',
+ $response->status || 'unknown',
+ $response->headers->header('Content-Type') || 'unknown',
+ $response->headers->header('Content-Length') || 'unknown'
+ )
+ );
+}
-Prepares uploads.
+=head2 $c->log_response_headers($headers);
+
+Hook method which can be wrapped by plugins to log the responseheaders.
+No-op in the default implementation.
=cut
-sub prepare_uploads {
- my $c = shift;
+sub log_response_headers {}
- $c->engine->prepare_uploads( $c, @_ );
+=head2 $c->log_request_parameters( query => {}, body => {} )
+
+Logs request parameters to debug logs
+
+=cut
+
+sub log_request_parameters {
+ my $c = shift;
+ my %all_params = @_;
+
+ return unless $c->debug;
+
+ my $column_width = Catalyst::Utils::term_width() - 44;
+ foreach my $type (qw(query body)) {
+ my $params = $all_params{$type};
+ next if ! keys %$params;
+ my $t = Text::SimpleTable->new( [ 35, 'Parameter' ], [ $column_width, 'Value' ] );
+ for my $key ( sort keys %$params ) {
+ my $param = $params->{$key};
+ my $value = defined($param) ? $param : '';
+ $t->row( $key, ref $value eq 'ARRAY' ? ( join ', ', @$value ) : $value );
+ }
+ $c->log->debug( ucfirst($type) . " Parameters are:\n" . $t->draw );
+ }
+}
+
+=head2 $c->log_request_uploads
+
+Logs file uploads included in the request to the debug logs.
+The parameter name, filename, file type, and file size are all included in
+the debug logs.
+
+=cut
- if ( $c->debug && keys %{ $c->request->uploads } ) {
+sub log_request_uploads {
+ my $c = shift;
+ my $request = shift;
+ return unless $c->debug;
+ my $uploads = $request->uploads;
+ if ( keys %$uploads ) {
my $t = Text::SimpleTable->new(
[ 12, 'Parameter' ],
[ 26, 'Filename' ],
[ 18, 'Type' ],
[ 9, 'Size' ]
);
- for my $key ( sort keys %{ $c->request->uploads } ) {
- my $upload = $c->request->uploads->{$key};
+ for my $key ( sort keys %$uploads ) {
+ my $upload = $uploads->{$key};
for my $u ( ref $upload eq 'ARRAY' ? @{$upload} : ($upload) ) {
$t->row( $key, $u->filename, $u->type, $u->size );
}
@@ -2068,6 +2282,68 @@ sub prepare_uploads {
}
}
+=head2 $c->log_request_headers($headers);
+
+Hook method which can be wrapped by plugins to log the request headers.
+No-op in the default implementation.
+
+=cut
+
+sub log_request_headers {}
+
+=head2 $c->log_headers($type => $headers)
+
+Logs L (either request or response) to the debug logs.
+
+=cut
+
+sub log_headers {
+ my $c = shift;
+ my $type = shift;
+ my $headers = shift; # an HTTP::Headers instance
+
+ return unless $c->debug;
+
+ my $column_width = Catalyst::Utils::term_width() - 28;
+ my $t = Text::SimpleTable->new( [ 15, 'Header Name' ], [ $column_width, 'Value' ] );
+ $headers->scan(
+ sub {
+ my ( $name, $value ) = @_;
+ $t->row( $name, $value );
+ }
+ );
+ $c->log->debug( ucfirst($type) . " Headers:\n" . $t->draw );
+}
+
+
+=head2 $c->prepare_read
+
+Prepares the input for reading.
+
+=cut
+
+sub prepare_read { my $c = shift; $c->engine->prepare_read( $c, @_ ) }
+
+=head2 $c->prepare_request
+
+Prepares the engine request.
+
+=cut
+
+sub prepare_request { my $c = shift; $c->engine->prepare_request( $c, @_ ) }
+
+=head2 $c->prepare_uploads
+
+Prepares uploads.
+
+=cut
+
+sub prepare_uploads {
+ my $c = shift;
+
+ $c->engine->prepare_uploads( $c, @_ );
+}
+
=head2 $c->prepare_write
Prepares the output for writing.
@@ -2127,40 +2403,31 @@ sub setup_actions { my $c = shift; $c->dispatcher->setup_actions( $c, @_ ) }
=head2 $c->setup_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.
+This method is called internally to set up the application's components.
+
+It finds modules by calling the L method, expands them to
+package names with the L method, and then installs
+each component into the application.
+
+The C config option is passed to both of the above methods.
-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.
+Installation of each component is performed by the L method,
+below.
=cut
sub setup_components {
my $class = shift;
- 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
- );
-
- my @comps = sort { length $a <=> length $b } $locator->plugins;
+ my @comps = $class->locate_components($config);
my %comps = map { $_ => 1 } @comps;
- my $deprecated_component_names = grep { /::[CMV]::/ } @comps;
+ my $deprecatedcatalyst_component_names = grep { /::[CMV]::/ } @comps;
$class->log->warn(qq{Your application is using the deprecated ::[MVC]:: type naming scheme.\n}.
qq{Please switch your class names to ::Model::, ::View:: and ::Controller: as appropriate.\n}
- ) if $deprecated_component_names;
+ ) if $deprecatedcatalyst_component_names;
for my $component ( @comps ) {
@@ -2169,36 +2436,68 @@ 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 = (
- $component => $module,
- map {
- $_ => $class->setup_component( $_ )
- } grep {
- not exists $comps{$_}
- } Devel::InnerPackage::list_packages( $component )
- );
+ }
- for my $key ( keys %modules ) {
- $class->components->{ $key } = $modules{ $key };
+ for my $component (@comps) {
+ my $instance = $class->components->{ $component } = $class->setup_component($component);
+ my @expanded_components = $instance->can('expand_modules')
+ ? $instance->expand_modules( $component, $config )
+ : $class->expand_component_module( $component, $config );
+ for my $component (@expanded_components) {
+ next if $comps{$component};
+ $class->components->{ $component } = $class->setup_component($component);
}
}
}
-=head2 $c->setup_component
+=head2 $c->locate_components( $setup_component_config )
+
+This method is meant to provide a list of component modules that should be
+setup for the application. By default, it will use L.
+
+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 _controller_init_base_classes {
- my ($app_class, $component) = @_;
- foreach my $class ( reverse @{ mro::get_linear_isa($component) } ) {
- Moose::Meta::Class->initialize( $class )
- unless find_meta($class);
- }
+sub locate_components {
+ my $class = shift;
+ my $config = shift;
+
+ my @paths = qw( ::Controller ::C ::Model ::M ::View ::V );
+ my $extra = delete $config->{ search_extra } || [];
+
+ push @paths, @$extra;
+
+ my $locator = Module::Pluggable::Object->new(
+ search_path => [ map { s/^(?=::)/$class/; $_; } @paths ],
+ %$config
+ );
+
+ # XXX think about ditching this sort entirely
+ my @comps = sort { length $a <=> length $b } $locator->plugins;
+
+ return @comps;
}
+=head2 $c->expand_component_module( $component, $setup_component_config )
+
+Components found by C will be passed to this method, which
+is expected to return a list of component (package) names to be set up.
+
+=cut
+
+sub expand_component_module {
+ my ($class, $module) = @_;
+ return Devel::InnerPackage::list_packages( $module );
+}
+
+=head2 $c->setup_component
+
+=cut
+
sub setup_component {
my( $class, $component ) = @_;
@@ -2206,16 +2505,12 @@ sub setup_component {
return $component;
}
- # FIXME - Ugly, ugly hack to ensure the we force initialize non-moose base classes
- # nearest to Catalyst::Controller first, no matter what order stuff happens
- # to be loaded. There are TODO tests in Moose for this, see
- # f2391d17574eff81d911b97be15ea51080500003
- if ($component->isa('Catalyst::Controller')) {
- $class->_controller_init_base_classes($component);
- }
-
my $suffix = Catalyst::Utils::class2classsuffix( $component );
my $config = $class->config->{ $suffix } || {};
+ # Stash catalyst_component_name in the config here, so that custom COMPONENT
+ # methods also pass it. local to avoid pointlessly shitting in config
+ # for the debug screen, as $component is already the key name.
+ local $config->{catalyst_component_name} = $component;
my $instance = eval { $component->COMPONENT( $class, $config ); };
@@ -2499,16 +2794,12 @@ the plugin name does not begin with C.
my $class = ref $proto || $proto;
Class::MOP::load_class( $plugin );
-
+ $class->log->warn( "$plugin inherits from 'Catalyst::Component' - this is decated and will not work in 5.81" )
+ if $plugin->isa( 'Catalyst::Component' );
$proto->_plugins->{$plugin} = 1;
unless ($instant) {
- no strict 'refs';
- if ( my $meta = Class::MOP::get_metaclass_by_name($class) ) {
- my @superclasses = ($plugin, $meta->superclasses );
- $meta->superclasses(@superclasses);
- } else {
- unshift @{"$class\::ISA"}, $plugin;
- }
+ my $meta = Class::MOP::get_metaclass_by_name($class);
+ $meta->superclasses($plugin, $meta->superclasses);
}
return $class;
}
@@ -2517,22 +2808,29 @@ the plugin name does not begin with C.
my ( $class, $plugins ) = @_;
$class->_plugins( {} ) unless $class->_plugins;
- $plugins ||= [];
+ $plugins = Data::OptList::mkopt($plugins || []);
- my @plugins = Catalyst::Utils::resolve_namespace($class . '::Plugin', 'Catalyst::Plugin', @$plugins);
+ my @plugins = map {
+ [ Catalyst::Utils::resolve_namespace(
+ $class . '::Plugin',
+ 'Catalyst::Plugin', $_->[0]
+ ),
+ $_->[1],
+ ]
+ } @{ $plugins };
for my $plugin ( reverse @plugins ) {
- Class::MOP::load_class($plugin);
- my $meta = find_meta($plugin);
+ Class::MOP::load_class($plugin->[0], $plugin->[1]);
+ my $meta = find_meta($plugin->[0]);
next if $meta && $meta->isa('Moose::Meta::Role');
- $class->_register_plugin($plugin);
+ $class->_register_plugin($plugin->[0]);
}
my @roles =
- map { $_->name }
- grep { $_ && blessed($_) && $_->isa('Moose::Meta::Role') }
- map { find_meta($_) }
+ map { $_->[0]->name, $_->[1] }
+ grep { blessed($_->[0]) && $_->[0]->isa('Moose::Meta::Role') }
+ map { [find_meta($_->[0]), $_->[1]] }
@plugins;
Moose::Util::apply_all_roles(
@@ -2546,15 +2844,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
+
+Returns the current timing statistics object. By default Catalyst uses
+L, but can be set otherwise with
+L<< stats_class|/"$c->stats_class" >>.
+
+Even if L<< -Stats|/"-Stats" >> is not enabled, the stats object is still
+available. By enabling it with C< $c->stats->enabled(1) >, it can be used to
+profile explicitly, although MyApp.pm still won't profile nor output anything
+by itself.
+
=head2 $c->stats_class
-Returns or sets the stats (timing statistics) class.
+Returns or sets the stats (timing statistics) class. L is used by default.
=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.
+Returns 1 when L<< stats collection|/"-Stats" >> is enabled.
Note that this is a static method, not an accessor and should be overridden
by declaring C in your MyApp.pm, not by calling C<< $c->use_stats(1) >>.
@@ -2590,6 +2897,78 @@ messages in template systems.
sub version { return $Catalyst::VERSION }
+=head1 CONFIGURATION
+
+There are a number of 'base' config variables which can be set:
+
+=over
+
+=item *
+
+C - The default model picked if you say C<< $c->model >>. See L<< /$c->model($name) >>.
+
+=item *
+
+C - The default view to be rendered or returned when C<< $c->view >> is called. See L<< /$c->view($name) >>.
+
+=item *
+
+C - Turns
+off the deprecated component resolution functionality so
+that if any of the component methods (e.g. C<< $c->controller('Foo') >>)
+are called then regex search will not be attempted on string values and
+instead C will be returned.
+
+=item *
+
+C - The application home directory. In an uninstalled application,
+this is the top level application directory. In an installed application,
+this will be the directory containing C<< MyApp.pm >>.
+
+=item *
+
+C - See L
+
+=item *
+
+C - The name of the application in debug messages and the debug and
+welcome screens
+
+=item *
+
+C - The request body (for example file uploads) will not be parsed
+until it is accessed. This allows you to (for example) check authentication (and reject
+the upload) before actually recieving all the data. See L
+
+=item *
+
+C - The root directory for templates. Usually this is just a
+subdirectory of the home directory, but you can set it to change the
+templates to a different directory.
+
+=item *
+
+C - Array reference passed to Module::Pluggable to for additional
+namespaces from which components will be loaded (and constructed and stored in
+C<< $c->components >>).
+
+=item *
+
+C - If true, causes internal actions such as C<< _DISPATCH >>
+to be shown in hit debug tables in the test server.
+
+=item *
+
+C - Controlls if the C or C environment
+variable should be used for determining the request path. See L
+for more information.
+
+=item *
+
+C - See L.
+
+=back
+
=head1 INTERNAL ACTIONS
Catalyst uses internal actions like C<_DISPATCH>, C<_BEGIN>, C<_AUTO>,
@@ -2598,16 +2977,6 @@ action table, but you can make them visible with a config parameter.
MyApp->config(show_internal_actions => 1);
-=head1 CASE SENSITIVITY
-
-By default Catalyst is not case sensitive, so C is
-mapped to C. You can activate case sensitivity with a config
-parameter.
-
- MyApp->config(case_sensitive => 1);
-
-This causes C to map to C.
-
=head1 ON-DEMAND PARSER
The request body is usually parsed at the beginning of a request,
@@ -2635,6 +3004,18 @@ changes are made to the request.
The host value for $c->req->base and $c->req->uri is set to the real
host, as read from the HTTP X-Forwarded-Host header.
+Additionally, you may be running your backend application on an insecure
+connection (port 80) while your frontend proxy is running under SSL. If there
+is a discrepancy in the ports, use the HTTP header C to
+tell Catalyst what port the frontend listens on. This will allow all URIs to
+be created properly.
+
+In the case of passing in:
+
+ X-Forwarded-Port: 443
+
+All calls to C will result in an https link, as is expected.
+
Obviously, your web server must support these headers for this to work.
In a more complex server farm environment where you may have your
@@ -2683,7 +3064,7 @@ Wiki:
=head2 L - The Catalyst Manual
-=head2 L, L - Base classes for components
+=head2 L, L - Base classes for components
=head2 L - Core engine
@@ -2705,9 +3086,11 @@ abw: Andy Wardley
acme: Leon Brocard
+abraxxa: Alexander Hartmaier
+
Andrew Bramble
-Andrew Ford
+Andrew Ford EA.Ford@ford-mason.co.ukE
Andrew Ruthven
@@ -2723,8 +3106,18 @@ chansen: Christian Hansen
chicks: Christopher Hicks
+Chisel Wright C
+
+Danijel Milicevic C
+
+David Kamholz Edkamholz@cpan.orgE
+
+David Naughton, C
+
David E. Wheeler
+dhoss: Devin Austin
+
dkubb: Dan Kubb
Drew Taylor
@@ -2735,16 +3128,26 @@ esskar: Sascha Kiefer
fireartist: Carl Franks
+frew: Arthur Axel "fREW" Schmidt
+
gabb: Danijel Milicevic
Gary Ashton Jones
+Gavin Henry C
+
Geoff Richards
+groditi: Guillermo Roditi
+
+hobbs: Andrew Rodland
+
ilmari: Dagfinn Ilmari Mannsåker
jcamacho: Juan Camacho
+jester: Jesse Sheidlower C
+
jhannah: Jay Hannah
Jody Belka
@@ -2753,6 +3156,12 @@ Johan Lindstrom
jon: Jon Schutz
+Jonathan Rockway C<< >>
+
+Kieren Diment C
+
+konobi: Scott McWhirter
+
marcus: Marcus Ramberg
miyagawa: Tatsuhiko Miyagawa
@@ -2771,6 +3180,8 @@ numa: Dan Sully
obra: Jesse Vincent
+Octavian Rasnita
+
omega: Andreas Marienborg
Oleg Kostyuk
@@ -2781,16 +3192,30 @@ rafl: Florian Ragwitz
random: Roland Lammel
+Robert Sedlacek C<< >>
+
+SpiceMan: Marcel Montes
+
sky: Arthur Bergman
-the_jester: Jesse Sheidlower
+szbalint: Balint Szilakszi
t0m: Tomas Doran
Ulf Edvinsson
+Viljo Marrandi C
+
+Will Hawes C
+
willert: Sebastian Willert
+wreis: Wallace Reis
+
+Yuval Kogman, C
+
+rainboxx: Matthias Dietrich, C
+
=head1 LICENSE
This library is free software. You can redistribute it and/or modify it under