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=4fd6ed1cc26ae8413c0bfee81d101c8767af32a8;hb=gsoc_breadboard;hpb=d6ae510491d8cc02625d845d6e08063209d8f193 diff --git a/lib/Catalyst.pm b/lib/Catalyst.pm index 4fd6ed1..9c7dec2 100644 --- a/lib/Catalyst.pm +++ b/lib/Catalyst.pm @@ -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 (); @@ -101,7 +100,7 @@ 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 run_options/; @@ -125,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); @@ -142,9 +143,25 @@ 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 @@ -539,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 = $name->$_isa('Regexp') ? $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 $name->$_isa('Regexp'); - - # 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) @@ -650,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 ( $name->$_isa('Regexp') ) { # 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) @@ -689,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 ( $name->$_isa('Regexp') ) { # 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) @@ -747,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 ( $name->$_isa('Regexp') ) { # 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}; + if (ref $c && !$name) { + my $current_instance = $c->stash->{"current_${type}_instance"}; + return $current_instance + if $current_instance && $type ne 'controller'; - my( $comp, $rest ) = $c->_comp_search_prefixes( undef, qw/View V/); - - 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 @@ -797,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 @@ -808,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; } @@ -820,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) @@ -835,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; - return - if $c->config->{disable_component_resolution_regex_fallback}; + 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; + } - # This is here so $c->comp( '::M::' ) works - my $query = ref $name ? $name : qr{$name}i; + $c->log->warn("Looking for '$component', but nothing was found."); - my @result = grep { m{$query} } keys %{ $c->components }; - return map { $c->_filter_component( $_, @args ) } @result if ref $name; + # 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.'); - 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 ); - } + return $c->component_list; +} - # I would expect to return an empty list here, but that breaks back-compat - } +=head2 $c->component_list - # fallback - return sort keys %{ $c->components }; -} +Returns the sorted list of the component names of the application. + +=cut + +sub component_list { sort keys %{ shift->components } } =head2 CLASS DATA AND HELPER CLASSES @@ -1036,6 +880,7 @@ sub path_to { 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 a future release/); @@ -1103,6 +948,7 @@ sub setup { } } + $class->setup_config(); $class->setup_home( delete $flags->{home} ); $class->setup_log( delete $flags->{log} ); @@ -1170,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; @@ -1606,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. @@ -2484,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 %args = %{ $class->config || {} }; - 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; + my $container_class; - for my $component ( @comps ) { - - # We pass ignore_loaded here so that overlay files for (e.g.) - # Model::DBI::Schema sub-classes are loaded - if it's in @comps - # we know M::P::O found a file on disk so this is safe - - Catalyst::Utils::ensure_class_loaded( $component, { ignore_loaded => 1 } ); + 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 } || []; - - push @paths, @$extra; - - my $locator = Module::Pluggable::Object->new( - search_path => [ map { s/^(?=::)/$class/; $_; } @paths ], - %$config - ); +sub finalize_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 @@ -2843,18 +2628,19 @@ Sets up the home directory. =cut sub setup_home { - my ( $class, $home ) = @_; - - if ( my $env = Catalyst::Utils::env_value( $class, 'HOME' ) ) { - $home = $env; - } + my ( $class, $home_flag ) = @_; - $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'); } } @@ -3077,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 >>. @@ -3112,12 +2890,6 @@ 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. @@ -3315,6 +3087,8 @@ Andrew Ford EA.Ford@ford-mason.co.ukE Andrew Ruthven +André Walker + andyg: Andy Grundman audreyt: Audrey Tang