X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Runtime.git;a=blobdiff_plain;f=lib%2FCatalyst.pm;h=9c7dec24edef217135cf642b5ad0d566d25bfc6e;hp=5d0dcaa1ca19292cf2c2a67dc0d883a4ff74632a;hb=gsoc_breadboard;hpb=d455230c403e68cf4a364f3e0ac50c1749353e8e diff --git a/lib/Catalyst.pm b/lib/Catalyst.pm index 5d0dcaa..9c7dec2 100644 --- a/lib/Catalyst.pm +++ b/lib/Catalyst.pm @@ -4,7 +4,7 @@ use Moose; use Moose::Meta::Class (); extends 'Catalyst::Component'; use Moose::Util qw/find_meta/; -use B::Hooks::EndOfScope (); +use namespace::clean -except => 'meta'; use Catalyst::Exception; use Catalyst::Exception::Detach; use Catalyst::Exception::Go; @@ -15,8 +15,7 @@ use Catalyst::Response; use Catalyst::Utils; use Catalyst::Controller; use Data::OptList; -use Devel::InnerPackage (); -use Module::Pluggable::Object (); +use File::stat; use Text::SimpleTable (); use Path::Class::Dir (); use Path::Class::File (); @@ -33,12 +32,14 @@ use Catalyst::EngineLoader; use utf8; use Carp qw/croak carp shortmess/; use Try::Tiny; +use Safe::Isa; use Plack::Middleware::Conditional; use Plack::Middleware::ReverseProxy; use Plack::Middleware::IIS6ScriptNameFix; +use Plack::Middleware::IIS7KeepAliveFix; use Plack::Middleware::LighttpdScriptNameFix; -BEGIN { require 5.008004; } +BEGIN { require 5.008003; } has stack => (is => 'ro', default => sub { [] }); has stash => (is => 'rw', default => sub { {} }); @@ -46,8 +47,34 @@ 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 request => ( + is => 'rw', + default => sub { + my $self = shift; + $self->request_class->new($self->_build_request_constructor_args); + }, + lazy => 1, +); +sub _build_request_constructor_args { + my $self = shift; + my %p = ( _log => $self->log ); + $p{_uploadtmp} = $self->_uploadtmp if $self->_has_uploadtmp; + \%p; +} + +has response => ( + is => 'rw', + default => sub { + my $self = shift; + $self->response_class->new($self->_build_response_constructor_args); + }, + lazy => 1, +); +sub _build_response_constructor_args { + my $self = shift; + { _log => $self->log }; +} + has namespace => (is => 'rw'); sub depth { scalar @{ shift->stack || [] }; } @@ -73,9 +100,9 @@ our $GO = Catalyst::Exception::Go->new; #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 + for qw/container arguments dispatcher engine log dispatcher_class engine_loader context_class request_class response_class stats_class - setup_finished _psgi_app loading_psgi_file/; + setup_finished _psgi_app loading_psgi_file run_options/; __PACKAGE__->dispatcher_class('Catalyst::Dispatcher'); __PACKAGE__->request_class('Catalyst::Request'); @@ -84,7 +111,7 @@ __PACKAGE__->stats_class('Catalyst::Stats'); # Remember to update this in Catalyst::Runtime as well! -our $VERSION = '5.90002'; +our $VERSION = '5.90017'; sub import { my ( $class, @arguments ) = @_; @@ -97,10 +124,12 @@ sub import { return if $caller eq 'main'; my $meta = Moose::Meta::Class->initialize($caller); - unless ( $caller->isa('Catalyst') ) { - my @superclasses = ($meta->superclasses, $class, 'Catalyst::Controller'); - $meta->superclasses(@superclasses); - } + + unless ( $caller->isa('Catalyst') ) { # XXX - Remove! + my @superclasses = ($meta->superclasses, $class, 'Catalyst::Component'); # XXX - Remove! + $meta->superclasses(@superclasses); # XXX - Remove! + } # XXX - Remove! + # Avoid possible C3 issues if 'Moose::Object' is already on RHS of MyApp $meta->superclasses(grep { $_ ne 'Moose::Object' } $meta->superclasses); @@ -114,11 +143,29 @@ sub import { } $caller->arguments( [@arguments] ); + + # FIXME + # what is this for? + # we call setup_home on import AND on ->setup + # is there a reason for it? + # anyway there is no point for setup_home without setup_config() so... + $caller->setup_config; $caller->setup_home; } +sub MODIFY_CODE_ATTRIBUTES { + Catalyst::Exception->throw( + "Catalyst applications (aka MyApp) cannot be controllers anymore. " . + "That has been deprecated and removed. You should create a " . + "controller class called Root.pm, and move relevant code to that class." + ); +} + + sub _application { $_[0] } +=encoding UTF-8 + =head1 NAME Catalyst - The Elegant MVC Web Application Framework @@ -148,7 +195,7 @@ documentation and tutorials. use Catalyst qw/-Debug/; # include plugins here as well ### In lib/MyApp/Controller/Root.pm (autocreated) - sub foo : Global { # called for /foo, /foo/1, /foo/1/2, etc. + sub foo : Chained('/') Args() { # 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 # lookup something from db -- stash vars are passed to TT @@ -166,50 +213,16 @@ documentation and tutorials. [% END %] # called for /bar/of/soap, /bar/of/soap/10, etc. - sub bar : Path('/bar/of/soap') { ... } - - # called for all actions, from the top-most controller downwards - sub auto : Private { - my ( $self, $c ) = @_; - if ( !$c->user_exists ) { # Catalyst::Plugin::Authentication - $c->res->redirect( '/login' ); # require login - return 0; # abort request and go immediately to end() - } - return 1; # success; carry on to next action - } + sub bar : Chained('/') PathPart('/bar/of/soap') Args() { ... } # called after all actions are finished - sub end : Private { + sub end : Action { my ( $self, $c ) = @_; if ( scalar @{ $c->error } ) { ... } # handle errors return if $c->res->body; # already have a response $c->forward( 'MyApp::View::TT' ); # render template } - ### in MyApp/Controller/Foo.pm - # called for /foo/bar - sub bar : Local { ... } - - # called for /blargle - sub blargle : Global { ... } - - # an index action matches /foo, but not /foo/1, etc. - sub index : Private { ... } - - ### in MyApp/Controller/Foo/Bar.pm - # called for /foo/bar/baz - sub baz : Local { ... } - - # first Root auto is called, then Foo auto, then this - sub auto : Private { ... } - - # powerful regular expression paths are also possible - sub details : Regex('^product/(\w+)/details$') { - my ( $self, $c ) = @_; - # extract the (\w+) from the URI - my $product = $c->req->captures->[0]; - } - See L for additional information. =head1 DESCRIPTION @@ -236,7 +249,7 @@ fully qualify the name by using a unary plus: +Fully::Qualified::Plugin::Name /; -Special flags like C<-Debug> and C<-Engine> can also be specified as +Special flags like C<-Debug> can also be specified as arguments when Catalyst is loaded: use Catalyst qw/-Debug My::Module/; @@ -256,13 +269,6 @@ 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 -C prefix of the engine name, i.e.: - - use Catalyst qw/-Engine=CGI/; - =head2 -Home Forces Catalyst to use a specific home directory, e.g.: @@ -276,11 +282,11 @@ 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 +home directory. If you are working in a development environment, 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 module, without the .pm extension (e.g., /foo/MyApp if your application was installed at /foo/MyApp.pm) =head2 -Log @@ -348,9 +354,10 @@ call to forward. 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: +L<< execute|/"$c->execute( $class, $coderef )" >> does), thus rendering all +exceptions thrown by the called action non-fatal and pushing them onto +$c->error instead. If you want C to propagate you need to do something +like: $c->forward('foo'); die join "\n", @{ $c->error } if @{ $c->error }; @@ -396,8 +403,12 @@ When called with no arguments it escapes the processing chain entirely. sub detach { my $c = shift; $c->dispatcher->detach( $c, @_ ) } +=head2 $c->visit( $action [, \@arguments ] ) + =head2 $c->visit( $action [, \@captures, \@arguments ] ) +=head2 $c->visit( $class, $method, [, \@arguments ] ) + =head2 $c->visit( $class, $method, [, \@captures, \@arguments ] ) Almost the same as L<< forward|/"$c->forward( $action [, \@arguments ] )" >>, @@ -412,7 +423,7 @@ 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. +invoked from the called action. C<< $c->stash >> is kept unchanged. @@ -426,8 +437,12 @@ transfer control to another action as if it had been reached directly from a URL sub visit { my $c = shift; $c->dispatcher->visit( $c, @_ ) } +=head2 $c->go( $action [, \@arguments ] ) + =head2 $c->go( $action [, \@captures, \@arguments ] ) +=head2 $c->go( $class, $method, [, \@arguments ] ) + =head2 $c->go( $class, $method, [, \@captures, \@arguments ] ) The relationship between C and @@ -541,98 +556,6 @@ sub clear_errors { $c->error(0); } -sub _comp_search_prefixes { - my $c = shift; - return map $c->components->{ $_ }, $c->_comp_names_search_prefixes(@_); -} - -# search components given a name and some prefixes -sub _comp_names_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; } - grep { /$filter/ } keys %{ $c->components }; - - # undef for a name will return all - return keys %eligible if !defined $name; - - my $query = ref $name ? $name : qr/^$name$/i; - my @result = grep { $eligible{$_} =~ m{$query} } keys %eligible; - - return @result if @result; - - # 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; - - # no results? try against full names - if( !@result ) { - @result = grep { m{$query} } keys %eligible; - } - - # don't warn if we didn't find any results, it just might not exist - if( @result ) { - # 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]; - # 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 " . - "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; -} - -# Find possible names for a prefix -sub _comp_names { - my ( $c, @prefixes ) = @_; - my $appclass = ref $c || $c; - - my $filter = "^${appclass}::(" . join( '|', @prefixes ) . ')::'; - - my @names = map { s{$filter}{}; $_; } - $c->_comp_names_search_prefixes( undef, @prefixes ); - - return @names; -} - -# Filter a component before returning by calling ACCEPT_CONTEXT if available -sub _filter_component { - my ( $c, $comp, @args ) = @_; - - if ( eval { $comp->can('ACCEPT_CONTEXT'); } ) { - return $comp->ACCEPT_CONTEXT( $c, @args ); - } - - return $comp; -} - =head2 COMPONENT ACCESSORS =head2 $c->controller($name) @@ -652,23 +575,7 @@ If you want to search for controllers, pass in a regexp as the argument. =cut -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 ); - } - - return $c->component( $c->action->class ); -} +sub controller { shift->_lookup_mvc('controller', @_) } =head2 $c->model($name) @@ -691,42 +598,7 @@ If you want to search for models, pass in a regexp as the argument. =cut -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 ); - } - - if (ref $c) { - return $c->stash->{current_model_instance} - if $c->stash->{current_model_instance}; - return $c->model( $c->stash->{current_model} ) - if $c->stash->{current_model}; - } - return $c->model( $appclass->config->{default_model} ) - if $appclass->config->{default_model}; - - my( $comp, $rest ) = $c->_comp_search_prefixes( undef, qw/Model M/); - - if( $rest ) { - $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.81, the "random" behavior will not work at all.' ); - } - - return $c->_filter_component( $comp ); -} - +sub model { shift->_lookup_mvc('model', @_) } =head2 $c->view($name) @@ -749,46 +621,23 @@ If you want to search for views, pass in a regexp as the argument. =cut -sub view { - my ( $c, $name, @args ) = @_; +sub view { shift->_lookup_mvc('view', @_) } - 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 ); - } +sub _lookup_mvc { + my ( $c, $type, $name, @args ) = @_; - if (ref $c) { - return $c->stash->{current_view_instance} - if $c->stash->{current_view_instance}; - return $c->view( $c->stash->{current_view} ) - if $c->stash->{current_view}; - } - return $c->view( $appclass->config->{default_view} ) - if $appclass->config->{default_view}; - - my( $comp, $rest ) = $c->_comp_search_prefixes( undef, qw/View V/); + if (ref $c && !$name) { + my $current_instance = $c->stash->{"current_${type}_instance"}; + return $current_instance + if $current_instance && $type ne 'controller'; - if( $rest ) { - $c->log->warn( 'Calling $c->view() will return a random view unless you specify one of:' ); - $c->log->warn( '* $c->config(default_view => "the name of the default view to use")' ); - $c->log->warn( '* $c->stash->{current_view} # the name of the view to use for this request' ); - $c->log->warn( '* $c->stash->{current_view_instance} # the instance of the view to use for this request' ); - $c->log->warn( 'NB: in version 5.81, the "random" behavior will not work at all.' ); + $name = $type eq 'controller' + ? Catalyst::Utils::class2classshortsuffix($c->action->class) + : $c->stash->{"current_${type}"} + ; } - return $c->_filter_component( $comp ); + return $c->container->get_component_from_sub_container($type, $name, $c, @args); } =head2 $c->controllers @@ -799,7 +648,7 @@ Returns the available names which can be passed to $c->controller sub controllers { my ( $c ) = @_; - return $c->_comp_names(qw/Controller C/); + return $c->container->get_sub_container('controller')->get_service_list; } =head2 $c->models @@ -810,7 +659,7 @@ Returns the available names which can be passed to $c->model sub models { my ( $c ) = @_; - return $c->_comp_names(qw/Model M/); + return $c->container->get_sub_container('model')->get_service_list; } @@ -822,7 +671,7 @@ Returns the available names which can be passed to $c->view sub views { my ( $c ) = @_; - return $c->_comp_names(qw/View V/); + return $c->container->get_sub_container('view')->get_service_list; } =head2 $c->comp($name) @@ -837,57 +686,50 @@ 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 { - my ( $c, $name, @args ) = @_; + my ( $c, $component, @args ) = @_; - if( $name ) { - my $comps = $c->components; + unless ($component) { + $c->log->warn('Calling $c->component with no args is deprecated and '); + $c->log->warn('will be removed in a future release.'); + $c->log->warn('Use $c->component_list instead.'); + return $c->component_list; + } - if( !ref $name ) { - # is it the exact name? - return $c->_filter_component( $comps->{ $name }, @args ) - if exists $comps->{ $name }; + my @result = $c->container->find_component( $component, $c, @args ); - # perhaps we just omitted "MyApp"? - my $composed = ( ref $c || $c ) . "::${name}"; - return $c->_filter_component( $comps->{ $composed }, @args ) - if exists $comps->{ $composed }; + # list context for regexp searches + return @result if ref $component; - # search all of the models, views and controllers - my( $comp ) = $c->_comp_search_prefixes( $name, qw/Model M Controller C View V/ ); - return $c->_filter_component( $comp, @args ) if $comp; - } + # only one component (if it's found) for string searches + return shift @result if @result; + + if (ref $c eq $component) { + $c->log->warn('You are calling $c->comp("MyApp"). This behaviour is'); + $c->log->warn('deprecated, and will be removed in a future release.'); + return $c; + } - return - if $c->config->{disable_component_resolution_regex_fallback}; + $c->log->warn("Looking for '$component', but nothing was found."); - # This is here so $c->comp( '::M::' ) works - my $query = ref $name ? $name : qr{$name}i; + # I would expect to return an empty list here, but that breaks back-compat + $c->log->warn('Component not found, returning the list of existing'); + $c->log->warn('components. This behavior is deprecated and will be'); + $c->log->warn('removed in a future release. Use $c->component_list'); + $c->log->warn('instead.'); - my @result = grep { m{$query} } keys %{ $c->components }; - return map { $c->_filter_component( $_, @args ) } @result if ref $name; + return $c->component_list; +} - 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 $c->_filter_component( $result[ 0 ], @args ); - } +=head2 $c->component_list - # I would expect to return an empty list here, but that breaks back-compat - } +Returns the sorted list of the component names of the application. - # fallback - return sort keys %{ $c->components }; -} +=cut + +sub component_list { sort keys %{ shift->components } } =head2 CLASS DATA AND HELPER CLASSES @@ -1035,26 +877,12 @@ sub path_to { else { return Path::Class::File->new( $c->config->{home}, @path ) } } -=head2 $c->plugin( $name, $class, @args ) - -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.81. -Please do not use this functionality in new code. - -=cut - sub plugin { my ( $class, $name, $plugin, @args ) = @_; + # See block comment in t/aggregate/unit_core_plugin.t # 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->log->warn(qq/Adding plugin using the ->plugin method is deprecated, and will be removed in a future release/); $class->_register_plugin( $plugin, 1 ); @@ -1083,6 +911,9 @@ Catalyst> line. MyApp->setup; MyApp->setup( qw/-Debug/ ); +B You B wrap this method with method modifiers +or bad things will happen - wrap the C method instead. + =cut sub setup { @@ -1117,6 +948,7 @@ sub setup { } } + $class->setup_config(); $class->setup_home( delete $flags->{home} ); $class->setup_log( delete $flags->{log} ); @@ -1184,25 +1016,17 @@ EOF $class->setup unless $Catalyst::__AM_RESTARTING; } - # Initialize our data structure - $class->components( {} ); - $class->setup_components; - if ( $class->debug ) { + if ( + $class->debug and + my $comps = $class->container->get_all_components($class) + ) { 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 ); - } - $class->log->debug( "Loaded components:\n" . $t->draw . "\n" ) - if ( keys %{ $class->components } ); - } + $t->row( $_ => ref($comps->{$_}) ? 'instance' : 'class' ) for keys %$comps; - # Add our self to components, since we are also a component - if( $class->isa('Catalyst::Controller') ){ - $class->components->{$class} = $class; + $class->log->debug( "Loaded components:\n" . $t->draw . "\n" ); } $class->setup_actions; @@ -1212,29 +1036,6 @@ EOF $class->log->info("$name powered by Catalyst $Catalyst::VERSION"); } - # Make sure that the application class becomes immutable at this point, - B::Hooks::EndOfScope::on_scope_end { - return if $@; - my $meta = Class::MOP::get_metaclass_by_name($class); - if ( - $meta->is_immutable - && ! { $meta->immutable_options }->{replace_constructor} - && ( - $class->isa('Class::Accessor::Fast') - || $class->isa('Class::Accessor') - ) - ) { - warn "You made your application class ($class) immutable, " - . "but did not inline the\nconstructor. " - . "This will break catalyst, as your app \@ISA " - . "Class::Accessor(::Fast)?\nPlease pass " - . "(replace_constructor => 1)\nwhen making your class immutable.\n"; - } - $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."); @@ -1317,7 +1118,7 @@ path, use C<< $c->uri_for_action >> instead. sub uri_for { my ( $c, $path, @args ) = @_; - if (blessed($path) && $path->isa('Catalyst::Controller')) { + if ( $path->$_isa('Catalyst::Controller') ) { $path = $path->path_prefix; $path =~ s{/+\z}{}; $path .= '/'; @@ -1334,7 +1135,7 @@ sub uri_for { $arg =~ s/([^$URI::uric])/$URI::Escape::escapes{$1}/go; } - if ( blessed($path) ) { # action object + if ( $path->$_isa('Catalyst::Action') ) { # action object s|/|%2F|g for @args; my $captures = [ map { s|/|%2F|g; $_; } ( scalar @args && ref $args[0] eq 'ARRAY' @@ -1347,7 +1148,15 @@ sub uri_for { } my $action = $path; - $path = $c->dispatcher->uri_for_action($action, $captures); + # ->uri_for( $action, \@captures_and_args, \%query_values? ) + if( !@args && $action->number_of_args ) { + my $expanded_action = $c->dispatcher->expand_action( $action ); + + my $num_captures = $expanded_action->number_of_captures; + unshift @args, splice @$captures, $num_captures; + } + + $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; @@ -1398,9 +1207,9 @@ sub uri_for { $res; } -=head2 $c->uri_for_action( $path, \@captures?, @args?, \%query_values? ) +=head2 $c->uri_for_action( $path, \@captures_and_args?, @args?, \%query_values? ) -=head2 $c->uri_for_action( $action, \@captures?, @args?, \%query_values? ) +=head2 $c->uri_for_action( $action, \@captures_and_args?, @args?, \%query_values? ) =over @@ -1429,6 +1238,31 @@ You can use: and it will create the URI /users/the-list. +=item \@captures_and_args? + +Optional array reference of Captures (i.e. C<req->captures>) +and arguments to the request. Usually used with L +to interpolate all the parameters in the URI. + +=item @args? + +Optional list of extra arguments - can be supplied in the +C<< \@captures_and_args? >> array ref, or here - whichever is easier for your +code. + +Your action can have zero, a fixed or a variable number of args (e.g. +C<< Args(1) >> for a fixed number or C<< Args() >> for a variable number).. + +=item \%query_values? + +Optional array reference of query parameters to append. E.g. + + { foo => 'bar' } + +will generate + + /rest/of/your/uri?foo=bar + =back =cut @@ -1548,18 +1382,18 @@ sub welcome_message { We do, however, provide you with a few starting points.

If you want to jump right into web development with Catalyst you might want to start with a tutorial.

-
perldoc Catalyst::Manual::Tutorial
+
perldoc Catalyst::Manual::Tutorial
 

Afterwards you can go on to check out a more complete look at our features.

-perldoc Catalyst::Manual::Intro
+perldoc Catalyst::Manual::Intro
 
 

What to do next?

Next it's time to write an actual application. Use the - helper scripts to generate controllers, - models, and - views; + helper scripts to generate controllers, + models, and + views; they can save you a lot of work.

script/${prefix}_create.pl --help

Also, be sure to check out the vast and growing @@ -1592,6 +1426,16 @@ sub welcome_message { EOF } +=head2 run_options + +Contains a hash of options passed from the application script, including +the original ARGV the script received, the processed values from that +ARGV and any extra arguments to the script which were not processed. + +This can be used to add custom options to your application's scripts +and setup your application differently depending on the values of these +options. + =head1 INTERNAL METHODS These methods are not meant to be used by end users. @@ -1600,6 +1444,23 @@ These methods are not meant to be used by end users. Returns a hash of components. +=cut + +sub components { + my ( $class, $comps ) = @_; + + # people create components calling this sub directly, before setup + $class->setup_config unless $class->container; + + my $container = $class->container; + + if ( $comps ) { + $container->add_component( $_ ) for keys %$comps; + } + + return $container->get_all_components($class); +} + =head2 $c->context_class Returns or sets the context class. @@ -1796,7 +1657,7 @@ sub finalize { $c->finalize_error; } - $c->finalize_headers; + $c->finalize_headers unless $c->response->finalized_headers; # HEAD request if ( $c->request->method eq 'HEAD' ) { @@ -1863,9 +1724,17 @@ sub finalize_headers { if ( !$response->has_body ) { # Add a default body if none is already present - $response->body( - qq{

This item has moved here.

} - ); + $response->body(<<"EOF"); + + + + Moved + + +

This item has moved here.

+ + +EOF $response->content_type('text/html; charset=utf-8'); } } @@ -1898,7 +1767,7 @@ sub finalize_headers { $c->finalize_cookies; - $c->engine->finalize_headers( $c, @_ ); + $c->response->finalize_headers(); # Done $response->finalized_headers(1); @@ -1977,13 +1846,18 @@ sub handle_request { return $status; } -=head2 $c->prepare( @arguments ) +=head2 $class->prepare( @arguments ) Creates a Catalyst context from an engine-specific request (Apache, CGI, etc.). =cut +has _uploadtmp => ( + is => 'ro', + predicate => '_has_uploadtmp', +); + sub prepare { my ( $class, @arguments ) = @_; @@ -1992,10 +1866,9 @@ sub prepare { # into the application. $class->context_class( ref $class || $class ) unless $class->context_class; - my $c = $class->context_class->new({}); + my $uploadtmp = $class->config->{uploadtmp}; + my $c = $class->context_class->new({ $uploadtmp ? (_uploadtmp => $uploadtmp) : ()}); - # For on-demand data - $c->request->_context($c); $c->response->_context($c); #surely this is not the most efficient way to do things... @@ -2013,8 +1886,8 @@ sub prepare { $c->prepare_request(@arguments); $c->prepare_connection; $c->prepare_query_parameters; - $c->prepare_headers; - $c->prepare_cookies; + $c->prepare_headers; # Just hooks, no longer needed - they just + $c->prepare_cookies; # cause the lazy attribute on req to build $c->prepare_path; # Prepare the body for reading, either by prepare_body @@ -2026,6 +1899,7 @@ sub prepare { $c->prepare_body; } } + $c->prepare_action; } # VERY ugly and probably shouldn't rely on ->finalize actually working catch { @@ -2033,19 +1907,19 @@ sub prepare { $c->response->status(400); $c->response->content_type('text/plain'); $c->response->body('Bad Request'); + # Note we call finalize and then die here, which escapes + # finalize being called in the enclosing block.. + # It in fact couldn't be called, as we don't return $c.. + # This is a mess - but I'm unsure you can fix this without + # breaking compat for people doing crazy things (we should set + # the 400 and just return the ctx here IMO, letting finalize get called + # above... $c->finalize; die $_; }; - my $method = $c->req->method || ''; - my $path = $c->req->path; - $path = '/' unless length $path; - my $address = $c->req->address || ''; - $c->log_request; - $c->prepare_action; - return $c; } @@ -2106,24 +1980,28 @@ Prepares connection. sub prepare_connection { my $c = shift; - $c->engine->prepare_connection( $c, @_ ); + # XXX - This is called on the engine (not the request) to maintain + # Engine::PSGI back compat. + $c->engine->prepare_connection($c); } =head2 $c->prepare_cookies -Prepares cookies. +Prepares cookies by ensuring that the attribute on the request +object has been built. =cut -sub prepare_cookies { my $c = shift; $c->engine->prepare_cookies( $c, @_ ) } +sub prepare_cookies { my $c = shift; $c->request->cookies } =head2 $c->prepare_headers -Prepares headers. +Prepares request headers by ensuring that the attribute on the request +object has been built. =cut -sub prepare_headers { my $c = shift; $c->engine->prepare_headers( $c, @_ ) } +sub prepare_headers { my $c = shift; $c->request->headers } =head2 $c->prepare_parameters @@ -2250,7 +2128,7 @@ sub log_response_status_line { =head2 $c->log_response_headers($headers); -Hook method which can be wrapped by plugins to log the responseheaders. +Hook method which can be wrapped by plugins to log the response headers. No-op in the default implementation. =cut @@ -2406,7 +2284,7 @@ $c->request. You must handle all body parsing yourself. =cut -sub read { my $c = shift; return $c->engine->read( $c, @_ ) } +sub read { my $c = shift; return $c->request->read( @_ ) } =head2 $c->run @@ -2416,11 +2294,35 @@ Starts the engine. sub run { my $app = shift; + $app->_make_immutable_if_needed; $app->engine_loader->needs_psgi_engine_compat_hack ? $app->engine->run($app, @_) : $app->engine->run( $app, $app->_finalized_psgi_app, @_ ); } +sub _make_immutable_if_needed { + my $class = shift; + my $meta = Class::MOP::get_metaclass_by_name($class); + my $isa_ca = $class->isa('Class::Accessor::Fast') || $class->isa('Class::Accessor'); + if ( + $meta->is_immutable + && ! { $meta->immutable_options }->{replace_constructor} + && $isa_ca + ) { + warn("You made your application class ($class) immutable, " + . "but did not inline the\nconstructor. " + . "This will break catalyst, as your app \@ISA " + . "Class::Accessor(::Fast)?\nPlease pass " + . "(replace_constructor => 1)\nwhen making your class immutable.\n"); + } + unless ($meta->is_immutable) { + # XXX - FIXME warning here as you should make your app immutable yourself. + $meta->make_immutable( + replace_constructor => 1, + ); + } +} + =head2 $c->set_action( $action, $code, $namespace, $attrs ) Sets an action in a given namespace. @@ -2437,137 +2339,67 @@ Sets up actions for a component. sub setup_actions { my $c = shift; $c->dispatcher->setup_actions( $c, @_ ) } -=head2 $c->setup_components - -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. - -Installation of each component is performed by the L method, -below. +=head2 $c->setup_config =cut -sub setup_components { +sub setup_config { my $class = shift; - my $config = $class->config->{ setup_components }; - - my @comps = $class->locate_components($config); - my %comps = map { $_ => 1 } @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 $deprecatedcatalyst_component_names; - - for my $component ( @comps ) { + my %args = %{ $class->config || {} }; - # 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 + my $container_class; - Catalyst::Utils::ensure_class_loaded( $component, { ignore_loaded => 1 } ); + if ( exists $args{container_class} ) { + $container_class = delete $args{container_class}; + Class::MOP::load_class($container_class); } - - 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); - } + else { + $container_class = Class::MOP::load_first_existing_class("${class}::Container", 'Catalyst::IOC::Container'); } -} -=head2 $c->locate_components( $setup_component_config ) + my $container = $container_class->new( %args, name => $class ); + $class->container($container); -This method is meant to provide a list of component modules that should be -setup for the application. By default, it will use L. + my $config = $container->resolve( service => 'config' ); + $class->config($config); + $class->finalize_config; # back-compat +} -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. +=head2 $c->finalize_config =cut -sub locate_components { - my $class = shift; - my $config = shift; - - my @paths = qw( ::Controller ::C ::Model ::M ::View ::V ); - my $extra = delete $config->{ search_extra } || []; +sub finalize_config { } - 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; +=head2 $c->setup_components - return @comps; -} +This method is called internally to set up the application's components. -=head2 $c->expand_component_module( $component, $setup_component_config ) +It finds modules by calling the L method, expands them to +package names with the $container->expand_component_module method, and then +installs each component into the application. -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. +The C config option is passed to both of the above methods. =cut -sub expand_component_module { - my ($class, $module) = @_; - return Devel::InnerPackage::list_packages( $module ); -} +sub setup_components { shift->container->setup_components } -=head2 $c->setup_component +=head2 locate_components =cut -sub setup_component { - my( $class, $component ) = @_; - - unless ( $component->can( 'COMPONENT' ) ) { - return $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 ); }; +sub locate_components { + my $class = shift; - if ( my $error = $@ ) { - chomp $error; - Catalyst::Exception->throw( - message => qq/Couldn't instantiate component "$component", "$error"/ - ); - } + $class->log->warn('The locate_components method has been deprecated.'); + $class->log->warn('Please read Catalyst::IOC::Container documentation to'); + $class->log->warn('update your application.'); - unless (blessed $instance) { - my $metaclass = Moose::Util::find_meta($component); - my $method_meta = $metaclass->find_method_by_name('COMPONENT'); - my $component_method_from = $method_meta->associated_metaclass->name; - my $value = defined($instance) ? $instance : 'undef'; - Catalyst::Exception->throw( - message => - qq/Couldn't instantiate component "$component", COMPONENT() method (from $component_method_from) didn't return an object-like value (value was $value)./ - ); - } - return $instance; + # XXX think about ditching this sort entirely + return sort { length $a <=> length $b } + @{ $class->container->resolve( service => 'locate_components' ) }; } =head2 $c->setup_dispatcher @@ -2611,17 +2443,32 @@ sub engine_class { Catalyst::EngineLoader->new({ application_name => $class, (defined $requested_engine - ? (requested_engine => $requested_engine) : ()), + ? (catalyst_engine_class => $requested_engine) : ()), }), ); } + $class->engine_loader->catalyst_engine_class; } sub setup_engine { my ($class, $requested_engine) = @_; - my $engine = $class->engine_class($requested_engine); + my $engine = do { + my $loader = $class->engine_loader; + + if (!$loader || $requested_engine) { + $loader = Catalyst::EngineLoader->new({ + application_name => $class, + (defined $requested_engine + ? (requested_engine => $requested_engine) : ()), + }), + + $class->engine_loader($loader); + } + + $loader->catalyst_engine_class; + }; # Don't really setup_engine -- see _setup_psgi_app for explanation. return if $class->loading_psgi_file; @@ -2638,7 +2485,7 @@ sub setup_engine { $meta->add_method(handler => sub { my $r = shift; - my $psgi_app = $class->psgi_app; + my $psgi_app = $class->_finalized_psgi_app; $apache->call_app($r, $psgi_app); }); @@ -2727,25 +2574,34 @@ sub apply_default_middlewares { }, ); - my $server_matches = sub { - my ($re) = @_; - return sub { - my ($env) = @_; - my $server = $env->{SERVER_SOFTWARE}; - return unless $server; - return $server =~ $re ? 1 : 0; - }; - }; - # If we're running under Lighttpd, swap PATH_INFO and SCRIPT_NAME # http://lists.scsys.co.uk/pipermail/catalyst/2006-June/008361.html - $psgi_app = Plack::Middleware::LighttpdScriptNameFix->wrap($psgi_app); + $psgi_app = Plack::Middleware::Conditional->wrap( + $psgi_app, + builder => sub { Plack::Middleware::LighttpdScriptNameFix->wrap($_[0]) }, + condition => sub { + my ($env) = @_; + return unless $env->{SERVER_SOFTWARE} && $env->{SERVER_SOFTWARE} =~ m!lighttpd[-/]1\.(\d+\.\d+)!; + return unless $1 < 4.23; + 1; + }, + ); # we're applying this unconditionally as the middleware itself already makes # sure it doesn't fuck things up if it's not running under one of the right # IIS versions $psgi_app = Plack::Middleware::IIS6ScriptNameFix->wrap($psgi_app); + # And another IIS issue, this time with IIS7. + $psgi_app = Plack::Middleware::Conditional->wrap( + $psgi_app, + builder => sub { Plack::Middleware::IIS7KeepAliveFix->wrap($_[0]) }, + condition => sub { + my ($env) = @_; + return $env->{SERVER_SOFTWARE} && $env->{SERVER_SOFTWARE} =~ m!IIS/7\.[0-9]!; + }, + ); + return $psgi_app; } @@ -2772,18 +2628,19 @@ Sets up the home directory. =cut sub setup_home { - my ( $class, $home ) = @_; + my ( $class, $home_flag ) = @_; - if ( my $env = Catalyst::Utils::env_value( $class, 'HOME' ) ) { - $home = $env; - } - - $home ||= Catalyst::Utils::home($class); + my $home = $class->container->resolve( + service => 'home', + parameters => { + home_flag => $home_flag + }, + ); 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'); + $class->config->{root} ||= $class->container->resolve(service => 'root_dir'); } } @@ -2855,7 +2712,7 @@ sub setup_stats { =head2 $c->registered_plugins Returns a sorted list of the plugins which have either been stated in the -import list or which have been added via C<< MyApp->plugin(@args); >>. +import list. If passed a given plugin name, it will report a boolean value indicating whether or not that plugin is loaded. A fully qualified name is required if @@ -2884,11 +2741,16 @@ the plugin name does not begin with C. Class::MOP::load_class( $plugin ); $class->log->warn( "$plugin inherits from 'Catalyst::Component' - this is deprecated and will not work in 5.81" ) if $plugin->isa( 'Catalyst::Component' ); - $proto->_plugins->{$plugin} = 1; - unless ($instant) { + my $plugin_meta = Moose::Meta::Class->create($plugin); + if (!$plugin_meta->has_method('new') + && ( $plugin->isa('Class::Accessor::Fast') || $plugin->isa('Class::Accessor') ) ) { + $plugin_meta->add_method('new', Moose::Object->meta->get_method('new')) + } + if (!$instant && !$proto->_plugins->{$plugin}) { my $meta = Class::MOP::get_metaclass_by_name($class); $meta->superclasses($plugin, $meta->superclasses); } + $proto->_plugins->{$plugin} = 1; return $class; } @@ -2970,10 +2832,10 @@ your output data, if known. sub write { my $c = shift; - # Finalize headers if someone manually writes output + # Finalize headers if someone manually writes output (for compat) $c->finalize_headers; - return $c->engine->write( $c, @_ ); + return $c->response->write( @_ ); } =head2 version @@ -3001,14 +2863,6 @@ C - The default view to be rendered or returned when C<< $c->view =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 >>. @@ -3026,7 +2880,7 @@ welcome screens 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 +the upload) before actually receiving all the data. See L =item * @@ -3036,18 +2890,12 @@ 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 +C - Controls if the C or C environment variable should be used for determining the request path. Most web server environments pass the requested path to the application using environment variables, @@ -3062,7 +2910,7 @@ is determined by the C<< $c->config(use_request_uri_for_path) >> setting (which =item use_request_uri_for_path => 0 This is the default (and the) traditional method that Catalyst has used for determining the path information. -The path is synthesised from a combination of the C and C environment variables. +The path is generated from a combination of the C and C environment variables. The allows the application to behave correctly when C is being used to redirect requests into the application, as these variables are adjusted by mod_rewrite to take account for the redirect. @@ -3153,7 +3001,26 @@ headers. If you do not wish to use the proxy support at all, you may set: - MyApp->config(ignore_frontend_proxy => 1); + MyApp->config(ignore_frontend_proxy => 0); + +=head2 Note about psgi files + +Note that if you supply your own .psgi file, calling +C<< MyApp->psgi_app(@_); >>, then B. + +You either need to apply L yourself +in your psgi, for example: + + builder { + enable "Plack::Middleware::ReverseProxy"; + MyApp->psgi_app + }; + +This will unconditionally add the ReverseProxy support, or you need to call +C<< $app = MyApp->apply_default_middlewares($app) >> (to conditionally +apply the support depending upon your config). + +See L for more information. =head1 THREAD SAFETY @@ -3220,6 +3087,8 @@ Andrew Ford EA.Ford@ford-mason.co.ukE Andrew Ruthven +André Walker + andyg: Andy Grundman audreyt: Audrey Tang