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, $class );
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 $class->log->warn(qq{Your application is using the deprecated ::[MVC]:: type naming scheme.\n}.
432 qq{Please switch your class names to ::Model::, ::View:: and ::Controller: as appropriate.\n}
436 for my $component (@expanded_components) {
437 $self->add_component( $component, $class )
438 unless $comps{$component};
442 $self->get_sub_container('model')->make_single_default;
443 $self->get_sub_container('view')->make_single_default;
450 prefix => $_ eq 'Component' ? '' : $_ . '::',
451 values => delete $config->{ lc $_ } || delete $config->{ $_ }
453 grep { ref $config->{ lc $_ } || ref $config->{ $_ } }
454 qw( Component Model M View V Controller C Plugin )
457 foreach my $comp ( @components ) {
458 my $prefix = $comp->{ prefix };
459 foreach my $element ( keys %{ $comp->{ values } } ) {
460 $config->{ "$prefix$element" } = $comp->{ values }->{ $element };
465 sub _config_substitutions {
466 my ( $self, $name, $subs, $arg ) = @_;
468 $subs->{ HOME } ||= sub { shift->path_to( '' ); };
472 if (! defined($ENV{$v})) {
473 Catalyst::Exception->throw( message =>
474 "Missing environment variable: $v" );
480 $subs->{ path_to } ||= sub { shift->path_to( @_ ); };
481 $subs->{ literal } ||= sub { return $_[ 1 ]; };
482 my $subsre = join( '|', keys %$subs );
484 $arg =~ s{__($subsre)(?:\((.+?)\))?__}{ $subs->{ $1 }->( $name, $2 ? split( /,/, $2 ) : () ) }eg;
488 sub get_component_from_sub_container {
489 my ( $self, $sub_container_name, $name, $c, @args ) = @_;
491 my $sub_container = $self->get_sub_container( $sub_container_name );
494 my $default = $sub_container->default_component;
496 return $sub_container->get_component( $default, $c, @args )
497 if $default && $sub_container->has_service( $default );
499 # FIXME - should I be calling $c->log->warn here?
500 # this is never a controller, so this is safe
501 $c->log->warn( "Calling \$c->$sub_container_name() is not supported unless you specify one of:" );
502 $c->log->warn( "* \$c->config(default_$sub_container_name => 'the name of the default $sub_container_name to use')" );
503 $c->log->warn( "* \$c->stash->{current_$sub_container_name} # the name of the view to use for this request" );
504 $c->log->warn( "* \$c->stash->{current_${sub_container_name}_instance} # the instance of the $sub_container_name to use for this request" );
509 return $sub_container->get_component_regexp( $name, $c, @args )
512 return $sub_container->get_component( $name, $c, @args )
513 if $sub_container->has_service( $name );
516 "Attempted to use $sub_container_name '$name', " .
517 "but it does not exist"
524 my ( $self, $component, $c, @args ) = @_;
525 my ( $type, $name ) = _get_component_type_name($component);
528 return $self->get_component_from_sub_container(
529 $type, $name, $c, @args
532 my $query = ref $component
537 for my $subcontainer_name (qw/model view controller/) {
538 my $subcontainer = $self->get_sub_container( $subcontainer_name );
539 my @components = $subcontainer->get_service_list;
540 @result = grep { m{$component} } @components;
542 return map { $subcontainer->get_component( $_, $c, @args ) } @result
546 # FIXME - I guess I shouldn't be calling $c->components here
547 # one last search for things like $c->comp(qr/::M::/)
548 @result = $self->find_component_regexp(
549 $c->components, $component, $c, @args
550 ) if !@result and ref $component;
552 # it expects an empty list on failed searches
556 sub find_component_regexp {
557 my ( $self, $components, $component, @args ) = @_;
560 my @components = grep { m{$component} } keys %{ $components };
563 my ($type, $name) = _get_component_type_name($_);
565 push @result, $self->get_component_from_sub_container(
573 # FIXME sorry for the name again :)
574 sub get_components_types {
578 for my $sub_container_name (qw/model view controller/) {
579 my $sub_container = $self->get_sub_container($sub_container_name);
580 for my $service ( $sub_container->get_service_list ) {
581 my $comp = $self->resolve(service => $service);
582 my $compname = ref $comp || $comp;
583 my $type = ref $comp ? 'instance' : 'class';
584 push @comps_types, [ $compname, $type ];
591 sub get_all_components {
596 map { $_ => $self->get_sub_container($_) } qw(model view controller)
599 for my $container (keys %$containers) {
600 for my $component ($containers->{$container}->get_service_list) {
601 my $comp = $containers->{$container}->resolve(
602 service => $component
604 my $comp_name = ref $comp || $comp;
605 $components{$comp_name} = $comp;
609 return lock_hash %components;
613 my ( $self, $component, $class ) = @_;
614 my ( $type, $name ) = _get_component_type_name($component);
618 $self->get_sub_container($type)->add_service(
619 Catalyst::IOC::ConstructorInjection->new(
623 depends_on( '/application_name' ),
624 depends_on( '/config' ),
629 default => Catalyst::Utils::class2classsuffix( $component ),
631 accept_context_args => {
632 isa => 'ArrayRef|Undef',
641 # FIXME: should this sub exist?
642 # should it be moved to Catalyst::Utils,
643 # or replaced by something already existing there?
644 sub _get_component_type_name {
645 my ( $component ) = @_;
647 my @parts = split /::/, $component;
649 while (my $type = shift @parts) {
650 return ('controller', join '::', @parts)
651 if $type =~ /^(c|controller)$/i;
653 return ('model', join '::', @parts)
654 if $type =~ /^(m|model)$/i;
656 return ('view', join '::', @parts)
657 if $type =~ /^(v|view)$/i;
660 return (undef, $component);
663 sub expand_component_module {
664 my ( $class, $module ) = @_;
665 return Devel::InnerPackage::list_packages( $module );
676 Catalyst::Container - IOC for Catalyst components
684 =head1 Building Containers
686 =head2 build_model_subcontainer
688 Container that stores all models.
690 =head2 build_view_subcontainer
692 Container that stores all views.
694 =head2 build_controller_subcontainer
696 Container that stores all controllers.
698 =head1 Building Services
700 =head2 build_application_name_service
702 Name of the application (such as MyApp).
704 =head2 build_driver_service
706 Config options passed directly to the driver being used.
708 =head2 build_file_service
712 =head2 build_substitutions_service
714 This method substitutes macros found with calls to a function. There are a
715 number of default macros:
719 =item * C<__HOME__> - replaced with C<$c-E<gt>path_to('')>
721 =item * C<__ENV(foo)__> - replaced with the value of C<$ENV{foo}>
723 =item * C<__path_to(foo/bar)__> - replaced with C<$c-E<gt>path_to('foo/bar')>
725 =item * C<__literal(__FOO__)__> - leaves __FOO__ alone (allows you to use
726 C<__DATA__> as a config value, for example)
730 The parameter list is split on comma (C<,>). You can override this method to
731 do your own string munging, or you can define your own macros in
732 C<MyApp-E<gt>config-E<gt>{ 'Plugin::ConfigLoader' }-E<gt>{ substitutions }>.
735 MyApp->config->{ 'Plugin::ConfigLoader' }->{ substitutions } = {
736 baz => sub { my $c = shift; qux( @_ ); }
739 The above will respond to C<__baz(x,y)__> in config strings.
741 =head2 build_extensions_service
743 Config::Any's available config file extensions (e.g. xml, json, pl, etc).
745 =head2 build_prefix_service
747 The prefix, based on the application name, that will be used to lookup the
748 config files (which will be in the format $prefix.$extension). If the app is
749 MyApp::Foo, the prefix will be myapp_foo.
751 =head2 build_path_service
753 The path to the config file (or environment variable, if defined).
755 =head2 build_config_service
757 The resulting configuration for the application, after it has successfully
758 been loaded, and all substitutions have been made.
760 =head2 build_raw_config_service
762 The merge of local_config and global_config hashes, before substitutions.
764 =head2 build_global_files_service
766 Gets all files for config that don't have the local_suffix, such as myapp.conf.
768 =head2 build_local_files_service
770 Gets all files for config that have the local_suffix, such as myapp_local.conf.
772 =head2 build_global_config_service
774 Reads config from global_files.
776 =head2 build_local_config_service
778 Reads config from local_files.
780 =head2 build_config_path_service
782 Splits the path to the config file, and returns on array ref containing
783 the path to the config file minus the extension in the first position,
784 and the extension in the second.
786 =head2 build_config_local_suffix_service
788 Determines the suffix of files used to override the main config. By default
789 this value is C<local>, which will load C<myapp_local.conf>. The suffix can
790 be specified in the following order of preference:
794 =item * C<$ENV{ MYAPP_CONFIG_LOCAL_SUFFIX }>
796 =item * C<$ENV{ CATALYST_CONFIG_LOCAL_SUFFIX }>
800 The first one of these values found replaces the default of C<local> in the
801 name of the local config file to be loaded.
803 For example, if C< $ENV{ MYAPP_CONFIG_LOCAL_SUFFIX }> is set to C<testing>,
804 ConfigLoader will try and load C<myapp_testing.conf> instead of
807 =head2 build_locate_components_service
809 This method is meant to provide a list of component modules that should be
810 setup for the application. By default, it will use L<Module::Pluggable>.
812 Specify a C<setup_components> config option to pass additional options directly
813 to L<Module::Pluggable>.
817 =head2 get_component_from_sub_container($sub_container, $name, $c, @args)
819 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.
821 =head2 get_components_types
823 =head2 get_all_components
825 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.
829 Adds a component to the appropriate subcontainer. The subcontainer is guessed by the component name given.
831 =head2 find_component
833 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.
835 =head2 find_component_regexp
837 Finds components that match a given regexp. Used internally, by find_component.
839 =head2 $c->expand_component_module( $component, $setup_component_config )
841 Components found by C<locate_components> will be passed to this method, which
842 is expected to return a list of component (package) names to be set up.
844 =head2 setup_components
848 Catalyst Contributors, see Catalyst.pm
852 This library is free software. You can redistribute it and/or modify it under
853 the same terms as Perl itself.