1 package Catalyst::IOC::Container;
5 use Data::Visitor::Callback;
6 use Catalyst::Utils ();
7 use List::Util qw(first);
8 use Devel::InnerPackage ();
9 use Hash::Util qw/lock_hash/;
10 use MooseX::Types::LoadableClass qw/ LoadableClass /;
12 use Scalar::Util qw/refaddr/;
13 use Catalyst::IOC::BlockInjection;
14 use Catalyst::IOC::ConstructorInjection;
15 use Module::Pluggable::Object ();
16 use namespace::autoclean;
18 extends 'Bread::Board::Container';
20 has config_local_suffix => (
29 default => sub { +{} },
38 has substitutions => (
41 default => sub { +{} },
44 has application_name => (
50 has sub_container_class => (
54 default => 'Catalyst::IOC::SubContainer',
56 new_sub_container => 'new',
61 my ( $self, $params ) = @_;
64 $self->${\"build_${_}_service"}
85 my $config = $self->resolve( service => 'config' );
87 # don't force default_component to be undef if the config wasn't set
88 my @default_view = $config->{default_view}
89 ? ( default_component => $config->{default_view} )
92 my @default_model = $config->{default_model}
93 ? ( default_component => $config->{default_model} )
97 $self->add_sub_container(
98 $self->build_component_subcontainer
101 $self->add_sub_container(
102 $self->build_controller_subcontainer
105 $self->add_sub_container(
106 $self->build_view_subcontainer( @default_view )
109 $self->add_sub_container(
110 $self->build_model_subcontainer( @default_model )
116 my $class = ref $self;
117 ${ $class . '::customise_container' }->($self)
118 if ${ $class . '::customise_container' };
122 sub build_model_subcontainer {
125 return $self->new_sub_container( @_,
130 sub build_view_subcontainer {
133 return $self->new_sub_container( @_,
138 sub build_controller_subcontainer {
141 return $self->new_sub_container(
142 name => 'controller',
146 sub build_component_subcontainer {
149 return Bread::Board::Container->new(
154 sub build_application_name_service {
157 return Bread::Board::Literal->new( name => 'application_name', value => $self->application_name );
160 sub build_driver_service {
163 return Bread::Board::Literal->new( name => 'driver', value => $self->driver );
166 sub build_file_service {
169 return Bread::Board::Literal->new( name => 'file', value => $self->file );
172 sub build_substitutions_service {
175 return Bread::Board::Literal->new( name => 'substitutions', value => $self->substitutions );
178 sub build_extensions_service {
181 return Bread::Board::BlockInjection->new(
182 lifecycle => 'Singleton',
183 name => 'extensions',
185 return \@{Config::Any->extensions};
190 sub build_prefix_service {
193 return Bread::Board::BlockInjection->new(
194 lifecycle => 'Singleton',
197 return Catalyst::Utils::appprefix( shift->param('application_name') );
199 dependencies => [ depends_on('application_name') ],
203 sub build_path_service {
206 return Bread::Board::BlockInjection->new(
207 lifecycle => 'Singleton',
212 return Catalyst::Utils::env_value( $s->param('application_name'), 'CONFIG' )
214 || $s->param('application_name')->path_to( $s->param('prefix') );
216 dependencies => [ depends_on('file'), depends_on('application_name'), depends_on('prefix') ],
220 sub build_config_service {
223 return Bread::Board::BlockInjection->new(
224 lifecycle => 'Singleton',
229 my $v = Data::Visitor::Callback->new(
231 return unless defined $_;
232 return $self->_config_substitutions( $s->param('application_name'), $s->param('substitutions'), $_ );
236 $v->visit( $s->param('raw_config') );
238 dependencies => [ depends_on('application_name'), depends_on('raw_config'), depends_on('substitutions') ],
242 sub build_raw_config_service {
245 return Bread::Board::BlockInjection->new(
246 lifecycle => 'Singleton',
247 name => 'raw_config',
251 my @global = @{$s->param('global_config')};
252 my @locals = @{$s->param('local_config')};
254 my $config = $s->param('class_config');
256 for my $cfg (@global, @locals) {
258 $config = Catalyst::Utils::merge_hashes( $config, $cfg->{$_} );
264 dependencies => [ depends_on('global_config'), depends_on('local_config'), depends_on('class_config') ],
268 sub build_global_files_service {
271 return Bread::Board::BlockInjection->new(
272 lifecycle => 'Singleton',
273 name => 'global_files',
277 my ( $path, $extension ) = @{$s->param('config_path')};
279 my @extensions = @{$s->param('extensions')};
283 die "Unable to handle files with the extension '${extension}'" unless grep { $_ eq $extension } @extensions;
286 @files = map { "$path.$_" } @extensions;
290 dependencies => [ depends_on('extensions'), depends_on('config_path') ],
294 sub build_local_files_service {
297 return Bread::Board::BlockInjection->new(
298 lifecycle => 'Singleton',
299 name => 'local_files',
303 my ( $path, $extension ) = @{$s->param('config_path')};
304 my $suffix = $s->param('config_local_suffix');
306 my @extensions = @{$s->param('extensions')};
310 die "Unable to handle files with the extension '${extension}'" unless grep { $_ eq $extension } @extensions;
311 $path =~ s{\.$extension}{_$suffix.$extension};
314 @files = map { "${path}_${suffix}.$_" } @extensions;
318 dependencies => [ depends_on('extensions'), depends_on('config_path'), depends_on('config_local_suffix') ],
322 sub build_class_config_service {
325 return Bread::Board::BlockInjection->new(
326 lifecycle => 'Singleton',
327 name => 'class_config',
330 my $app = $s->param('application_name');
332 # Container might be called outside Catalyst context
333 return {} unless Class::MOP::is_class_loaded($app);
335 # config might not have been defined
336 return $app->config || {};
338 dependencies => [ depends_on('application_name') ],
342 sub build_global_config_service {
345 return Bread::Board::BlockInjection->new(
346 lifecycle => 'Singleton',
347 name => 'global_config',
351 return Config::Any->load_files({
352 files => $s->param('global_files'),
353 filter => \&_fix_syntax,
355 driver_args => $s->param('driver'),
358 dependencies => [ depends_on('global_files') ],
362 sub build_local_config_service {
365 return Bread::Board::BlockInjection->new(
366 lifecycle => 'Singleton',
367 name => 'local_config',
371 return Config::Any->load_files({
372 files => $s->param('local_files'),
373 filter => \&_fix_syntax,
375 driver_args => $s->param('driver'),
378 dependencies => [ depends_on('local_files') ],
382 sub build_config_path_service {
385 return Bread::Board::BlockInjection->new(
386 lifecycle => 'Singleton',
387 name => 'config_path',
391 my $path = $s->param('path');
392 my $prefix = $s->param('prefix');
394 my ( $extension ) = ( $path =~ m{\.(.{1,4})$} );
397 $path =~ s{[\/\\]$}{};
401 return [ $path, $extension ];
403 dependencies => [ depends_on('prefix'), depends_on('path') ],
407 sub build_config_local_suffix_service {
410 return Bread::Board::BlockInjection->new(
411 lifecycle => 'Singleton',
412 name => 'config_local_suffix',
415 my $suffix = Catalyst::Utils::env_value( $s->param('application_name'), 'CONFIG_LOCAL_SUFFIX' ) || $self->config_local_suffix;
419 dependencies => [ depends_on('application_name') ],
423 sub build_locate_components_service {
426 return Bread::Board::BlockInjection->new(
427 lifecycle => 'Singleton',
428 name => 'locate_components',
431 my $class = $s->param('application_name');
432 my $config = $s->param('config')->{ setup_components };
434 Catalyst::Exception->throw(
435 qq{You are using search_extra config option. That option is\n} .
436 qq{deprecated, please refer to the documentation for\n} .
437 qq{other ways of achieving the same results.\n}
438 ) if delete $config->{ search_extra };
440 my @paths = qw( ::Controller ::C ::Model ::M ::View ::V );
442 my $locator = Module::Pluggable::Object->new(
443 search_path => [ map { s/^(?=::)/$class/; $_; } @paths ],
447 return [ $locator->plugins ];
449 dependencies => [ depends_on('application_name'), depends_on('config') ],
453 sub setup_components {
455 my $class = $self->resolve( service => 'application_name' );
456 my @comps = @{ $self->resolve( service => 'locate_components' ) };
457 my %comps = map { $_ => 1 } @comps;
458 my $deprecatedcatalyst_component_names = 0;
460 my $app_locate_components_addr = refaddr(
461 $class->can('locate_components')
463 my $cat_locate_components_addr = refaddr(
464 Catalyst->can('locate_components')
467 if ($app_locate_components_addr != $cat_locate_components_addr) {
468 # FIXME - why not just say: @comps = $class->locate_components() ?
469 $class->log->warn(qq{You have overridden locate_components. That } .
470 qq{no longer works. Please refer to the documentation to achieve } .
471 qq{similar results.\n}
475 for my $component ( @comps ) {
477 # We pass ignore_loaded here so that overlay files for (e.g.)
478 # Model::DBI::Schema sub-classes are loaded - if it's in @comps
479 # we know M::P::O found a file on disk so this is safe
481 Catalyst::Utils::ensure_class_loaded( $component, { ignore_loaded => 1 } );
484 for my $component (@comps) {
485 $self->add_component( $component );
486 # FIXME - $instance->expand_modules() is broken
487 my @expanded_components = $self->expand_component_module( $component );
490 !$deprecatedcatalyst_component_names &&
491 ($deprecatedcatalyst_component_names = $component =~ m/::[CMV]::/) ||
492 ($deprecatedcatalyst_component_names = grep { /::[CMV]::/ } @expanded_components)
494 # FIXME - should I be calling warn here?
495 # Maybe it's time to remove it, or become fatal
496 $class->log->warn(qq{Your application is using the deprecated ::[MVC]:: type naming scheme.\n}.
497 qq{Please switch your class names to ::Model::, ::View:: and ::Controller: as appropriate.\n}
501 for my $component (@expanded_components) {
502 $self->add_component( $component )
503 unless $comps{$component};
512 prefix => $_ eq 'Component' ? '' : $_ . '::',
513 values => delete $config->{ lc $_ } || delete $config->{ $_ }
515 grep { ref $config->{ lc $_ } || ref $config->{ $_ } }
516 qw( Component Model M View V Controller C Plugin )
519 foreach my $comp ( @components ) {
520 my $prefix = $comp->{ prefix };
521 foreach my $element ( keys %{ $comp->{ values } } ) {
522 $config->{ "$prefix$element" } = $comp->{ values }->{ $element };
527 sub _config_substitutions {
528 my ( $self, $name, $subs, $arg ) = @_;
530 $subs->{ HOME } ||= sub { shift->path_to( '' ); };
534 if (! defined($ENV{$v})) {
535 Catalyst::Exception->throw( message =>
536 "Missing environment variable: $v" );
542 $subs->{ path_to } ||= sub { shift->path_to( @_ ); };
543 $subs->{ literal } ||= sub { return $_[ 1 ]; };
544 my $subsre = join( '|', keys %$subs );
546 $arg =~ s{__($subsre)(?:\((.+?)\))?__}{ $subs->{ $1 }->( $name, $2 ? split( /,/, $2 ) : () ) }eg;
550 sub get_component_from_sub_container {
551 my ( $self, $sub_container_name, $name, $c, @args ) = @_;
553 my $sub_container = $self->get_sub_container( $sub_container_name );
556 my $default = $sub_container->default_component;
558 return $sub_container->get_component( $default, $c, @args )
559 if $default && $sub_container->has_service( $default );
561 # FIXME - should I be calling $c->log->warn here?
562 # this is never a controller, so this is safe
563 $c->log->warn( "Calling \$c->$sub_container_name() is not supported unless you specify one of:" );
564 $c->log->warn( "* \$c->config(default_$sub_container_name => 'the name of the default $sub_container_name to use')" );
565 $c->log->warn( "* \$c->stash->{current_$sub_container_name} # the name of the view to use for this request" );
566 $c->log->warn( "* \$c->stash->{current_${sub_container_name}_instance} # the instance of the $sub_container_name to use for this request" );
571 return $sub_container->get_component_regexp( $name, $c, @args )
574 return $sub_container->get_component( $name, $c, @args )
575 if $sub_container->has_service( $name );
578 "Attempted to use $sub_container_name '$name', " .
579 "but it does not exist"
586 my ( $self, $component, @args ) = @_;
587 my ( $type, $name ) = _get_component_type_name($component);
590 return $self->get_component_from_sub_container(
594 my $query = ref $component
599 for my $subcontainer_name (qw/model view controller/) {
600 my $subcontainer = $self->get_sub_container( $subcontainer_name );
601 my @components = $subcontainer->get_service_list;
602 @result = grep { m{$component} } @components;
604 return map { $subcontainer->get_component( $_, @args ) } @result
608 # one last search for things like $c->comp(qr/::M::/)
609 @result = $self->_find_component_regexp(
611 ) if !@result and ref $component;
613 # it expects an empty list on failed searches
617 sub _find_component_regexp {
618 my ( $self, $component, $ctx, @args ) = @_;
621 my @components = grep { m{$component} } keys %{ $self->get_all_components($ctx) };
624 my ($type, $name) = _get_component_type_name($_);
626 push @result, $self->get_component_from_sub_container(
627 $type, $name, $ctx, @args
634 sub get_all_components {
635 my ($self, $class) = @_;
638 # FIXME - if we're getting from these containers, we need to either:
639 # - pass 'ctx' and 'accept_context_args' OR
640 # - make these params optional
641 # big problem when setting up the dispatcher - this method is called
642 # as $container->get_all_components('MyApp'). What to do with Request
644 foreach my $type (qw/model view controller /) {
645 my $container = $self->get_sub_container($type);
647 for my $component ($container->get_service_list) {
648 my $comp_service = $container->get_service($component);
650 $components{$comp_service->catalyst_component_name} = $comp_service->get(ctx => $class);
654 return lock_hash %components;
658 my ( $self, $component ) = @_;
659 my ( $type, $name ) = _get_component_type_name($component);
663 # The 'component' sub-container will create the object, and store it's
664 # instance, which, by default, will live throughout the application.
665 # The model/view/controller sub-containers only reference the instance
666 # held in the aforementioned sub-container, and execute the ACCEPT_CONTEXT
667 # sub every time they are called, when it exists.
668 my $instance_container = $self->get_sub_container('component');
669 my $accept_context_container = $self->get_sub_container($type);
671 # Custom containers might have added the service already
672 # We don't want to override that
673 return if $accept_context_container->has_service( $name );
675 my $component_service_name = "${type}_${name}";
677 $instance_container->add_service(
678 Catalyst::IOC::ConstructorInjection->new(
679 name => $component_service_name,
680 catalyst_component_name => $component,
682 lifecycle => 'Singleton',
684 depends_on( '/application_name' ),
688 # XXX - FIXME - We have to explicitly build the service here,
689 # causing the COMPONENT method to be called early here, as otherwise
690 # if the component method defines other classes (e.g. the
691 # ACCEPT_CONTEXT injection Model::DBIC::Schema does)
692 # then they won't be found by Devel::InnerPackage
693 # see also t/aggregate/unit_core_component_loading.t
694 $instance_container->get_service($component_service_name)->get;
696 $accept_context_container->add_service(
697 Catalyst::IOC::BlockInjection->new(
699 catalyst_component_name => $component,
701 depends_on( "/component/$component_service_name" ),
703 block => sub { shift->param($component_service_name) },
708 # FIXME: should this sub exist?
709 # should it be moved to Catalyst::Utils,
710 # or replaced by something already existing there?
711 sub _get_component_type_name {
712 my ( $component ) = @_;
715 while ( !$result and (my $index = index $component, '::') > 0 ) {
716 my $type = lc substr $component, 0, $index;
717 $component = substr $component, $index + 2;
718 $result = first { $type eq $_ or $type eq substr($_, 0, 1) }
719 qw{ model view controller };
722 return ($result, $component);
725 sub expand_component_module {
726 my ( $class, $module ) = @_;
727 return Devel::InnerPackage::list_packages( $module );
738 Catalyst::Container - IOC for Catalyst components
746 =head1 Methods for Building Containers
748 =head2 build_component_subcontainer
750 Container that stores all components, i.e. all models, views and controllers
751 together. Each service is an instance of the actual component, and by default
752 it lives while the application is running. Retrieving components from this
753 sub-container will instantiate the component, if it hasn't been instantiated
754 already, but will not execute ACCEPT_CONTEXT.
756 =head2 build_model_subcontainer
758 Container that stores references for all models that are inside the components
759 sub-container. Retrieving a model triggers ACCEPT_CONTEXT, if it exists.
761 =head2 build_view_subcontainer
763 Same as L<build_model_subcontainer>, but for views.
765 =head2 build_controller_subcontainer
767 Same as L<build_model_subcontainer>, but for controllers.
769 =head1 Methods for Building Services
771 =head2 build_application_name_service
773 Name of the application (such as MyApp).
775 =head2 build_driver_service
777 Config options passed directly to the driver being used.
779 =head2 build_file_service
783 =head2 build_substitutions_service
785 This method substitutes macros found with calls to a function. There are a
786 number of default macros:
790 =item * C<__HOME__> - replaced with C<$c-E<gt>path_to('')>
792 =item * C<__ENV(foo)__> - replaced with the value of C<$ENV{foo}>
794 =item * C<__path_to(foo/bar)__> - replaced with C<$c-E<gt>path_to('foo/bar')>
796 =item * C<__literal(__FOO__)__> - leaves __FOO__ alone (allows you to use
797 C<__DATA__> as a config value, for example)
801 The parameter list is split on comma (C<,>). You can override this method to
802 do your own string munging, or you can define your own macros in
803 C<< <MyApp->config( 'Plugin::ConfigLoader' => { substitutions => { ... } } ) >>.
806 MyApp->config( 'Plugin::ConfigLoader' => {
808 baz => sub { my $c = shift; qux( @_ ); },
812 The above will respond to C<__baz(x,y)__> in config strings.
814 =head2 build_extensions_service
816 Config::Any's available config file extensions (e.g. xml, json, pl, etc).
818 =head2 build_prefix_service
820 The prefix, based on the application name, that will be used to look-up the
821 config files (which will be in the format $prefix.$extension). If the app is
822 MyApp::Foo, the prefix will be myapp_foo.
824 =head2 build_path_service
826 The path to the config file (or environment variable, if defined).
828 =head2 build_config_service
830 The resulting configuration for the application, after it has successfully
831 been loaded, and all substitutions have been made.
833 =head2 build_raw_config_service
835 The merge of local_config and global_config hashes, before substitutions.
837 =head2 build_global_files_service
839 Gets all files for config that don't have the local_suffix, such as myapp.conf.
841 =head2 build_local_files_service
843 Gets all files for config that have the local_suffix, such as myapp_local.conf.
845 =head2 build_global_config_service
847 Reads config from global_files.
849 =head2 build_local_config_service
851 Reads config from local_files.
853 =head2 build_class_config_service
855 Reads config set from the application's class attribute config,
856 i.e. MyApp->config( name => 'MyApp', ... )
858 =head2 build_config_path_service
860 Splits the path to the config file, and returns on array ref containing
861 the path to the config file minus the extension in the first position,
862 and the extension in the second.
864 =head2 build_config_local_suffix_service
866 Determines the suffix of files used to override the main config. By default
867 this value is C<local>, which will load C<myapp_local.conf>. The suffix can
868 be specified in the following order of preference:
872 =item * C<$ENV{ MYAPP_CONFIG_LOCAL_SUFFIX }>
874 =item * C<$ENV{ CATALYST_CONFIG_LOCAL_SUFFIX }>
878 The first one of these values found replaces the default of C<local> in the
879 name of the local config file to be loaded.
881 For example, if C< $ENV{ MYAPP_CONFIG_LOCAL_SUFFIX }> is set to C<testing>,
882 ConfigLoader will try and load C<myapp_testing.conf> instead of
885 =head2 build_locate_components_service
887 This method is meant to provide a list of component modules that should be
888 setup for the application. By default, it will use L<Module::Pluggable>.
890 Specify a C<setup_components> config option to pass additional options directly
891 to L<Module::Pluggable>.
895 =head2 get_component_from_sub_container($sub_container, $name, $c, @args)
897 Looks for components in a given sub-container (such as controller, model or
898 view), and returns the searched component. If $name is undef, it returns the
899 default component (such as default_view, if $sub_container is 'view'). If
900 $name is a regexp, it returns an array of matching components. Otherwise, it
901 looks for the component with name $name.
903 =head2 get_all_components
905 Fetches all the components, in each of the sub_containers model, view and
906 controller, and returns a read-only hash. The keys are the class names, and
907 the values are the blessed objects. This is what is returned by $c->components.
911 Adds a component to the appropriate sub-container. The sub-container is guessed
912 by the component name given.
914 =head2 find_component
916 Searches for components in all containers. If $component is the full class
917 name, the sub-container is guessed, and it gets the searched component in there.
918 Otherwise, it looks for a component with that name in all sub-containers. If
919 $component is a regexp it calls _find_component_regexp and matches all
920 components against that regexp.
922 =head2 expand_component_module
924 Components found by C<locate_components> will be passed to this method, which
925 is expected to return a list of component (package) names to be set up.
927 =head2 setup_components
929 Uses locate_components service to list the components, and adds them to the
930 appropriate sub-containers, using add_component().
934 Catalyst Contributors, see Catalyst.pm
938 This library is free software. You can redistribute it and/or modify it under
939 the same terms as Perl itself.