1 package Catalyst::IOC::Container;
2 use Bread::Board qw/depends_on/;
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 sub_container_class => (
48 default => 'Catalyst::IOC::SubContainer',
50 new_sub_container => 'new',
55 my ( $self, $params ) = @_;
58 $self->${\"build_${_}_service"}
79 my $config = $self->resolve( service => 'config' );
81 # don't force default_component to be undef if the config wasn't set
82 my @default_view = $config->{default_view}
83 ? ( default_component => $config->{default_view} )
86 my @default_model = $config->{default_model}
87 ? ( default_component => $config->{default_model} )
91 $self->add_sub_container(
92 $self->build_component_subcontainer
95 $self->add_sub_container(
96 $self->build_controller_subcontainer
99 $self->add_sub_container(
100 $self->build_view_subcontainer( @default_view )
103 $self->add_sub_container(
104 $self->build_model_subcontainer( @default_model )
110 my $class = ref $self;
111 ${ $class . '::customise_container' }->($self)
112 if ${ $class . '::customise_container' };
116 sub build_model_subcontainer {
119 return $self->new_sub_container( @_,
124 sub build_view_subcontainer {
127 return $self->new_sub_container( @_,
132 sub build_controller_subcontainer {
135 return $self->new_sub_container(
136 name => 'controller',
140 sub build_component_subcontainer {
143 return Bread::Board::Container->new(
148 sub build_home_service {
151 return Bread::Board::BlockInjection->new(
152 lifecycle => 'Singleton',
156 my $class = $self->param('application_name');
159 if ( my $env = Catalyst::Utils::env_value( $class, 'HOME' ) ) {
163 $home ||= Catalyst::Utils::home($class);
166 dependencies => [ depends_on('application_name') ],
170 # FIXME: very ambiguous - maybe root_dir?
171 sub build_root_service {
174 return Bread::Board::BlockInjection->new(
175 lifecycle => 'Singleton',
180 return Path::Class::Dir->new( $self->param('home') )->subdir('root');
182 dependencies => [ depends_on('home') ],
186 sub build_application_name_service {
189 return Bread::Board::Literal->new( name => 'application_name', value => $self->name );
192 sub build_driver_service {
195 return Bread::Board::Literal->new( name => 'driver', value => $self->driver );
198 sub build_file_service {
201 return Bread::Board::Literal->new( name => 'file', value => $self->file );
204 sub build_substitutions_service {
207 return Bread::Board::Literal->new( name => 'substitutions', value => $self->substitutions );
210 sub build_extensions_service {
213 return Bread::Board::BlockInjection->new(
214 lifecycle => 'Singleton',
215 name => 'extensions',
217 return \@{Config::Any->extensions};
222 sub build_prefix_service {
225 return Bread::Board::BlockInjection->new(
226 lifecycle => 'Singleton',
229 return Catalyst::Utils::appprefix( shift->param('application_name') );
231 dependencies => [ depends_on('application_name') ],
235 sub build_path_service {
238 return Bread::Board::BlockInjection->new(
239 lifecycle => 'Singleton',
244 return Catalyst::Utils::env_value( $s->param('application_name'), 'CONFIG' )
246 || $s->param('application_name')->path_to( $s->param('prefix') );
248 dependencies => [ depends_on('file'), depends_on('application_name'), depends_on('prefix') ],
252 sub build_config_service {
255 return Bread::Board::BlockInjection->new(
256 lifecycle => 'Singleton',
261 my $v = Data::Visitor::Callback->new(
263 return unless defined $_;
264 return $self->_config_substitutions( $s->param('application_name'), $s->param('substitutions'), $_ );
268 $v->visit( $s->param('raw_config') );
270 dependencies => [ depends_on('application_name'), depends_on('raw_config'), depends_on('substitutions') ],
274 sub build_raw_config_service {
277 return Bread::Board::BlockInjection->new(
278 lifecycle => 'Singleton',
279 name => 'raw_config',
283 my @global = @{$s->param('global_config')};
284 my @locals = @{$s->param('local_config')};
286 my $config = $s->param('class_config');
288 for my $cfg (@global, @locals) {
290 $config = Catalyst::Utils::merge_hashes( $config, $cfg->{$_} );
296 dependencies => [ depends_on('global_config'), depends_on('local_config'), depends_on('class_config') ],
300 sub build_global_files_service {
303 return Bread::Board::BlockInjection->new(
304 lifecycle => 'Singleton',
305 name => 'global_files',
309 my ( $path, $extension ) = @{$s->param('config_path')};
311 my @extensions = @{$s->param('extensions')};
315 die "Unable to handle files with the extension '${extension}'" unless grep { $_ eq $extension } @extensions;
318 @files = map { "$path.$_" } @extensions;
322 dependencies => [ depends_on('extensions'), depends_on('config_path') ],
326 sub build_local_files_service {
329 return Bread::Board::BlockInjection->new(
330 lifecycle => 'Singleton',
331 name => 'local_files',
335 my ( $path, $extension ) = @{$s->param('config_path')};
336 my $suffix = $s->param('config_local_suffix');
338 my @extensions = @{$s->param('extensions')};
342 die "Unable to handle files with the extension '${extension}'" unless grep { $_ eq $extension } @extensions;
343 $path =~ s{\.$extension}{_$suffix.$extension};
346 @files = map { "${path}_${suffix}.$_" } @extensions;
350 dependencies => [ depends_on('extensions'), depends_on('config_path'), depends_on('config_local_suffix') ],
354 sub build_class_config_service {
357 return Bread::Board::BlockInjection->new(
358 lifecycle => 'Singleton',
359 name => 'class_config',
362 my $app = $s->param('application_name');
364 # Container might be called outside Catalyst context
365 return {} unless Class::MOP::is_class_loaded($app);
367 # config might not have been defined
368 return $app->config || {};
370 dependencies => [ depends_on('application_name') ],
374 sub build_global_config_service {
377 return Bread::Board::BlockInjection->new(
378 lifecycle => 'Singleton',
379 name => 'global_config',
383 return Config::Any->load_files({
384 files => $s->param('global_files'),
385 filter => \&_fix_syntax,
387 driver_args => $s->param('driver'),
390 dependencies => [ depends_on('global_files') ],
394 sub build_local_config_service {
397 return Bread::Board::BlockInjection->new(
398 lifecycle => 'Singleton',
399 name => 'local_config',
403 return Config::Any->load_files({
404 files => $s->param('local_files'),
405 filter => \&_fix_syntax,
407 driver_args => $s->param('driver'),
410 dependencies => [ depends_on('local_files') ],
414 sub build_config_path_service {
417 return Bread::Board::BlockInjection->new(
418 lifecycle => 'Singleton',
419 name => 'config_path',
423 my $path = $s->param('path');
424 my $prefix = $s->param('prefix');
426 my ( $extension ) = ( $path =~ m{\.(.{1,4})$} );
429 $path =~ s{[\/\\]$}{};
433 return [ $path, $extension ];
435 dependencies => [ depends_on('prefix'), depends_on('path') ],
439 sub build_config_local_suffix_service {
442 return Bread::Board::BlockInjection->new(
443 lifecycle => 'Singleton',
444 name => 'config_local_suffix',
447 my $suffix = Catalyst::Utils::env_value( $s->param('application_name'), 'CONFIG_LOCAL_SUFFIX' ) || $self->config_local_suffix;
451 dependencies => [ depends_on('application_name') ],
455 sub build_locate_components_service {
458 return Bread::Board::BlockInjection->new(
459 lifecycle => 'Singleton',
460 name => 'locate_components',
463 my $class = $s->param('application_name');
464 my $config = $s->param('config')->{ setup_components };
466 Catalyst::Exception->throw(
467 qq{You are using search_extra config option. That option is\n} .
468 qq{deprecated, please refer to the documentation for\n} .
469 qq{other ways of achieving the same results.\n}
470 ) if delete $config->{ search_extra };
472 my @paths = qw( ::Controller ::C ::Model ::M ::View ::V );
474 my $locator = Module::Pluggable::Object->new(
475 search_path => [ map { s/^(?=::)/$class/; $_; } @paths ],
479 return [ $locator->plugins ];
481 dependencies => [ depends_on('application_name'), depends_on('config') ],
485 sub setup_components {
487 my $class = $self->resolve( service => 'application_name' );
488 my @comps = @{ $self->resolve( service => 'locate_components' ) };
489 my %comps = map { $_ => 1 } @comps;
490 my $deprecatedcatalyst_component_names = 0;
492 my $app_locate_components_addr = refaddr(
493 $class->can('locate_components')
495 my $cat_locate_components_addr = refaddr(
496 Catalyst->can('locate_components')
499 if ($app_locate_components_addr != $cat_locate_components_addr) {
500 # FIXME - why not just say: @comps = $class->locate_components() ?
501 $class->log->warn(qq{You have overridden locate_components. That } .
502 qq{no longer works. Please refer to the documentation to achieve } .
503 qq{similar results.\n}
507 for my $component ( @comps ) {
509 # We pass ignore_loaded here so that overlay files for (e.g.)
510 # Model::DBI::Schema sub-classes are loaded - if it's in @comps
511 # we know M::P::O found a file on disk so this is safe
513 Catalyst::Utils::ensure_class_loaded( $component, { ignore_loaded => 1 } );
516 for my $component (@comps) {
517 $self->add_component( $component );
518 # FIXME - $instance->expand_modules() is broken
519 my @expanded_components = $self->expand_component_module( $component );
522 !$deprecatedcatalyst_component_names &&
523 ($deprecatedcatalyst_component_names = $component =~ m/::[CMV]::/) ||
524 ($deprecatedcatalyst_component_names = grep { /::[CMV]::/ } @expanded_components)
526 # FIXME - should I be calling warn here?
527 # Maybe it's time to remove it, or become fatal
528 $class->log->warn(qq{Your application is using the deprecated ::[MVC]:: type naming scheme.\n}.
529 qq{Please switch your class names to ::Model::, ::View:: and ::Controller: as appropriate.\n}
533 for my $component (@expanded_components) {
534 $self->add_component( $component )
535 unless $comps{$component};
544 prefix => $_ eq 'Component' ? '' : $_ . '::',
545 values => delete $config->{ lc $_ } || delete $config->{ $_ }
547 grep { ref $config->{ lc $_ } || ref $config->{ $_ } }
548 qw( Component Model M View V Controller C Plugin )
551 foreach my $comp ( @components ) {
552 my $prefix = $comp->{ prefix };
553 foreach my $element ( keys %{ $comp->{ values } } ) {
554 $config->{ "$prefix$element" } = $comp->{ values }->{ $element };
559 sub _config_substitutions {
560 my ( $self, $name, $subs, $arg ) = @_;
562 $subs->{ HOME } ||= sub { shift->path_to( '' ); };
566 if (! defined($ENV{$v})) {
567 Catalyst::Exception->throw( message =>
568 "Missing environment variable: $v" );
574 $subs->{ path_to } ||= sub { shift->path_to( @_ ); };
575 $subs->{ literal } ||= sub { return $_[ 1 ]; };
576 my $subsre = join( '|', keys %$subs );
578 $arg =~ s{__($subsre)(?:\((.+?)\))?__}{ $subs->{ $1 }->( $name, $2 ? split( /,/, $2 ) : () ) }eg;
582 sub get_component_from_sub_container {
583 my ( $self, $sub_container_name, $name, $c, @args ) = @_;
585 my $sub_container = $self->get_sub_container( $sub_container_name );
588 my $default = $sub_container->default_component;
590 return $sub_container->get_component( $default, $c, @args )
591 if $default && $sub_container->has_service( $default );
593 # FIXME - should I be calling $c->log->warn here?
594 # this is never a controller, so this is safe
595 $c->log->warn( "Calling \$c->$sub_container_name() is not supported unless you specify one of:" );
596 $c->log->warn( "* \$c->config(default_$sub_container_name => 'the name of the default $sub_container_name to use')" );
597 $c->log->warn( "* \$c->stash->{current_$sub_container_name} # the name of the view to use for this request" );
598 $c->log->warn( "* \$c->stash->{current_${sub_container_name}_instance} # the instance of the $sub_container_name to use for this request" );
603 return $sub_container->get_component_regexp( $name, $c, @args )
606 return $sub_container->get_component( $name, $c, @args )
607 if $sub_container->has_service( $name );
610 "Attempted to use $sub_container_name '$name', " .
611 "but it does not exist"
618 my ( $self, $component, @args ) = @_;
619 my ( $type, $name ) = _get_component_type_name($component);
622 return $self->get_component_from_sub_container(
626 my $query = ref $component
631 for my $subcontainer_name (qw/model view controller/) {
632 my $subcontainer = $self->get_sub_container( $subcontainer_name );
633 my @components = $subcontainer->get_service_list;
634 @result = grep { m{$component} } @components;
636 return map { $subcontainer->get_component( $_, @args ) } @result
640 # one last search for things like $c->comp(qr/::M::/)
641 @result = $self->_find_component_regexp(
643 ) if !@result and ref $component;
645 # it expects an empty list on failed searches
649 sub _find_component_regexp {
650 my ( $self, $component, $ctx, @args ) = @_;
653 my @components = grep { m{$component} } keys %{ $self->get_all_components($ctx) };
656 my ($type, $name) = _get_component_type_name($_);
658 push @result, $self->get_component_from_sub_container(
659 $type, $name, $ctx, @args
666 sub get_all_components {
667 my ($self, $class) = @_;
670 # FIXME - if we're getting from these containers, we need to either:
671 # - pass 'ctx' and 'accept_context_args' OR
672 # - make these params optional
673 # big problem when setting up the dispatcher - this method is called
674 # as $container->get_all_components('MyApp'). What to do with Request
676 foreach my $type (qw/model view controller /) {
677 my $container = $self->get_sub_container($type);
679 for my $component ($container->get_service_list) {
680 my $comp_service = $container->get_service($component);
682 $components{$comp_service->catalyst_component_name} = $comp_service->get(ctx => $class);
686 return lock_hash %components;
690 my ( $self, $component ) = @_;
691 my ( $type, $name ) = _get_component_type_name($component);
695 # The 'component' sub-container will create the object, and store it's
696 # instance, which, by default, will live throughout the application.
697 # The model/view/controller sub-containers only reference the instance
698 # held in the aforementioned sub-container, and execute the ACCEPT_CONTEXT
699 # sub every time they are called, when it exists.
700 my $instance_container = $self->get_sub_container('component');
701 my $accept_context_container = $self->get_sub_container($type);
703 # Custom containers might have added the service already
704 # We don't want to override that
705 return if $accept_context_container->has_service( $name );
707 my $component_service_name = "${type}_${name}";
709 $instance_container->add_service(
710 Catalyst::IOC::ConstructorInjection->new(
711 name => $component_service_name,
712 catalyst_component_name => $component,
714 lifecycle => 'Singleton',
716 depends_on( '/application_name' ),
720 # XXX - FIXME - We have to explicitly build the service here,
721 # causing the COMPONENT method to be called early here, as otherwise
722 # if the component method defines other classes (e.g. the
723 # ACCEPT_CONTEXT injection Model::DBIC::Schema does)
724 # then they won't be found by Devel::InnerPackage
725 # see also t/aggregate/unit_core_component_loading.t
726 $instance_container->get_service($component_service_name)->get;
728 $accept_context_container->add_service(
729 Catalyst::IOC::BlockInjection->new(
731 catalyst_component_name => $component,
733 depends_on( "/component/$component_service_name" ),
735 block => sub { shift->param($component_service_name) },
740 # FIXME: should this sub exist?
741 # should it be moved to Catalyst::Utils,
742 # or replaced by something already existing there?
743 sub _get_component_type_name {
744 my ( $component ) = @_;
747 while ( !$result and (my $index = index $component, '::') > 0 ) {
748 my $type = lc substr $component, 0, $index;
749 $component = substr $component, $index + 2;
750 $result = first { $type eq $_ or $type eq substr($_, 0, 1) }
751 qw{ model view controller };
754 return ($result, $component);
757 sub expand_component_module {
758 my ( $class, $module ) = @_;
759 return Devel::InnerPackage::list_packages( $module );
762 __PACKAGE__->meta->make_immutable;
772 Catalyst::Container - IOC for Catalyst components
780 =head1 Methods for Building Containers
782 =head2 build_component_subcontainer
784 Container that stores all components, i.e. all models, views and controllers
785 together. Each service is an instance of the actual component, and by default
786 it lives while the application is running. Retrieving components from this
787 sub-container will instantiate the component, if it hasn't been instantiated
788 already, but will not execute ACCEPT_CONTEXT.
790 =head2 build_model_subcontainer
792 Container that stores references for all models that are inside the components
793 sub-container. Retrieving a model triggers ACCEPT_CONTEXT, if it exists.
795 =head2 build_view_subcontainer
797 Same as L<build_model_subcontainer>, but for views.
799 =head2 build_controller_subcontainer
801 Same as L<build_model_subcontainer>, but for controllers.
803 =head1 Methods for Building Services
805 =head2 build_application_name_service
807 Name of the application (such as MyApp).
809 =head2 build_driver_service
811 Config options passed directly to the driver being used.
813 =head2 build_file_service
817 =head2 build_substitutions_service
819 This method substitutes macros found with calls to a function. There are a
820 number of default macros:
824 =item * C<__HOME__> - replaced with C<$c-E<gt>path_to('')>
826 =item * C<__ENV(foo)__> - replaced with the value of C<$ENV{foo}>
828 =item * C<__path_to(foo/bar)__> - replaced with C<$c-E<gt>path_to('foo/bar')>
830 =item * C<__literal(__FOO__)__> - leaves __FOO__ alone (allows you to use
831 C<__DATA__> as a config value, for example)
835 The parameter list is split on comma (C<,>). You can override this method to
836 do your own string munging, or you can define your own macros in
837 C<< <MyApp->config( 'Plugin::ConfigLoader' => { substitutions => { ... } } ) >>.
840 MyApp->config( 'Plugin::ConfigLoader' => {
842 baz => sub { my $c = shift; qux( @_ ); },
846 The above will respond to C<__baz(x,y)__> in config strings.
848 =head2 build_extensions_service
850 Config::Any's available config file extensions (e.g. xml, json, pl, etc).
852 =head2 build_prefix_service
854 The prefix, based on the application name, that will be used to look-up the
855 config files (which will be in the format $prefix.$extension). If the app is
856 MyApp::Foo, the prefix will be myapp_foo.
858 =head2 build_path_service
860 The path to the config file (or environment variable, if defined).
862 =head2 build_config_service
864 The resulting configuration for the application, after it has successfully
865 been loaded, and all substitutions have been made.
867 =head2 build_raw_config_service
869 The merge of local_config and global_config hashes, before substitutions.
871 =head2 build_global_files_service
873 Gets all files for config that don't have the local_suffix, such as myapp.conf.
875 =head2 build_local_files_service
877 Gets all files for config that have the local_suffix, such as myapp_local.conf.
879 =head2 build_global_config_service
881 Reads config from global_files.
883 =head2 build_local_config_service
885 Reads config from local_files.
887 =head2 build_class_config_service
889 Reads config set from the application's class attribute config,
890 i.e. MyApp->config( name => 'MyApp', ... )
892 =head2 build_config_path_service
894 Splits the path to the config file, and returns on array ref containing
895 the path to the config file minus the extension in the first position,
896 and the extension in the second.
898 =head2 build_config_local_suffix_service
900 Determines the suffix of files used to override the main config. By default
901 this value is C<local>, which will load C<myapp_local.conf>. The suffix can
902 be specified in the following order of preference:
906 =item * C<$ENV{ MYAPP_CONFIG_LOCAL_SUFFIX }>
908 =item * C<$ENV{ CATALYST_CONFIG_LOCAL_SUFFIX }>
912 The first one of these values found replaces the default of C<local> in the
913 name of the local config file to be loaded.
915 For example, if C< $ENV{ MYAPP_CONFIG_LOCAL_SUFFIX }> is set to C<testing>,
916 ConfigLoader will try and load C<myapp_testing.conf> instead of
919 =head2 build_locate_components_service
921 This method is meant to provide a list of component modules that should be
922 setup for the application. By default, it will use L<Module::Pluggable>.
924 Specify a C<setup_components> config option to pass additional options directly
925 to L<Module::Pluggable>.
929 =head2 get_component_from_sub_container($sub_container, $name, $c, @args)
931 Looks for components in a given sub-container (such as controller, model or
932 view), and returns the searched component. If $name is undef, it returns the
933 default component (such as default_view, if $sub_container is 'view'). If
934 $name is a regexp, it returns an array of matching components. Otherwise, it
935 looks for the component with name $name.
937 =head2 get_all_components
939 Fetches all the components, in each of the sub_containers model, view and
940 controller, and returns a read-only hash. The keys are the class names, and
941 the values are the blessed objects. This is what is returned by $c->components.
945 Adds a component to the appropriate sub-container. The sub-container is guessed
946 by the component name given.
948 =head2 find_component
950 Searches for components in all containers. If $component is the full class
951 name, the sub-container is guessed, and it gets the searched component in there.
952 Otherwise, it looks for a component with that name in all sub-containers. If
953 $component is a regexp it calls _find_component_regexp and matches all
954 components against that regexp.
956 =head2 expand_component_module
958 Components found by C<locate_components> will be passed to this method, which
959 is expected to return a list of component (package) names to be set up.
961 =head2 setup_components
963 Uses locate_components service to list the components, and adds them to the
964 appropriate sub-containers, using add_component().
968 Catalyst Contributors, see Catalyst.pm
972 This library is free software. You can redistribute it and/or modify it under
973 the same terms as Perl itself.