X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FCatalyst%2FIOC%2FContainer.pm;h=742a5eb8e89e8f8cee7354ccfbb4982cc22a500e;hb=55f417ad0baa0cdf22d48e334e8d8754bade331e;hp=94a2fec050cce97c252e35a52edd332e2aba129e;hpb=d3742403a25c58e77fb45e7ec6c7765da91cb306;p=catagits%2FCatalyst-Runtime.git diff --git a/lib/Catalyst/IOC/Container.pm b/lib/Catalyst/IOC/Container.pm index 94a2fec..742a5eb 100644 --- a/lib/Catalyst/IOC/Container.pm +++ b/lib/Catalyst/IOC/Container.pm @@ -8,7 +8,8 @@ use Devel::InnerPackage (); use Hash::Util qw/lock_hash/; use MooseX::Types::LoadableClass qw/ LoadableClass /; use Moose::Util; -use Catalyst::IOC::BlockInjection; +use Catalyst::IOC::ConstructorInjection; +use Module::Pluggable::Object (); use namespace::autoclean; extends 'Bread::Board::Container'; @@ -37,10 +38,10 @@ has substitutions => ( default => sub { +{} }, ); -has name => ( - is => 'ro', - isa => 'Str', - default => 'MyApp', +has application_name => ( + is => 'ro', + isa => 'Str', + required => 1, ); has sub_container_class => ( @@ -62,7 +63,7 @@ sub BUILD { substitutions file driver - name + application_name prefix extensions path @@ -72,28 +73,27 @@ sub BUILD { local_files global_config local_config + class_config config_local_suffix config_path + locate_components /; + my $config = $self->resolve( service => 'config' ); + $self->add_sub_container( $self->build_controller_subcontainer ); - # FIXME - the config should be merged at this point - my $config = $self->resolve( service => 'config' ); - my $default_view = $params->{default_view} || $config->{default_view}; - my $default_model = $params->{default_model} || $config->{default_model}; - $self->add_sub_container( $self->build_view_subcontainer( - default_component => $default_view, + default_component => $config->{default_view}, ) ); $self->add_sub_container( $self->build_model_subcontainer( - default_component => $default_model, + default_component => $config->{default_model}, ) ); } @@ -122,10 +122,10 @@ sub build_controller_subcontainer { ); } -sub build_name_service { +sub build_application_name_service { my $self = shift; - return Bread::Board::Literal->new( name => 'name', value => $self->name ); + return Bread::Board::Literal->new( name => 'application_name', value => $self->application_name ); } sub build_driver_service { @@ -150,6 +150,7 @@ sub build_extensions_service { my $self = shift; return Bread::Board::BlockInjection->new( + lifecycle => 'Singleton', name => 'extensions', block => sub { return \@{Config::Any->extensions}; @@ -161,11 +162,12 @@ sub build_prefix_service { my $self = shift; return Bread::Board::BlockInjection->new( + lifecycle => 'Singleton', name => 'prefix', block => sub { - return Catalyst::Utils::appprefix( shift->param('name') ); + return Catalyst::Utils::appprefix( shift->param('application_name') ); }, - dependencies => [ depends_on('name') ], + dependencies => [ depends_on('application_name') ], ); } @@ -173,15 +175,16 @@ sub build_path_service { my $self = shift; return Bread::Board::BlockInjection->new( + lifecycle => 'Singleton', name => 'path', block => sub { my $s = shift; - return Catalyst::Utils::env_value( $s->param('name'), 'CONFIG' ) + return Catalyst::Utils::env_value( $s->param('application_name'), 'CONFIG' ) || $s->param('file') - || $s->param('name')->path_to( $s->param('prefix') ); + || $s->param('application_name')->path_to( $s->param('prefix') ); }, - dependencies => [ depends_on('file'), depends_on('name'), depends_on('prefix') ], + dependencies => [ depends_on('file'), depends_on('application_name'), depends_on('prefix') ], ); } @@ -189,6 +192,7 @@ sub build_config_service { my $self = shift; return Bread::Board::BlockInjection->new( + lifecycle => 'Singleton', name => 'config', block => sub { my $s = shift; @@ -196,13 +200,13 @@ sub build_config_service { my $v = Data::Visitor::Callback->new( plain_value => sub { return unless defined $_; - return $self->_config_substitutions( $s->param('name'), $s->param('substitutions'), $_ ); + return $self->_config_substitutions( $s->param('application_name'), $s->param('substitutions'), $_ ); } ); $v->visit( $s->param('raw_config') ); }, - dependencies => [ depends_on('name'), depends_on('raw_config'), depends_on('substitutions') ], + dependencies => [ depends_on('application_name'), depends_on('raw_config'), depends_on('substitutions') ], ); } @@ -210,6 +214,7 @@ sub build_raw_config_service { my $self = shift; return Bread::Board::BlockInjection->new( + lifecycle => 'Singleton', name => 'raw_config', block => sub { my $s = shift; @@ -217,15 +222,17 @@ sub build_raw_config_service { my @global = @{$s->param('global_config')}; my @locals = @{$s->param('local_config')}; - my $config = {}; + my $config = $s->param('class_config'); + for my $cfg (@global, @locals) { for (keys %$cfg) { $config = Catalyst::Utils::merge_hashes( $config, $cfg->{$_} ); } } + return $config; }, - dependencies => [ depends_on('global_config'), depends_on('local_config') ], + dependencies => [ depends_on('global_config'), depends_on('local_config'), depends_on('class_config') ], ); } @@ -233,6 +240,7 @@ sub build_global_files_service { my $self = shift; return Bread::Board::BlockInjection->new( + lifecycle => 'Singleton', name => 'global_files', block => sub { my $s = shift; @@ -258,6 +266,7 @@ sub build_local_files_service { my $self = shift; return Bread::Board::BlockInjection->new( + lifecycle => 'Singleton', name => 'local_files', block => sub { my $s = shift; @@ -281,10 +290,31 @@ sub build_local_files_service { ); } +sub build_class_config_service { + my $self = shift; + + return Bread::Board::BlockInjection->new( + lifecycle => 'Singleton', + name => 'class_config', + block => sub { + my $s = shift; + my $app = $s->param('application_name'); + + # Container might be called outside Catalyst context + return {} unless Class::MOP::is_class_loaded($app); + + # config might not have been defined + return $app->config || {}; + }, + dependencies => [ depends_on('application_name') ], + ); +} + sub build_global_config_service { my $self = shift; return Bread::Board::BlockInjection->new( + lifecycle => 'Singleton', name => 'global_config', block => sub { my $s = shift; @@ -304,6 +334,7 @@ sub build_local_config_service { my $self = shift; return Bread::Board::BlockInjection->new( + lifecycle => 'Singleton', name => 'local_config', block => sub { my $s = shift; @@ -323,6 +354,7 @@ sub build_config_path_service { my $self = shift; return Bread::Board::BlockInjection->new( + lifecycle => 'Singleton', name => 'config_path', block => sub { my $s = shift; @@ -347,17 +379,91 @@ sub build_config_local_suffix_service { my $self = shift; return Bread::Board::BlockInjection->new( + lifecycle => 'Singleton', name => 'config_local_suffix', block => sub { my $s = shift; - my $suffix = Catalyst::Utils::env_value( $s->param('name'), 'CONFIG_LOCAL_SUFFIX' ) || $self->config_local_suffix; + my $suffix = Catalyst::Utils::env_value( $s->param('application_name'), 'CONFIG_LOCAL_SUFFIX' ) || $self->config_local_suffix; return $suffix; }, - dependencies => [ depends_on('name') ], + dependencies => [ depends_on('application_name') ], ); } +sub build_locate_components_service { + my $self = shift; + + return Bread::Board::BlockInjection->new( + lifecycle => 'Singleton', + name => 'locate_components', + block => sub { + my $s = shift; + my $class = $s->param('application_name'); + my $config = $s->param('config')->{ setup_components }; + + Catalyst::Exception->throw( + qq{You are using search_extra config option. That option is\n} . + qq{deprecated, please refer to the documentation for\n} . + qq{other ways of achieving the same results.\n} + ) if delete $config->{ search_extra }; + + my @paths = qw( ::Controller ::C ::Model ::M ::View ::V ); + + my $locator = Module::Pluggable::Object->new( + search_path => [ map { s/^(?=::)/$class/; $_; } @paths ], + %$config + ); + + return [ $locator->plugins ]; + }, + dependencies => [ depends_on('application_name'), depends_on('config') ], + ); +} + +sub setup_components { + my $self = shift; + my $class = $self->resolve( service => 'application_name' ); + my @comps = @{ $self->resolve( service => 'locate_components' ) }; + my %comps = map { $_ => 1 } @comps; + my $deprecatedcatalyst_component_names = 0; + + 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 } ); + } + + for my $component (@comps) { + $self->add_component( $component ); + # FIXME - $instance->expand_modules() is broken + my @expanded_components = $self->expand_component_module( $component ); + + if ( + !$deprecatedcatalyst_component_names && + ($deprecatedcatalyst_component_names = $component =~ m/::[CMV]::/) || + ($deprecatedcatalyst_component_names = grep { /::[CMV]::/ } @expanded_components) + ) { + # FIXME - should I be calling warn here? + # Maybe it's time to remove it, or become fatal + $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} + ); + } + + for my $component (@expanded_components) { + $self->add_component( $component ) + unless $comps{$component}; + } + } + + $self->get_sub_container('model')->make_single_default; + $self->get_sub_container('view')->make_single_default; +} + sub _fix_syntax { my $config = shift; my @components = ( @@ -485,22 +591,23 @@ sub find_component_regexp { return @result; } -# FIXME sorry for the name again :) -sub get_components_types { +# FIXME - t0m, how do you feel about this name? +# also, do you think I should draw it here, or just return the data structure? +sub get_components_names_types { my ( $self ) = @_; - my @comps_types; + my @comps_names_types; for my $sub_container_name (qw/model view controller/) { my $sub_container = $self->get_sub_container($sub_container_name); for my $service ( $sub_container->get_service_list ) { - my $comp = $self->resolve(service => $service); + my $comp = $sub_container->resolve(service => $service); my $compname = ref $comp || $comp; my $type = ref $comp ? 'instance' : 'class'; - push @comps_types, [ $compname, $type ]; + push @comps_names_types, [ $compname, $type ]; } } - return @comps_types; + return @comps_names_types; } sub get_all_components { @@ -525,15 +632,30 @@ sub get_all_components { } sub add_component { - my ( $self, $component, $class ) = @_; + my ( $self, $component ) = @_; my ( $type, $name ) = _get_component_type_name($component); return unless $type; $self->get_sub_container($type)->add_service( - Catalyst::IOC::BlockInjection->new( - name => $name, - block => sub { $self->setup_component( $component, $class ) }, + Catalyst::IOC::ConstructorInjection->new( + name => $name, + class => $component, + dependencies => [ + depends_on( '/application_name' ), + depends_on( '/config' ), + ], + parameters => { + suffix => { + isa => 'Str', + default => Catalyst::Utils::class2classsuffix( $component ), + }, + accept_context_args => { + isa => 'ArrayRef|Undef', + required => 0, + default => undef, + }, + }, ) ); } @@ -560,47 +682,6 @@ sub _get_component_type_name { return (undef, $component); } -# FIXME ugly and temporary -# Just moved it here the way it was, so we can work on it here in the container -sub setup_component { - my ( $self, $component, $class ) = @_; - - unless ( $component->can( 'COMPONENT' ) ) { - return $component; - } - - # FIXME I know this isn't the "Dependency Injection" way of doing things, - # its just temporary - my $suffix = Catalyst::Utils::class2classsuffix( $component ); - my $config = $self->resolve(service => '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 ); }; - - if ( my $error = $@ ) { - chomp $error; - Catalyst::Exception->throw( - message => qq/Couldn't instantiate component "$component", "$error"/ - ); - } - elsif (!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; -} - sub expand_component_module { my ( $class, $module ) = @_; return Devel::InnerPackage::list_packages( $module ); @@ -622,7 +703,7 @@ Catalyst::Container - IOC for Catalyst components =head1 METHODS -=head1 Containers +=head1 Building Containers =head2 build_model_subcontainer @@ -636,11 +717,11 @@ Container that stores all views. Container that stores all controllers. -=head1 Services +=head1 Building Services -=head2 build_name_service +=head2 build_application_name_service -Name of the application. +Name of the application (such as MyApp). =head2 build_driver_service @@ -652,28 +733,83 @@ Config options passed directly to the driver being used. =head2 build_substitutions_service -Executes all the substitutions in config. See L method. +This method substitutes macros found with calls to a function. There are a +number of default macros: + +=over + +=item * C<__HOME__> - replaced with C<$c-Epath_to('')> + +=item * C<__ENV(foo)__> - replaced with the value of C<$ENV{foo}> + +=item * C<__path_to(foo/bar)__> - replaced with C<$c-Epath_to('foo/bar')> + +=item * C<__literal(__FOO__)__> - leaves __FOO__ alone (allows you to use +C<__DATA__> as a config value, for example) + +=back + +The parameter list is split on comma (C<,>). You can override this method to +do your own string munging, or you can define your own macros in +Cconfig-E{ 'Plugin::ConfigLoader' }-E{ substitutions }>. +Example: + + MyApp->config->{ 'Plugin::ConfigLoader' }->{ substitutions } = { + baz => sub { my $c = shift; qux( @_ ); } + } + +The above will respond to C<__baz(x,y)__> in config strings. =head2 build_extensions_service +Config::Any's available config file extensions (e.g. xml, json, pl, etc). + =head2 build_prefix_service +The prefix, based on the application name, that will be used to lookup the +config files (which will be in the format $prefix.$extension). If the app is +MyApp::Foo, the prefix will be myapp_foo. + =head2 build_path_service +The path to the config file (or environment variable, if defined). + =head2 build_config_service +The resulting configuration for the application, after it has successfully +been loaded, and all substitutions have been made. + =head2 build_raw_config_service +The merge of local_config and global_config hashes, before substitutions. + =head2 build_global_files_service +Gets all files for config that don't have the local_suffix, such as myapp.conf. + =head2 build_local_files_service +Gets all files for config that have the local_suffix, such as myapp_local.conf. + =head2 build_global_config_service +Reads config from global_files. + =head2 build_local_config_service +Reads config from local_files. + +=head2 build_class_config_service + +Reads config set from the application's class attribute config, +i.e. MyApp->config( name => 'MyApp', ... ) + =head2 build_config_path_service +Splits the path to the config file, and returns on array ref containing +the path to the config file minus the extension in the first position, +and the extension in the second. + =head2 build_config_local_suffix_service Determines the suffix of files used to override the main config. By default @@ -695,67 +831,59 @@ For example, if C< $ENV{ MYAPP_CONFIG_LOCAL_SUFFIX }> is set to C, ConfigLoader will try and load C instead of C. +=head2 build_locate_components_service + +This method is meant to provide a list of component modules that should be +setup for the application. By default, it will use L. + +Specify a C config option to pass additional options directly +to L. + +=head1 Other methods + =head2 get_component_from_sub_container($sub_container, $name, $c, @args) -Looks for components in a given subcontainer (such as controller, model or view), and returns the searched component. If $name is undef, it returns the default component (such as default_view, if $sub_container is 'view'). If $name is a regexp, it returns an array of matching components. Otherwise, it looks for the component with name $name. +Looks for components in a given subcontainer (such as controller, model or +view), and returns the searched component. If $name is undef, it returns the +default component (such as default_view, if $sub_container is 'view'). If +$name is a regexp, it returns an array of matching components. Otherwise, it +looks for the component with name $name. + +=head2 get_components_names_types -=head2 get_components_types +Gets all components from all containers and returns them as an array of +arrayrefs containing the component name and the component type (i.e., whether +it's an instance or a class). =head2 get_all_components -Fetches all the components, in each of the sub_containers model, view and controller, and returns a readonly hash. The keys are the class names, and the values are the blessed objects. This is what is returned by $c->components. +Fetches all the components, in each of the sub_containers model, view and +controller, and returns a readonly hash. The keys are the class names, and +the values are the blessed objects. This is what is returned by $c->components. =head2 add_component -Adds a component to the appropriate subcontainer. The subcontainer is guessed by the component name given. +Adds a component to the appropriate subcontainer. The subcontainer is guessed +by the component name given. =head2 find_component -Searches for components in all containers. If $component is the full class name, the subcontainer is guessed, and it gets the searched component in there. Otherwise, it looks for a component with that name in all subcontainers. If $component is a regexp, it calls the method below, find_component_regexp, and matches all components against that regexp. +Searches for components in all containers. If $component is the full class +name, the subcontainer is guessed, and it gets the searched component in there. +Otherwise, it looks for a component with that name in all subcontainers. If +$component is a regexp, it calls the method below, find_component_regexp, +and matches all components against that regexp. =head2 find_component_regexp Finds components that match a given regexp. Used internally, by find_component. -=head2 setup_component - -=head2 _fix_syntax - -=head2 _config_substitutions - -This method substitutes macros found with calls to a function. There are a -number of default macros: - -=over - -=item * C<__HOME__> - replaced with C<$c-Epath_to('')> - -=item * C<__ENV(foo)__> - replaced with the value of C<$ENV{foo}> - -=item * C<__path_to(foo/bar)__> - replaced with C<$c-Epath_to('foo/bar')> - -=item * C<__literal(__FOO__)__> - leaves __FOO__ alone (allows you to use -C<__DATA__> as a config value, for example) - -=back - -The parameter list is split on comma (C<,>). You can override this method to -do your own string munging, or you can define your own macros in -Cconfig-E{ 'Plugin::ConfigLoader' }-E{ substitutions }>. -Example: - - MyApp->config->{ 'Plugin::ConfigLoader' }->{ substitutions } = { - baz => sub { my $c = shift; qux( @_ ); } - } - -The above will respond to C<__baz(x,y)__> in config strings. - -=head2 $c->expand_component_module( $component, $setup_component_config ) +=head2 expand_component_module Components found by C will be passed to this method, which is expected to return a list of component (package) names to be set up. -=cut +=head2 setup_components =head1 AUTHORS