1 package Catalyst::IOC::Container;
5 use Data::Visitor::Callback;
6 use Catalyst::Utils ();
7 use Devel::InnerPackage ();
8 use Hash::Util qw/lock_hash/;
9 use MooseX::Types::LoadableClass qw/ LoadableClass /;
11 use Catalyst::IOC::ConstructorInjection;
12 use Module::Pluggable::Object ();
13 use namespace::autoclean;
15 extends 'Bread::Board::Container';
17 has config_local_suffix => (
26 default => sub { +{} },
35 has substitutions => (
38 default => sub { +{} },
41 has application_name => (
47 has sub_container_class => (
51 default => 'Catalyst::IOC::SubContainer',
53 new_sub_container => 'new',
58 my ( $self, $params ) = @_;
61 $self->${\"build_${_}_service"}
82 my $config = $self->resolve( service => 'config' );
84 $self->add_sub_container(
85 $self->build_controller_subcontainer
88 $self->add_sub_container(
89 $self->build_view_subcontainer(
90 default_component => $config->{default_view},
94 $self->add_sub_container(
95 $self->build_model_subcontainer(
96 default_component => $config->{default_model},
101 sub build_model_subcontainer {
104 return $self->new_sub_container( @_,
109 sub build_view_subcontainer {
112 return $self->new_sub_container( @_,
117 sub build_controller_subcontainer {
120 return $self->new_sub_container(
121 name => 'controller',
125 sub build_application_name_service {
128 return Bread::Board::Literal->new( name => 'application_name', value => $self->application_name );
131 sub build_driver_service {
134 return Bread::Board::Literal->new( name => 'driver', value => $self->driver );
137 sub build_file_service {
140 return Bread::Board::Literal->new( name => 'file', value => $self->file );
143 sub build_substitutions_service {
146 return Bread::Board::Literal->new( name => 'substitutions', value => $self->substitutions );
149 sub build_extensions_service {
152 return Bread::Board::BlockInjection->new(
153 lifecycle => 'Singleton',
154 name => 'extensions',
156 return \@{Config::Any->extensions};
161 sub build_prefix_service {
164 return Bread::Board::BlockInjection->new(
165 lifecycle => 'Singleton',
168 return Catalyst::Utils::appprefix( shift->param('application_name') );
170 dependencies => [ depends_on('application_name') ],
174 sub build_path_service {
177 return Bread::Board::BlockInjection->new(
178 lifecycle => 'Singleton',
183 return Catalyst::Utils::env_value( $s->param('application_name'), 'CONFIG' )
185 || $s->param('application_name')->path_to( $s->param('prefix') );
187 dependencies => [ depends_on('file'), depends_on('application_name'), depends_on('prefix') ],
191 sub build_config_service {
194 return Bread::Board::BlockInjection->new(
195 lifecycle => 'Singleton',
200 my $v = Data::Visitor::Callback->new(
202 return unless defined $_;
203 return $self->_config_substitutions( $s->param('application_name'), $s->param('substitutions'), $_ );
207 $v->visit( $s->param('raw_config') );
209 dependencies => [ depends_on('application_name'), depends_on('raw_config'), depends_on('substitutions') ],
213 sub build_raw_config_service {
216 return Bread::Board::BlockInjection->new(
217 lifecycle => 'Singleton',
218 name => 'raw_config',
222 my @global = @{$s->param('global_config')};
223 my @locals = @{$s->param('local_config')};
225 my $config = $s->param('class_config');
227 for my $cfg (@global, @locals) {
229 $config = Catalyst::Utils::merge_hashes( $config, $cfg->{$_} );
235 dependencies => [ depends_on('global_config'), depends_on('local_config'), depends_on('class_config') ],
239 sub build_global_files_service {
242 return Bread::Board::BlockInjection->new(
243 lifecycle => 'Singleton',
244 name => 'global_files',
248 my ( $path, $extension ) = @{$s->param('config_path')};
250 my @extensions = @{$s->param('extensions')};
254 die "Unable to handle files with the extension '${extension}'" unless grep { $_ eq $extension } @extensions;
257 @files = map { "$path.$_" } @extensions;
261 dependencies => [ depends_on('extensions'), depends_on('config_path') ],
265 sub build_local_files_service {
268 return Bread::Board::BlockInjection->new(
269 lifecycle => 'Singleton',
270 name => 'local_files',
274 my ( $path, $extension ) = @{$s->param('config_path')};
275 my $suffix = $s->param('config_local_suffix');
277 my @extensions = @{$s->param('extensions')};
281 die "Unable to handle files with the extension '${extension}'" unless grep { $_ eq $extension } @extensions;
282 $path =~ s{\.$extension}{_$suffix.$extension};
285 @files = map { "${path}_${suffix}.$_" } @extensions;
289 dependencies => [ depends_on('extensions'), depends_on('config_path'), depends_on('config_local_suffix') ],
293 sub build_class_config_service {
296 return Bread::Board::BlockInjection->new(
297 lifecycle => 'Singleton',
298 name => 'class_config',
301 my $app = $s->param('application_name');
303 # Container might be called outside Catalyst context
304 return {} unless Class::MOP::is_class_loaded($app);
306 # config might not have been defined
307 return $app->config || {};
309 dependencies => [ depends_on('application_name') ],
313 sub build_global_config_service {
316 return Bread::Board::BlockInjection->new(
317 lifecycle => 'Singleton',
318 name => 'global_config',
322 return Config::Any->load_files({
323 files => $s->param('global_files'),
324 filter => \&_fix_syntax,
326 driver_args => $s->param('driver'),
329 dependencies => [ depends_on('global_files') ],
333 sub build_local_config_service {
336 return Bread::Board::BlockInjection->new(
337 lifecycle => 'Singleton',
338 name => 'local_config',
342 return Config::Any->load_files({
343 files => $s->param('local_files'),
344 filter => \&_fix_syntax,
346 driver_args => $s->param('driver'),
349 dependencies => [ depends_on('local_files') ],
353 sub build_config_path_service {
356 return Bread::Board::BlockInjection->new(
357 lifecycle => 'Singleton',
358 name => 'config_path',
362 my $path = $s->param('path');
363 my $prefix = $s->param('prefix');
365 my ( $extension ) = ( $path =~ m{\.(.{1,4})$} );
368 $path =~ s{[\/\\]$}{};
372 return [ $path, $extension ];
374 dependencies => [ depends_on('prefix'), depends_on('path') ],
378 sub build_config_local_suffix_service {
381 return Bread::Board::BlockInjection->new(
382 lifecycle => 'Singleton',
383 name => 'config_local_suffix',
386 my $suffix = Catalyst::Utils::env_value( $s->param('application_name'), 'CONFIG_LOCAL_SUFFIX' ) || $self->config_local_suffix;
390 dependencies => [ depends_on('application_name') ],
394 sub build_locate_components_service {
397 return Bread::Board::BlockInjection->new(
398 lifecycle => 'Singleton',
399 name => 'locate_components',
402 my $class = $s->param('application_name');
403 my $config = $s->param('config')->{ setup_components };
405 Catalyst::Exception->throw(
406 qq{You are using search_extra config option. That option is\n} .
407 qq{deprecated, please refer to the documentation for\n} .
408 qq{other ways of achieving the same results.\n}
409 ) if delete $config->{ search_extra };
411 my @paths = qw( ::Controller ::C ::Model ::M ::View ::V );
413 my $locator = Module::Pluggable::Object->new(
414 search_path => [ map { s/^(?=::)/$class/; $_; } @paths ],
418 return [ $locator->plugins ];
420 dependencies => [ depends_on('application_name'), depends_on('config') ],
424 sub setup_components {
426 my $class = $self->resolve( service => 'application_name' );
427 my @comps = @{ $self->resolve( service => 'locate_components' ) };
428 my %comps = map { $_ => 1 } @comps;
429 my $deprecatedcatalyst_component_names = 0;
431 for my $component ( @comps ) {
433 # We pass ignore_loaded here so that overlay files for (e.g.)
434 # Model::DBI::Schema sub-classes are loaded - if it's in @comps
435 # we know M::P::O found a file on disk so this is safe
437 Catalyst::Utils::ensure_class_loaded( $component, { ignore_loaded => 1 } );
440 for my $component (@comps) {
441 $self->add_component( $component );
442 # FIXME - $instance->expand_modules() is broken
443 my @expanded_components = $self->expand_component_module( $component );
446 !$deprecatedcatalyst_component_names &&
447 ($deprecatedcatalyst_component_names = $component =~ m/::[CMV]::/) ||
448 ($deprecatedcatalyst_component_names = grep { /::[CMV]::/ } @expanded_components)
450 # FIXME - should I be calling warn here?
451 # Maybe it's time to remove it, or become fatal
452 $class->log->warn(qq{Your application is using the deprecated ::[MVC]:: type naming scheme.\n}.
453 qq{Please switch your class names to ::Model::, ::View:: and ::Controller: as appropriate.\n}
457 for my $component (@expanded_components) {
458 $self->add_component( $component )
459 unless $comps{$component};
463 $self->get_sub_container('model')->make_single_default;
464 $self->get_sub_container('view')->make_single_default;
471 prefix => $_ eq 'Component' ? '' : $_ . '::',
472 values => delete $config->{ lc $_ } || delete $config->{ $_ }
474 grep { ref $config->{ lc $_ } || ref $config->{ $_ } }
475 qw( Component Model M View V Controller C Plugin )
478 foreach my $comp ( @components ) {
479 my $prefix = $comp->{ prefix };
480 foreach my $element ( keys %{ $comp->{ values } } ) {
481 $config->{ "$prefix$element" } = $comp->{ values }->{ $element };
486 sub _config_substitutions {
487 my ( $self, $name, $subs, $arg ) = @_;
489 $subs->{ HOME } ||= sub { shift->path_to( '' ); };
493 if (! defined($ENV{$v})) {
494 Catalyst::Exception->throw( message =>
495 "Missing environment variable: $v" );
501 $subs->{ path_to } ||= sub { shift->path_to( @_ ); };
502 $subs->{ literal } ||= sub { return $_[ 1 ]; };
503 my $subsre = join( '|', keys %$subs );
505 $arg =~ s{__($subsre)(?:\((.+?)\))?__}{ $subs->{ $1 }->( $name, $2 ? split( /,/, $2 ) : () ) }eg;
509 sub get_component_from_sub_container {
510 my ( $self, $sub_container_name, $name, $c, @args ) = @_;
512 my $sub_container = $self->get_sub_container( $sub_container_name );
515 my $default = $sub_container->default_component;
517 return $sub_container->get_component( $default, $c, @args )
518 if $default && $sub_container->has_service( $default );
520 # FIXME - should I be calling $c->log->warn here?
521 # this is never a controller, so this is safe
522 $c->log->warn( "Calling \$c->$sub_container_name() is not supported unless you specify one of:" );
523 $c->log->warn( "* \$c->config(default_$sub_container_name => 'the name of the default $sub_container_name to use')" );
524 $c->log->warn( "* \$c->stash->{current_$sub_container_name} # the name of the view to use for this request" );
525 $c->log->warn( "* \$c->stash->{current_${sub_container_name}_instance} # the instance of the $sub_container_name to use for this request" );
530 return $sub_container->get_component_regexp( $name, $c, @args )
533 return $sub_container->get_component( $name, $c, @args )
534 if $sub_container->has_service( $name );
537 "Attempted to use $sub_container_name '$name', " .
538 "but it does not exist"
545 my ( $self, $component, $c, @args ) = @_;
546 my ( $type, $name ) = _get_component_type_name($component);
549 return $self->get_component_from_sub_container(
550 $type, $name, $c, @args
553 my $query = ref $component
558 for my $subcontainer_name (qw/model view controller/) {
559 my $subcontainer = $self->get_sub_container( $subcontainer_name );
560 my @components = $subcontainer->get_service_list;
561 @result = grep { m{$component} } @components;
563 return map { $subcontainer->get_component( $_, $c, @args ) } @result
567 # FIXME - I guess I shouldn't be calling $c->components here
568 # one last search for things like $c->comp(qr/::M::/)
569 @result = $self->find_component_regexp(
570 $c->components, $component, $c, @args
571 ) if !@result and ref $component;
573 # it expects an empty list on failed searches
577 sub find_component_regexp {
578 my ( $self, $components, $component, @args ) = @_;
581 my @components = grep { m{$component} } keys %{ $components };
584 my ($type, $name) = _get_component_type_name($_);
586 push @result, $self->get_component_from_sub_container(
594 # FIXME - t0m, how do you feel about this name?
595 # also, do you think I should draw it here, or just return the data structure?
596 sub get_components_names_types {
598 my @comps_names_types;
600 for my $sub_container_name (qw/model view controller/) {
601 my $sub_container = $self->get_sub_container($sub_container_name);
602 for my $service ( $sub_container->get_service_list ) {
603 my $comp = $self->resolve(service => $service);
604 my $compname = ref $comp || $comp;
605 my $type = ref $comp ? 'instance' : 'class';
606 push @comps_names_types, [ $compname, $type ];
610 return @comps_names_types;
613 sub get_all_components {
618 map { $_ => $self->get_sub_container($_) } qw(model view controller)
621 for my $container (keys %$containers) {
622 for my $component ($containers->{$container}->get_service_list) {
623 my $comp = $containers->{$container}->resolve(
624 service => $component
626 my $comp_name = ref $comp || $comp;
627 $components{$comp_name} = $comp;
631 return lock_hash %components;
635 my ( $self, $component ) = @_;
636 my ( $type, $name ) = _get_component_type_name($component);
640 $self->get_sub_container($type)->add_service(
641 Catalyst::IOC::ConstructorInjection->new(
645 depends_on( '/application_name' ),
646 depends_on( '/config' ),
651 default => Catalyst::Utils::class2classsuffix( $component ),
653 accept_context_args => {
654 isa => 'ArrayRef|Undef',
663 # FIXME: should this sub exist?
664 # should it be moved to Catalyst::Utils,
665 # or replaced by something already existing there?
666 sub _get_component_type_name {
667 my ( $component ) = @_;
669 my @parts = split /::/, $component;
671 while (my $type = shift @parts) {
672 return ('controller', join '::', @parts)
673 if $type =~ /^(c|controller)$/i;
675 return ('model', join '::', @parts)
676 if $type =~ /^(m|model)$/i;
678 return ('view', join '::', @parts)
679 if $type =~ /^(v|view)$/i;
682 return (undef, $component);
685 sub expand_component_module {
686 my ( $class, $module ) = @_;
687 return Devel::InnerPackage::list_packages( $module );
698 Catalyst::Container - IOC for Catalyst components
706 =head1 Building Containers
708 =head2 build_model_subcontainer
710 Container that stores all models.
712 =head2 build_view_subcontainer
714 Container that stores all views.
716 =head2 build_controller_subcontainer
718 Container that stores all controllers.
720 =head1 Building Services
722 =head2 build_application_name_service
724 Name of the application (such as MyApp).
726 =head2 build_driver_service
728 Config options passed directly to the driver being used.
730 =head2 build_file_service
734 =head2 build_substitutions_service
736 This method substitutes macros found with calls to a function. There are a
737 number of default macros:
741 =item * C<__HOME__> - replaced with C<$c-E<gt>path_to('')>
743 =item * C<__ENV(foo)__> - replaced with the value of C<$ENV{foo}>
745 =item * C<__path_to(foo/bar)__> - replaced with C<$c-E<gt>path_to('foo/bar')>
747 =item * C<__literal(__FOO__)__> - leaves __FOO__ alone (allows you to use
748 C<__DATA__> as a config value, for example)
752 The parameter list is split on comma (C<,>). You can override this method to
753 do your own string munging, or you can define your own macros in
754 C<MyApp-E<gt>config-E<gt>{ 'Plugin::ConfigLoader' }-E<gt>{ substitutions }>.
757 MyApp->config->{ 'Plugin::ConfigLoader' }->{ substitutions } = {
758 baz => sub { my $c = shift; qux( @_ ); }
761 The above will respond to C<__baz(x,y)__> in config strings.
763 =head2 build_extensions_service
765 Config::Any's available config file extensions (e.g. xml, json, pl, etc).
767 =head2 build_prefix_service
769 The prefix, based on the application name, that will be used to lookup the
770 config files (which will be in the format $prefix.$extension). If the app is
771 MyApp::Foo, the prefix will be myapp_foo.
773 =head2 build_path_service
775 The path to the config file (or environment variable, if defined).
777 =head2 build_config_service
779 The resulting configuration for the application, after it has successfully
780 been loaded, and all substitutions have been made.
782 =head2 build_raw_config_service
784 The merge of local_config and global_config hashes, before substitutions.
786 =head2 build_global_files_service
788 Gets all files for config that don't have the local_suffix, such as myapp.conf.
790 =head2 build_local_files_service
792 Gets all files for config that have the local_suffix, such as myapp_local.conf.
794 =head2 build_global_config_service
796 Reads config from global_files.
798 =head2 build_local_config_service
800 Reads config from local_files.
802 =head2 build_config_path_service
804 Splits the path to the config file, and returns on array ref containing
805 the path to the config file minus the extension in the first position,
806 and the extension in the second.
808 =head2 build_config_local_suffix_service
810 Determines the suffix of files used to override the main config. By default
811 this value is C<local>, which will load C<myapp_local.conf>. The suffix can
812 be specified in the following order of preference:
816 =item * C<$ENV{ MYAPP_CONFIG_LOCAL_SUFFIX }>
818 =item * C<$ENV{ CATALYST_CONFIG_LOCAL_SUFFIX }>
822 The first one of these values found replaces the default of C<local> in the
823 name of the local config file to be loaded.
825 For example, if C< $ENV{ MYAPP_CONFIG_LOCAL_SUFFIX }> is set to C<testing>,
826 ConfigLoader will try and load C<myapp_testing.conf> instead of
829 =head2 build_locate_components_service
831 This method is meant to provide a list of component modules that should be
832 setup for the application. By default, it will use L<Module::Pluggable>.
834 Specify a C<setup_components> config option to pass additional options directly
835 to L<Module::Pluggable>.
839 =head2 get_component_from_sub_container($sub_container, $name, $c, @args)
841 Looks for components in a given subcontainer (such as controller, model or
842 view), and returns the searched component. If $name is undef, it returns the
843 default component (such as default_view, if $sub_container is 'view'). If
844 $name is a regexp, it returns an array of matching components. Otherwise, it
845 looks for the component with name $name.
847 =head2 get_components_names_types
849 Gets all components from all containers and returns them as an array of
850 arrayrefs containing the component name and the component type (i.e., whether
851 it's an instance or a class).
853 =head2 get_all_components
855 Fetches all the components, in each of the sub_containers model, view and
856 controller, and returns a readonly hash. The keys are the class names, and
857 the values are the blessed objects. This is what is returned by $c->components.
861 Adds a component to the appropriate subcontainer. The subcontainer is guessed
862 by the component name given.
864 =head2 find_component
866 Searches for components in all containers. If $component is the full class
867 name, the subcontainer is guessed, and it gets the searched component in there.
868 Otherwise, it looks for a component with that name in all subcontainers. If
869 $component is a regexp, it calls the method below, find_component_regexp,
870 and matches all components against that regexp.
872 =head2 find_component_regexp
874 Finds components that match a given regexp. Used internally, by find_component.
876 =head2 expand_component_module
878 Components found by C<locate_components> will be passed to this method, which
879 is expected to return a list of component (package) names to be set up.
881 =head2 setup_components
885 Catalyst Contributors, see Catalyst.pm
889 This library is free software. You can redistribute it and/or modify it under
890 the same terms as Perl itself.