X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Runtime.git;a=blobdiff_plain;f=lib%2FCatalyst.pm;h=9c5bc8cc840cac66477dc743edccb90832eeec58;hp=bed30ee274fab24510b0b8999c32e3a712632c38;hb=4ac0b9cb8e9043db8a95f44af685c782bf9426e7;hpb=75aff34de50f286204ccee4b293e761c90243b4e diff --git a/lib/Catalyst.pm b/lib/Catalyst.pm index bed30ee..9c5bc8c 100644 --- a/lib/Catalyst.pm +++ b/lib/Catalyst.pm @@ -1,8 +1,9 @@ package Catalyst; -use strict; -use base 'Catalyst::Component'; +use Moose; +extends 'Catalyst::Component'; use bytes; +use Scope::Upper (); use Catalyst::Exception; use Catalyst::Log; use Catalyst::Request; @@ -13,36 +14,45 @@ 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 Class::C3::Adopt::NEXT; use attributes; use utf8; -use Carp qw/croak carp/; +use Carp qw/croak carp shortmess/; BEGIN { require 5.008001; } -__PACKAGE__->mk_accessors( - qw/counter request response state action stack namespace stats/ -); +has stack => (is => 'ro', 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; @@ -51,6 +61,8 @@ 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 stats_class @@ -64,7 +76,14 @@ __PACKAGE__->stats_class('Catalyst::Stats'); # Remember to update this in Catalyst::Runtime as well! -our $VERSION = '5.7099_02'; +our $VERSION = '5.8000_06'; + +{ + my $dev_version = $VERSION =~ /_\d{2}$/; + *_IS_DEVELOPMENT_VERSION = sub () { $dev_version }; +} + +$VERSION = eval $VERSION; sub import { my ( $class, @arguments ) = @_; @@ -73,11 +92,23 @@ sub import { # callers @ISA. return unless $class eq 'Catalyst'; - my $caller = caller(0); + 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); + #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] ); @@ -98,7 +129,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 @@ -239,7 +270,9 @@ MYAPP_WEB_HOME. If both variables are set, the MYAPP_HOME one will be used. =head2 -Log -Specifies log level. + use Catalyst '-Log=warn,fatal,error'; + +Specifies a comma-delimited list of log levels. =head2 -Stats @@ -311,7 +344,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 ] ) @@ -328,13 +361,40 @@ When called with no arguments it escapes the processing chain entirely. sub detach { my $c = shift; $c->dispatcher->detach( $c, @_ ) } -=head2 $c->go( $action [, \@arguments ] ) +=head2 $c->visit( $action [, \@captures, \@arguments ] ) -=head2 $c->go( $class, $method, [, \@arguments ] ) +=head2 $c->visit( $class, $method, [, \@captures, \@arguments ] ) -Almost the same as C, but does a full dispatch, instead of just +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 is called, just like a new request. +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 +invoked from the callee. + +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 [, \@captures, \@arguments ] ) + +=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. C<$c-Estash> is kept unchanged. @@ -367,17 +427,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 @@ -434,6 +498,7 @@ sub _comp_search_prefixes { my ( $c, $name, @prefixes ) = @_; my $appclass = ref $c || $c; my $filter = "^${appclass}::(" . join( '|', @prefixes ) . ')::'; + $filter = qr/$filter/; # Compile regex now rather than once per loop # map the original component name to the sub part that we will search against my %eligible = map { my $n = $_; $n =~ s{^$appclass\::[^:]+::}{}; $_ => $n; } @@ -461,8 +526,26 @@ sub _comp_search_prefixes { # don't warn if we didn't find any results, it just might not exist if( @result ) { - $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.' ); + # Disgusting hack to work out correct method name + my $warn_for = lc $prefixes[0]; + my $msg = "Used regexp fallback for \$c->{$warn_for}('${name}'), which found '" . + (join '", "', @result) . "'. Relying on regexp fallback behavior for " . + "component resolution is unreliable and unsafe."; + my $short = $result[0]; + $short =~ s/.*?Model:://; + my $shortmess = Carp::shortmess(''); + if ($shortmess =~ m#Catalyst/Plugin#) { + $msg .= " You probably need to set '$short' instead of '${name}' in this " . + "plugin's config"; + } elsif ($shortmess =~ m#Catalyst/lib/(View|Controller)#) { + $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}'}), " . + "but if you really wanted to search, pass in a regexp as the argument " . + "like so: \$c->${warn_for}(qr/${name}/)"; + } + $c->log->warn( "${msg}$shortmess" ); } return @result; @@ -563,11 +646,11 @@ sub model { my( $comp, $rest ) = $c->_comp_search_prefixes( undef, qw/Model M/); if( $rest ) { - $c->log->warn( 'Calling $c->model() will return a random model unless you specify one of:' ); + $c->log->warn( Carp::shortmess('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.' ); + $c->log->warn( 'NB: in version 5.81, the "random" behavior will not work at all.' ); } return $c->_filter_component( $comp ); @@ -620,7 +703,7 @@ sub view { $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.' ); + $c->log->warn( 'NB: in version 5.81, the "random" behavior will not work at all.' ); } return $c->_filter_component( $comp ); @@ -682,11 +765,13 @@ sub component { if( !ref $name ) { # is it the exact name? - return $comps->{ $name } if exists $comps->{ $name }; + return $c->_filter_component( $comps->{ $name }, @args ) + if exists $comps->{ $name }; # perhaps we just omitted "MyApp"? my $composed = ( ref $c || $c ) . "::${name}"; - return $comps->{ $composed } if exists $comps->{ $composed }; + return $c->_filter_component( $comps->{ $composed }, @args ) + if exists $comps->{ $composed }; # search all of the models, views and controllers my( $comp ) = $c->_comp_search_prefixes( $name, qw/Model M Controller C View V/ ); @@ -697,12 +782,13 @@ sub component { my $query = ref $name ? $name : qr{$name}i; my @result = grep { m{$query} } keys %{ $c->components }; - return @result if ref $name; + return map { $c->_filter_component( $_, @args ) } @result if ref $name; if( $result[ 0 ] ) { + $c->log->warn( Carp::shortmess(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 $result[ 0 ]; + return $c->_filter_component( $result[ 0 ], @args ); } # I would expect to return an empty list here, but that breaks back-compat @@ -721,23 +807,46 @@ 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.yml in your applications home directory. See +like myapp.conf in your applications home directory. See L. - --- - db: dsn:SQLite:foo.db +=head3 Cascading configuration. + +The config method is present on all Catalyst components, and configuration +will be merged when an application is started. Configuration loaded with +L takes precedence over other configuration, +followed by configuration in your top level C class. These two +configurations are merged, and then configuration data whos hash key matches a +component name is merged with configuration for that component. + +The configuration for a component is then passed to the C method when a +component is constructed. + +For example: + + MyApp->config({ 'Model::Foo' => { bar => 'baz', overrides => 'me' } }); + MyApp::Model::Foo->config({ quux => 'frob', 'overrides => 'this' }); + +will mean that C receives the following data when +constructed: + MyApp::Model::Foo->new({ + bar => 'baz', + quux => 'frob', + overrides => 'me', + }); =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 ); + croak('Setting config after setup has been run is not allowed.') + if ( @_ and $c->setup_finished ); - $c->NEXT::config(@_); -} + $c->$orig(@_); +}; =head2 $c->log @@ -759,10 +868,21 @@ L. =head2 $c->debug -Overload to enable debug messages (same as -Debug option). +Returns 1 if debug mode is enabled, 0 otherwise. -Note that this is a static method, not an accessor and should be overloaded -by declaring "sub debug { 1 }" in your MyApp.pm, not by calling $c->debug(1). +You can enable debug mode in several ways: + +=over + +=item With the environment variables MYAPP_DEBUG, or CATALYST_DEBUG + +=item The -Debug option in your MyApp.pm + +=item By declaring "sub debug { 1 }" in your MyApp.pm. + +=back + +Calling $c->debug(1) has no effect. =cut @@ -770,13 +890,11 @@ sub debug { 0 } =head2 $c->dispatcher -Returns the dispatcher instance. Stringifies to class name. See -L. +Returns the dispatcher instance. See L. =head2 $c->engine -Returns the engine instance. Stringifies to the class name. See -L. +Returns the engine instance. See L. =head2 UTILITY METHODS @@ -801,17 +919,25 @@ sub path_to { =head2 $c->plugin( $name, $class, @args ) -Helper method for plugins. It creates a classdata accessor/mutator and +Helper method for plugins. It creates a class data accessor/mutator and loads and instantiates the given class. MyApp->plugin( 'prototype', 'HTML::Prototype' ); $c->prototype->define_javascript_functions; + +B This method of adding plugins is deprecated. The ability +to add plugins like this B in a Catalyst 5.9. +Please do not use this functionality in new code. =cut sub plugin { my ( $class, $name, $plugin, @args ) = @_; + + # See block comment in t/unit_core_plugin.t + $class->log->warn(qq/Adding plugin using the ->plugin method is deprecated, and will be removed in Catalyst 5.81/); + $class->_register_plugin( $plugin, 1 ); eval { $plugin->import }; @@ -843,9 +969,8 @@ Catalyst> line. sub setup { my ( $class, @arguments ) = @_; - - $class->log->warn("Running setup twice is not a good idea.") - if ( $class->setup_finished ); + croak('Running setup more than once') + if ( $class->setup_finished ); unless ( $class->isa('Catalyst') ) { @@ -910,7 +1035,8 @@ EOF my @plugins = map { "$_ " . ( $_->VERSION || '' ) } $class->registered_plugins; if (@plugins) { - my $t = Text::SimpleTable->new(74); + my $column_width = Catalyst::Utils::term_width() - 6; + my $t = Text::SimpleTable->new($column_width); $t->row($_) for @plugins; $class->log->debug( "Loaded plugins:\n" . $t->draw . "\n" ); } @@ -919,8 +1045,8 @@ EOF my $engine = $class->engine; my $home = $class->config->{home}; - $class->log->debug(qq/Loaded dispatcher "$dispatcher"/); - $class->log->debug(qq/Loaded engine "$engine"/); + $class->log->debug(sprintf(q/Loaded dispatcher "%s"/, blessed($dispatcher))); + $class->log->debug(sprintf(q/Loaded engine "%s"/, blessed($engine))); $home ? ( -d $home ) @@ -929,7 +1055,7 @@ EOF : $class->log->debug(q/Couldn't find home/); } - # Call plugins setup + # Call plugins setup, this is stupid and evil. { no warnings qw/redefine/; local *setup = sub { }; @@ -942,7 +1068,8 @@ EOF $class->setup_components; if ( $class->debug ) { - my $t = Text::SimpleTable->new( [ 63, 'Class' ], [ 8, 'Type' ] ); + my $column_width = Catalyst::Utils::term_width() - 8 - 9; + my $t = Text::SimpleTable->new( [ $column_width, 'Class' ], [ 8, 'Type' ] ); for my $comp ( sort keys %{ $class->components } ) { my $type = ref $class->components->{$comp} ? 'instance' : 'class'; $t->row( $comp, $type ); @@ -952,7 +1079,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; @@ -962,38 +1091,86 @@ EOF } $class->log->_flush() if $class->log->can('_flush'); + # Make sure that the application class becomes immutable at this point, + # which ensures that it gets an inlined constructor. This means that it + # works even if the user has added a plugin which contains a new method. + # Note however that we have to do the work on scope end, so that method + # modifiers work correctly in MyApp (as you have to call setup _before_ + # applying modifiers). + Scope::Upper::reap(sub { + my $meta = Class::MOP::get_metaclass_by_name($class); + $meta->make_immutable unless $meta->is_immutable; + }, Scope::Upper::SCOPE(1)); + + $class->setup_finalize; +} + + +=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. + + sub setup_finalize { + + my $app = shift; + + ## do stuff, i.e., determine a primary key column for sessions stored in a DB + + $app->next::method(@_); + + + } + +=cut + +sub setup_finalize { + my ($class) = @_; $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 + +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 >>. -Note that uri_for is destructive to the passed hashref. Subsequent calls -with the same hashref may have unintended results. + # 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); -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. +=back =cut sub uri_for { my ( $c, $path, @args ) = @_; - if ( Scalar::Util::blessed($path) ) { # action object + if ( blessed($path) ) { # action object my $captures = ( scalar @args && ref $args[0] eq 'ARRAY' ? shift(@args) : [] ); - $path = $c->dispatcher->uri_for_action($path, $captures); - return undef unless defined($path); + my $action = $path; + $path = $c->dispatcher->uri_for_action($action, $captures); + if (not defined $path) { + $c->log->debug(qq/Can't find uri_for action '$action' @$captures/) + if $c->debug; + return undef; + } $path = '/' if $path eq ''; } @@ -1018,7 +1195,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{(?uri_for_action( $path, \@captures?, @args?, \%query_values? ) + +=head2 $c->uri_for_action( $action, \@captures?, @args?, \%query_values? ) + +=over + +=item $path + +A private path to the Catalyst action you want to create a URI for. + +This is a shortcut for calling C<< $c->dispatcher->get_action_by_path($path) +>> and passing the resulting C<$action> and the remaining arguments to C<< +$c->uri_for >>. + +You can also pass in a Catalyst::Action object, in which case it is passed to +C<< $c->uri_for >>. + +=back + +=cut + +sub uri_for_action { + my ( $c, $path, @args ) = @_; + my $action = blessed($path) + ? $path + : $c->dispatcher->get_action_by_path($path); + unless (defined $action) { + croak "Can't find action for path '$path'"; + } + return $c->uri_for( $action, @args ); +} + =head2 $c->welcome_message Returns the Catalyst welcome HTML page. @@ -1166,7 +1375,7 @@ sub welcome_message { they can save you a lot of work.

script/${prefix}_create.pl -help

Also, be sure to check out the vast and growing - collection of plugins for Catalyst on CPAN; + collection of plugins for Catalyst on CPAN; you are likely to find what you need there.

@@ -1178,7 +1387,7 @@ sub welcome_message { Wiki
  • - Mailing-List + Mailing-List
  • IRC channel #catalyst on irc.perl.org @@ -1260,9 +1469,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); @@ -1273,7 +1482,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; @@ -1307,9 +1516,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 @@ -1328,7 +1538,7 @@ sub _stats_start_execute { } } - my $uid = "$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$/ ) { @@ -1371,6 +1581,8 @@ sub _stats_finish_execute { =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 ) = ( @_ ); @@ -1398,8 +1610,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 { @@ -1463,31 +1676,33 @@ 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'); @@ -1495,14 +1710,14 @@ sub finalize_headers { } else { # everything should be bytes at this point, but just in case - $c->response->content_length( bytes::length( $c->response->body ) ); + $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; @@ -1510,7 +1725,7 @@ sub finalize_headers { $c->engine->finalize_headers( $c, @_ ); # Done - $c->response->{_finalized_headers} = 1; + $response->finalized_headers(1); } =head2 $c->finalize_output @@ -1580,7 +1795,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; } @@ -1594,48 +1812,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 ); @@ -1688,8 +1882,7 @@ Prepares message body. sub prepare_body { my $c = shift; - # Do we run for the first time? - return if defined $c->request->{_body}; + return if $c->request->_has_body; # Initialize on-demand data $c->engine->prepare_body( $c, @_ ); @@ -1934,7 +2127,12 @@ sub setup_components { my @comps = sort { length $a <=> length $b } $locator->plugins; my %comps = map { $_ => 1 } @comps; - + + my $deprecated_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; + for my $component ( @comps ) { # We pass ignore_loaded here so that overlay files for (e.g.) @@ -1942,6 +2140,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 = ( @@ -1985,7 +2184,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; } @@ -2011,9 +2210,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 ); @@ -2037,12 +2234,10 @@ sub setup_engine { } if ( $ENV{MOD_PERL} ) { - + my $meta = Class::MOP::get_metaclass_by_name($class); + # 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+)+)/; @@ -2099,9 +2294,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; @@ -2152,11 +2345,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'); } @@ -2164,21 +2356,35 @@ sub setup_home { =head2 $c->setup_log -Sets up log. +Sets up log by instantiating a L object and +passing it to C. Pass in a comma-delimited list of levels to set the +log to. + +This method also installs a C method that returns a true value into the +catalyst subclass if the "debug" level is passed in the comma-delimited list, +or if the C<$CATALYST_DEBUG> environment variable is set to a true value. + +Note that if the log has already been setup, by either a previous call to +C or by a call such as C<< __PACKAGE__->log( MyLogger->new ) >>, +that this method won't actually set up the log object. =cut sub setup_log { - my ( $class, $debug ) = @_; + my ( $class, $levels ) = @_; + $levels ||= ''; + $levels =~ s/^\s+//; + $levels =~ s/\s+$//; + my %levels = map { $_ => 1 } split /\s*,\s*/, $levels || ''; + unless ( $class->log ) { - $class->log( Catalyst::Log->new ); + $class->log( Catalyst::Log->new(keys %levels) ); } my $env_debug = Catalyst::Utils::env_value( $class, 'DEBUG' ); - if ( defined($env_debug) ? $env_debug : $debug ) { - no strict 'refs'; - *{"$class\::debug"} = sub { 1 }; + if ( defined($env_debug) or $levels{debug} ) { + Class::MOP::get_metaclass_by_name($class)->add_method('debug' => sub { 1 }); $class->log->debug('Debug messages enabled'); } } @@ -2202,8 +2408,7 @@ sub setup_stats { my $env = Catalyst::Utils::env_value( $class, 'STATS' ); if ( defined($env) ? $env : ($stats || $class->debug ) ) { - no strict 'refs'; - *{"$class\::use_stats"} = sub { 1 }; + Class::MOP::get_metaclass_by_name($class)->add_method('use_stats' => sub { 1 }); $class->log->debug('Statistics enabled'); } } @@ -2241,12 +2446,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::MOP::get_metaclass_by_name($class) ) { + my @superclasses = ($plugin, $meta->superclasses ); + $meta->superclasses(@superclasses); + } else { + unshift @{"$class\::ISA"}, $plugin; + } } return $class; } @@ -2392,8 +2602,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: @@ -2443,10 +2653,14 @@ audreyt: Audrey Tang bricas: Brian Cassidy +Caelum: Rafael Kitover + chansen: Christian Hansen chicks: Christopher Hicks +David E. Wheeler + dkubb: Dan Kubb Drew Taylor @@ -2461,8 +2675,12 @@ Gary Ashton Jones Geoff Richards +ilmari: Dagfinn Ilmari Mannsåker + jcamacho: Juan Camacho +jhannah: Jay Hannah + Jody Belka Johan Lindstrom @@ -2489,12 +2707,18 @@ obra: Jesse Vincent omega: Andreas Marienborg +Oleg Kostyuk + phaylon: Robert Sedlacek +rafl: Florian Ragwitz + sky: Arthur Bergman the_jester: Jesse Sheidlower +t0m: Tomas Doran + Ulf Edvinsson willert: Sebastian Willert @@ -2506,4 +2730,8 @@ the same terms as Perl itself. =cut +no Moose; + +__PACKAGE__->meta->make_immutable; + 1;