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"}
81 $self->add_sub_container(
82 $self->build_controller_subcontainer
85 # FIXME - the config should be merged at this point
86 my $config = $self->resolve( service => 'config' );
87 my $default_view = $params->{default_view} || $config->{default_view};
88 my $default_model = $params->{default_model} || $config->{default_model};
90 $self->add_sub_container(
91 $self->build_view_subcontainer(
92 default_component => $default_view,
96 $self->add_sub_container(
97 $self->build_model_subcontainer(
98 default_component => $default_model,
103 sub build_model_subcontainer {
106 return $self->new_sub_container( @_,
111 sub build_view_subcontainer {
114 return $self->new_sub_container( @_,
119 sub build_controller_subcontainer {
122 return $self->new_sub_container(
123 name => 'controller',
127 sub build_application_name_service {
130 return Bread::Board::Literal->new( name => 'application_name', value => $self->application_name );
133 sub build_driver_service {
136 return Bread::Board::Literal->new( name => 'driver', value => $self->driver );
139 sub build_file_service {
142 return Bread::Board::Literal->new( name => 'file', value => $self->file );
145 sub build_substitutions_service {
148 return Bread::Board::Literal->new( name => 'substitutions', value => $self->substitutions );
151 sub build_extensions_service {
154 return Bread::Board::BlockInjection->new(
155 lifecycle => 'Singleton',
156 name => 'extensions',
158 return \@{Config::Any->extensions};
163 sub build_prefix_service {
166 return Bread::Board::BlockInjection->new(
167 lifecycle => 'Singleton',
170 return Catalyst::Utils::appprefix( shift->param('application_name') );
172 dependencies => [ depends_on('application_name') ],
176 sub build_path_service {
179 return Bread::Board::BlockInjection->new(
180 lifecycle => 'Singleton',
185 return Catalyst::Utils::env_value( $s->param('application_name'), 'CONFIG' )
187 || $s->param('application_name')->path_to( $s->param('prefix') );
189 dependencies => [ depends_on('file'), depends_on('application_name'), depends_on('prefix') ],
193 sub build_config_service {
196 return Bread::Board::BlockInjection->new(
197 lifecycle => 'Singleton',
202 my $v = Data::Visitor::Callback->new(
204 return unless defined $_;
205 return $self->_config_substitutions( $s->param('application_name'), $s->param('substitutions'), $_ );
209 $v->visit( $s->param('raw_config') );
211 dependencies => [ depends_on('application_name'), depends_on('raw_config'), depends_on('substitutions') ],
215 sub build_raw_config_service {
218 return Bread::Board::BlockInjection->new(
219 lifecycle => 'Singleton',
220 name => 'raw_config',
224 my @global = @{$s->param('global_config')};
225 my @locals = @{$s->param('local_config')};
228 for my $cfg (@global, @locals) {
230 $config = Catalyst::Utils::merge_hashes( $config, $cfg->{$_} );
235 dependencies => [ depends_on('global_config'), depends_on('local_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_global_config_service {
296 return Bread::Board::BlockInjection->new(
297 lifecycle => 'Singleton',
298 name => 'global_config',
302 return Config::Any->load_files({
303 files => $s->param('global_files'),
304 filter => \&_fix_syntax,
306 driver_args => $s->param('driver'),
309 dependencies => [ depends_on('global_files') ],
313 sub build_local_config_service {
316 return Bread::Board::BlockInjection->new(
317 lifecycle => 'Singleton',
318 name => 'local_config',
322 return Config::Any->load_files({
323 files => $s->param('local_files'),
324 filter => \&_fix_syntax,
326 driver_args => $s->param('driver'),
329 dependencies => [ depends_on('local_files') ],
333 sub build_config_path_service {
336 return Bread::Board::BlockInjection->new(
337 lifecycle => 'Singleton',
338 name => 'config_path',
342 my $path = $s->param('path');
343 my $prefix = $s->param('prefix');
345 my ( $extension ) = ( $path =~ m{\.(.{1,4})$} );
348 $path =~ s{[\/\\]$}{};
352 return [ $path, $extension ];
354 dependencies => [ depends_on('prefix'), depends_on('path') ],
358 sub build_config_local_suffix_service {
361 return Bread::Board::BlockInjection->new(
362 lifecycle => 'Singleton',
363 name => 'config_local_suffix',
366 my $suffix = Catalyst::Utils::env_value( $s->param('application_name'), 'CONFIG_LOCAL_SUFFIX' ) || $self->config_local_suffix;
370 dependencies => [ depends_on('application_name') ],
374 sub build_locate_components_service {
377 return Bread::Board::BlockInjection->new(
378 lifecycle => 'Singleton',
379 name => 'locate_components',
382 my $class = $s->param('application_name');
383 my $config = $s->param('config')->{ setup_components };
385 Catalyst::Exception->throw(
386 qq{You are using search_extra config option. That option is\n} .
387 qq{deprecated, please refer to the documentation for\n} .
388 qq{other ways of achieving the same results.\n}
389 ) if delete $config->{ search_extra };
391 my @paths = qw( ::Controller ::C ::Model ::M ::View ::V );
393 my $locator = Module::Pluggable::Object->new(
394 search_path => [ map { s/^(?=::)/$class/; $_; } @paths ],
398 return [ $locator->plugins ];
400 dependencies => [ depends_on('application_name'), depends_on('config') ],
404 sub setup_components {
406 my $class = $self->resolve( service => 'application_name' );
407 my @comps = @{ $self->resolve( service => 'locate_components' ) };
408 my %comps = map { $_ => 1 } @comps;
409 my $deprecatedcatalyst_component_names = 0;
411 for my $component ( @comps ) {
413 # We pass ignore_loaded here so that overlay files for (e.g.)
414 # Model::DBI::Schema sub-classes are loaded - if it's in @comps
415 # we know M::P::O found a file on disk so this is safe
417 Catalyst::Utils::ensure_class_loaded( $component, { ignore_loaded => 1 } );
420 for my $component (@comps) {
421 $self->add_component( $component );
422 # FIXME - $instance->expand_modules() is broken
423 my @expanded_components = $self->expand_component_module( $component );
426 !$deprecatedcatalyst_component_names &&
427 ($deprecatedcatalyst_component_names = $component =~ m/::[CMV]::/) ||
428 ($deprecatedcatalyst_component_names = grep { /::[CMV]::/ } @expanded_components)
430 # FIXME - should I be calling warn here?
431 # Maybe it's time to remove it, or become fatal
432 $class->log->warn(qq{Your application is using the deprecated ::[MVC]:: type naming scheme.\n}.
433 qq{Please switch your class names to ::Model::, ::View:: and ::Controller: as appropriate.\n}
437 for my $component (@expanded_components) {
438 $self->add_component( $component )
439 unless $comps{$component};
443 $self->get_sub_container('model')->make_single_default;
444 $self->get_sub_container('view')->make_single_default;
451 prefix => $_ eq 'Component' ? '' : $_ . '::',
452 values => delete $config->{ lc $_ } || delete $config->{ $_ }
454 grep { ref $config->{ lc $_ } || ref $config->{ $_ } }
455 qw( Component Model M View V Controller C Plugin )
458 foreach my $comp ( @components ) {
459 my $prefix = $comp->{ prefix };
460 foreach my $element ( keys %{ $comp->{ values } } ) {
461 $config->{ "$prefix$element" } = $comp->{ values }->{ $element };
466 sub _config_substitutions {
467 my ( $self, $name, $subs, $arg ) = @_;
469 $subs->{ HOME } ||= sub { shift->path_to( '' ); };
473 if (! defined($ENV{$v})) {
474 Catalyst::Exception->throw( message =>
475 "Missing environment variable: $v" );
481 $subs->{ path_to } ||= sub { shift->path_to( @_ ); };
482 $subs->{ literal } ||= sub { return $_[ 1 ]; };
483 my $subsre = join( '|', keys %$subs );
485 $arg =~ s{__($subsre)(?:\((.+?)\))?__}{ $subs->{ $1 }->( $name, $2 ? split( /,/, $2 ) : () ) }eg;
489 sub get_component_from_sub_container {
490 my ( $self, $sub_container_name, $name, $c, @args ) = @_;
492 my $sub_container = $self->get_sub_container( $sub_container_name );
495 my $default = $sub_container->default_component;
497 return $sub_container->get_component( $default, $c, @args )
498 if $default && $sub_container->has_service( $default );
500 # FIXME - should I be calling $c->log->warn here?
501 # this is never a controller, so this is safe
502 $c->log->warn( "Calling \$c->$sub_container_name() is not supported unless you specify one of:" );
503 $c->log->warn( "* \$c->config(default_$sub_container_name => 'the name of the default $sub_container_name to use')" );
504 $c->log->warn( "* \$c->stash->{current_$sub_container_name} # the name of the view to use for this request" );
505 $c->log->warn( "* \$c->stash->{current_${sub_container_name}_instance} # the instance of the $sub_container_name to use for this request" );
510 return $sub_container->get_component_regexp( $name, $c, @args )
513 return $sub_container->get_component( $name, $c, @args )
514 if $sub_container->has_service( $name );
517 "Attempted to use $sub_container_name '$name', " .
518 "but it does not exist"
525 my ( $self, $component, $c, @args ) = @_;
526 my ( $type, $name ) = _get_component_type_name($component);
529 return $self->get_component_from_sub_container(
530 $type, $name, $c, @args
533 my $query = ref $component
538 for my $subcontainer_name (qw/model view controller/) {
539 my $subcontainer = $self->get_sub_container( $subcontainer_name );
540 my @components = $subcontainer->get_service_list;
541 @result = grep { m{$component} } @components;
543 return map { $subcontainer->get_component( $_, $c, @args ) } @result
547 # FIXME - I guess I shouldn't be calling $c->components here
548 # one last search for things like $c->comp(qr/::M::/)
549 @result = $self->find_component_regexp(
550 $c->components, $component, $c, @args
551 ) if !@result and ref $component;
553 # it expects an empty list on failed searches
557 sub find_component_regexp {
558 my ( $self, $components, $component, @args ) = @_;
561 my @components = grep { m{$component} } keys %{ $components };
564 my ($type, $name) = _get_component_type_name($_);
566 push @result, $self->get_component_from_sub_container(
574 # FIXME - t0m, how do you feel about this name?
575 # also, do you think I should draw it here, or just return the data structure?
576 sub get_components_names_types {
578 my @comps_names_types;
580 for my $sub_container_name (qw/model view controller/) {
581 my $sub_container = $self->get_sub_container($sub_container_name);
582 for my $service ( $sub_container->get_service_list ) {
583 my $comp = $self->resolve(service => $service);
584 my $compname = ref $comp || $comp;
585 my $type = ref $comp ? 'instance' : 'class';
586 push @comps_names_types, [ $compname, $type ];
590 return @comps_names_types;
593 sub get_all_components {
598 map { $_ => $self->get_sub_container($_) } qw(model view controller)
601 for my $container (keys %$containers) {
602 for my $component ($containers->{$container}->get_service_list) {
603 my $comp = $containers->{$container}->resolve(
604 service => $component
606 my $comp_name = ref $comp || $comp;
607 $components{$comp_name} = $comp;
611 return lock_hash %components;
615 my ( $self, $component ) = @_;
616 my ( $type, $name ) = _get_component_type_name($component);
620 $self->get_sub_container($type)->add_service(
621 Catalyst::IOC::ConstructorInjection->new(
625 depends_on( '/application_name' ),
626 depends_on( '/config' ),
631 default => Catalyst::Utils::class2classsuffix( $component ),
633 accept_context_args => {
634 isa => 'ArrayRef|Undef',
643 # FIXME: should this sub exist?
644 # should it be moved to Catalyst::Utils,
645 # or replaced by something already existing there?
646 sub _get_component_type_name {
647 my ( $component ) = @_;
649 my @parts = split /::/, $component;
651 while (my $type = shift @parts) {
652 return ('controller', join '::', @parts)
653 if $type =~ /^(c|controller)$/i;
655 return ('model', join '::', @parts)
656 if $type =~ /^(m|model)$/i;
658 return ('view', join '::', @parts)
659 if $type =~ /^(v|view)$/i;
662 return (undef, $component);
665 sub expand_component_module {
666 my ( $class, $module ) = @_;
667 return Devel::InnerPackage::list_packages( $module );
678 Catalyst::Container - IOC for Catalyst components
686 =head1 Building Containers
688 =head2 build_model_subcontainer
690 Container that stores all models.
692 =head2 build_view_subcontainer
694 Container that stores all views.
696 =head2 build_controller_subcontainer
698 Container that stores all controllers.
700 =head1 Building Services
702 =head2 build_application_name_service
704 Name of the application (such as MyApp).
706 =head2 build_driver_service
708 Config options passed directly to the driver being used.
710 =head2 build_file_service
714 =head2 build_substitutions_service
716 This method substitutes macros found with calls to a function. There are a
717 number of default macros:
721 =item * C<__HOME__> - replaced with C<$c-E<gt>path_to('')>
723 =item * C<__ENV(foo)__> - replaced with the value of C<$ENV{foo}>
725 =item * C<__path_to(foo/bar)__> - replaced with C<$c-E<gt>path_to('foo/bar')>
727 =item * C<__literal(__FOO__)__> - leaves __FOO__ alone (allows you to use
728 C<__DATA__> as a config value, for example)
732 The parameter list is split on comma (C<,>). You can override this method to
733 do your own string munging, or you can define your own macros in
734 C<MyApp-E<gt>config-E<gt>{ 'Plugin::ConfigLoader' }-E<gt>{ substitutions }>.
737 MyApp->config->{ 'Plugin::ConfigLoader' }->{ substitutions } = {
738 baz => sub { my $c = shift; qux( @_ ); }
741 The above will respond to C<__baz(x,y)__> in config strings.
743 =head2 build_extensions_service
745 Config::Any's available config file extensions (e.g. xml, json, pl, etc).
747 =head2 build_prefix_service
749 The prefix, based on the application name, that will be used to lookup the
750 config files (which will be in the format $prefix.$extension). If the app is
751 MyApp::Foo, the prefix will be myapp_foo.
753 =head2 build_path_service
755 The path to the config file (or environment variable, if defined).
757 =head2 build_config_service
759 The resulting configuration for the application, after it has successfully
760 been loaded, and all substitutions have been made.
762 =head2 build_raw_config_service
764 The merge of local_config and global_config hashes, before substitutions.
766 =head2 build_global_files_service
768 Gets all files for config that don't have the local_suffix, such as myapp.conf.
770 =head2 build_local_files_service
772 Gets all files for config that have the local_suffix, such as myapp_local.conf.
774 =head2 build_global_config_service
776 Reads config from global_files.
778 =head2 build_local_config_service
780 Reads config from local_files.
782 =head2 build_config_path_service
784 Splits the path to the config file, and returns on array ref containing
785 the path to the config file minus the extension in the first position,
786 and the extension in the second.
788 =head2 build_config_local_suffix_service
790 Determines the suffix of files used to override the main config. By default
791 this value is C<local>, which will load C<myapp_local.conf>. The suffix can
792 be specified in the following order of preference:
796 =item * C<$ENV{ MYAPP_CONFIG_LOCAL_SUFFIX }>
798 =item * C<$ENV{ CATALYST_CONFIG_LOCAL_SUFFIX }>
802 The first one of these values found replaces the default of C<local> in the
803 name of the local config file to be loaded.
805 For example, if C< $ENV{ MYAPP_CONFIG_LOCAL_SUFFIX }> is set to C<testing>,
806 ConfigLoader will try and load C<myapp_testing.conf> instead of
809 =head2 build_locate_components_service
811 This method is meant to provide a list of component modules that should be
812 setup for the application. By default, it will use L<Module::Pluggable>.
814 Specify a C<setup_components> config option to pass additional options directly
815 to L<Module::Pluggable>.
819 =head2 get_component_from_sub_container($sub_container, $name, $c, @args)
821 Looks for components in a given subcontainer (such as controller, model or
822 view), and returns the searched component. If $name is undef, it returns the
823 default component (such as default_view, if $sub_container is 'view'). If
824 $name is a regexp, it returns an array of matching components. Otherwise, it
825 looks for the component with name $name.
827 =head2 get_components_names_types
829 Gets all components from all containers and returns them as an array of
830 arrayrefs containing the component name and the component type (i.e., whether
831 it's an instance or a class).
833 =head2 get_all_components
835 Fetches all the components, in each of the sub_containers model, view and
836 controller, and returns a readonly hash. The keys are the class names, and
837 the values are the blessed objects. This is what is returned by $c->components.
841 Adds a component to the appropriate subcontainer. The subcontainer is guessed
842 by the component name given.
844 =head2 find_component
846 Searches for components in all containers. If $component is the full class
847 name, the subcontainer is guessed, and it gets the searched component in there.
848 Otherwise, it looks for a component with that name in all subcontainers. If
849 $component is a regexp, it calls the method below, find_component_regexp,
850 and matches all components against that regexp.
852 =head2 find_component_regexp
854 Finds components that match a given regexp. Used internally, by find_component.
856 =head2 expand_component_module
858 Components found by C<locate_components> will be passed to this method, which
859 is expected to return a list of component (package) names to be set up.
861 =head2 setup_components
865 Catalyst Contributors, see Catalyst.pm
869 This library is free software. You can redistribute it and/or modify it under
870 the same terms as Perl itself.