fixme and warning
[catagits/Catalyst-Runtime.git] / lib / Catalyst / IOC / Container.pm
1 package Catalyst::IOC::Container;
2 use Bread::Board;
3 use Moose;
4 use Config::Any;
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 /;
11 use Moose::Util;
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;
17
18 extends 'Bread::Board::Container';
19
20 has config_local_suffix => (
21     is      => 'ro',
22     isa     => 'Str',
23     default => 'local',
24 );
25
26 has driver => (
27     is      => 'ro',
28     isa     => 'HashRef',
29     default => sub { +{} },
30 );
31
32 has file => (
33     is      => 'ro',
34     isa     => 'Str',
35     default => '',
36 );
37
38 has substitutions => (
39     is      => 'ro',
40     isa     => 'HashRef',
41     default => sub { +{} },
42 );
43
44 has application_name => (
45     is       => 'ro',
46     isa      => 'Str',
47     required => 1,
48 );
49
50 has sub_container_class => (
51     isa     => LoadableClass,
52     is      => 'ro',
53     coerce  => 1,
54     default => 'Catalyst::IOC::SubContainer',
55     handles => {
56         new_sub_container => 'new',
57     }
58 );
59
60 sub BUILD {
61     my ( $self, $params ) = @_;
62
63     $self->add_service(
64         $self->${\"build_${_}_service"}
65     ) for qw/
66         substitutions
67         file
68         driver
69         application_name
70         prefix
71         extensions
72         path
73         config
74         raw_config
75         global_files
76         local_files
77         global_config
78         local_config
79         class_config
80         config_local_suffix
81         config_path
82         locate_components
83     /;
84
85     my $config = $self->resolve( service => 'config' );
86
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} )
90                       : ( )
91                       ;
92     my @default_model = $config->{default_model}
93                       ? ( default_component => $config->{default_model} )
94                       : ( )
95                       ;
96
97     $self->add_sub_container(
98         $self->build_component_subcontainer
99     );
100
101     $self->add_sub_container(
102         $self->build_controller_subcontainer
103     );
104
105     $self->add_sub_container(
106         $self->build_view_subcontainer( @default_view )
107     );
108
109     $self->add_sub_container(
110         $self->build_model_subcontainer( @default_model )
111     );
112
113     {
114         no strict 'refs';
115         no warnings 'once';
116         my $class = ref $self;
117         warn("In build $class");
118         ${ $class . '::customise_container' }->($self)
119             if ${ $class . '::customise_container' };
120     }
121 }
122
123 sub build_model_subcontainer {
124     my $self = shift;
125
126     return $self->new_sub_container( @_,
127         name => 'model',
128     );
129 }
130
131 sub build_view_subcontainer {
132     my $self = shift;
133
134     return $self->new_sub_container( @_,
135         name => 'view',
136     );
137 }
138
139 sub build_controller_subcontainer {
140     my $self = shift;
141
142     return $self->new_sub_container(
143         name => 'controller',
144     );
145 }
146
147 sub build_component_subcontainer {
148     my $self = shift;
149
150     return Bread::Board::Container->new(
151         name => 'component',
152     );
153 }
154
155 sub build_application_name_service {
156     my $self = shift;
157
158     return Bread::Board::Literal->new( name => 'application_name', value => $self->application_name );
159 }
160
161 sub build_driver_service {
162     my $self = shift;
163
164     return Bread::Board::Literal->new( name => 'driver', value => $self->driver );
165 }
166
167 sub build_file_service {
168     my $self = shift;
169
170     return Bread::Board::Literal->new( name => 'file', value => $self->file );
171 }
172
173 sub build_substitutions_service {
174     my $self = shift;
175
176     return Bread::Board::Literal->new( name => 'substitutions', value => $self->substitutions );
177 }
178
179 sub build_extensions_service {
180     my $self = shift;
181
182     return Bread::Board::BlockInjection->new(
183         lifecycle => 'Singleton',
184         name => 'extensions',
185         block => sub {
186             return \@{Config::Any->extensions};
187         },
188     );
189 }
190
191 sub build_prefix_service {
192     my $self = shift;
193
194     return Bread::Board::BlockInjection->new(
195         lifecycle => 'Singleton',
196         name => 'prefix',
197         block => sub {
198             return Catalyst::Utils::appprefix( shift->param('application_name') );
199         },
200         dependencies => [ depends_on('application_name') ],
201     );
202 }
203
204 sub build_path_service {
205     my $self = shift;
206
207     return Bread::Board::BlockInjection->new(
208         lifecycle => 'Singleton',
209         name => 'path',
210         block => sub {
211             my $s = shift;
212
213             return Catalyst::Utils::env_value( $s->param('application_name'), 'CONFIG' )
214             || $s->param('file')
215             || $s->param('application_name')->path_to( $s->param('prefix') );
216         },
217         dependencies => [ depends_on('file'), depends_on('application_name'), depends_on('prefix') ],
218     );
219 }
220
221 sub build_config_service {
222     my $self = shift;
223
224     return Bread::Board::BlockInjection->new(
225         lifecycle => 'Singleton',
226         name => 'config',
227         block => sub {
228             my $s = shift;
229
230             my $v = Data::Visitor::Callback->new(
231                 plain_value => sub {
232                     return unless defined $_;
233                     return $self->_config_substitutions( $s->param('application_name'), $s->param('substitutions'), $_ );
234                 }
235
236             );
237             $v->visit( $s->param('raw_config') );
238         },
239         dependencies => [ depends_on('application_name'), depends_on('raw_config'), depends_on('substitutions') ],
240     );
241 }
242
243 sub build_raw_config_service {
244     my $self = shift;
245
246     return Bread::Board::BlockInjection->new(
247         lifecycle => 'Singleton',
248         name => 'raw_config',
249         block => sub {
250             my $s = shift;
251
252             my @global = @{$s->param('global_config')};
253             my @locals = @{$s->param('local_config')};
254
255             my $config = $s->param('class_config');
256
257             for my $cfg (@global, @locals) {
258                 for (keys %$cfg) {
259                     $config = Catalyst::Utils::merge_hashes( $config, $cfg->{$_} );
260                 }
261             }
262
263             return $config;
264         },
265         dependencies => [ depends_on('global_config'), depends_on('local_config'), depends_on('class_config') ],
266     );
267 }
268
269 sub build_global_files_service {
270     my $self = shift;
271
272     return Bread::Board::BlockInjection->new(
273         lifecycle => 'Singleton',
274         name => 'global_files',
275         block => sub {
276             my $s = shift;
277
278             my ( $path, $extension ) = @{$s->param('config_path')};
279
280             my @extensions = @{$s->param('extensions')};
281
282             my @files;
283             if ( $extension ) {
284                 die "Unable to handle files with the extension '${extension}'" unless grep { $_ eq $extension } @extensions;
285                 push @files, $path;
286             } else {
287                 @files = map { "$path.$_" } @extensions;
288             }
289             return \@files;
290         },
291         dependencies => [ depends_on('extensions'), depends_on('config_path') ],
292     );
293 }
294
295 sub build_local_files_service {
296     my $self = shift;
297
298     return Bread::Board::BlockInjection->new(
299         lifecycle => 'Singleton',
300         name => 'local_files',
301         block => sub {
302             my $s = shift;
303
304             my ( $path, $extension ) = @{$s->param('config_path')};
305             my $suffix = $s->param('config_local_suffix');
306
307             my @extensions = @{$s->param('extensions')};
308
309             my @files;
310             if ( $extension ) {
311                 die "Unable to handle files with the extension '${extension}'" unless grep { $_ eq $extension } @extensions;
312                 $path =~ s{\.$extension}{_$suffix.$extension};
313                 push @files, $path;
314             } else {
315                 @files = map { "${path}_${suffix}.$_" } @extensions;
316             }
317             return \@files;
318         },
319         dependencies => [ depends_on('extensions'), depends_on('config_path'), depends_on('config_local_suffix') ],
320     );
321 }
322
323 sub build_class_config_service {
324     my $self = shift;
325
326     return Bread::Board::BlockInjection->new(
327         lifecycle => 'Singleton',
328         name => 'class_config',
329         block => sub {
330             my $s   = shift;
331             my $app = $s->param('application_name');
332
333             # Container might be called outside Catalyst context
334             return {} unless Class::MOP::is_class_loaded($app);
335
336             # config might not have been defined
337             return $app->config || {};
338         },
339         dependencies => [ depends_on('application_name') ],
340     );
341 }
342
343 sub build_global_config_service {
344     my $self = shift;
345
346     return Bread::Board::BlockInjection->new(
347         lifecycle => 'Singleton',
348         name => 'global_config',
349         block => sub {
350             my $s = shift;
351
352             return Config::Any->load_files({
353                 files       => $s->param('global_files'),
354                 filter      => \&_fix_syntax,
355                 use_ext     => 1,
356                 driver_args => $s->param('driver'),
357             });
358         },
359         dependencies => [ depends_on('global_files') ],
360     );
361 }
362
363 sub build_local_config_service {
364     my $self = shift;
365
366     return Bread::Board::BlockInjection->new(
367         lifecycle => 'Singleton',
368         name => 'local_config',
369         block => sub {
370             my $s = shift;
371
372             return Config::Any->load_files({
373                 files       => $s->param('local_files'),
374                 filter      => \&_fix_syntax,
375                 use_ext     => 1,
376                 driver_args => $s->param('driver'),
377             });
378         },
379         dependencies => [ depends_on('local_files') ],
380     );
381 }
382
383 sub build_config_path_service {
384     my $self = shift;
385
386     return Bread::Board::BlockInjection->new(
387         lifecycle => 'Singleton',
388         name => 'config_path',
389         block => sub {
390             my $s = shift;
391
392             my $path = $s->param('path');
393             my $prefix = $s->param('prefix');
394
395             my ( $extension ) = ( $path =~ m{\.(.{1,4})$} );
396
397             if ( -d $path ) {
398                 $path =~ s{[\/\\]$}{};
399                 $path .= "/$prefix";
400             }
401
402             return [ $path, $extension ];
403         },
404         dependencies => [ depends_on('prefix'), depends_on('path') ],
405     );
406 }
407
408 sub build_config_local_suffix_service {
409     my $self = shift;
410
411     return Bread::Board::BlockInjection->new(
412         lifecycle => 'Singleton',
413         name => 'config_local_suffix',
414         block => sub {
415             my $s = shift;
416             my $suffix = Catalyst::Utils::env_value( $s->param('application_name'), 'CONFIG_LOCAL_SUFFIX' ) || $self->config_local_suffix;
417
418             return $suffix;
419         },
420         dependencies => [ depends_on('application_name') ],
421     );
422 }
423
424 sub build_locate_components_service {
425     my $self = shift;
426
427     return Bread::Board::BlockInjection->new(
428         lifecycle => 'Singleton',
429         name      => 'locate_components',
430         block     => sub {
431             my $s      = shift;
432             my $class  = $s->param('application_name');
433             my $config = $s->param('config')->{ setup_components };
434
435             Catalyst::Exception->throw(
436                 qq{You are using search_extra config option. That option is\n} .
437                 qq{deprecated, please refer to the documentation for\n} .
438                 qq{other ways of achieving the same results.\n}
439             ) if delete $config->{ search_extra };
440
441             my @paths = qw( ::Controller ::C ::Model ::M ::View ::V );
442
443             my $locator = Module::Pluggable::Object->new(
444                 search_path => [ map { s/^(?=::)/$class/; $_; } @paths ],
445                 %$config
446             );
447
448             return [ $locator->plugins ];
449         },
450         dependencies => [ depends_on('application_name'), depends_on('config') ],
451     );
452 }
453
454 sub setup_components {
455     my $self = shift;
456     warn("Setting up default components");
457     my $class = $self->resolve( service => 'application_name' );
458     my @comps = @{ $self->resolve( service => 'locate_components' ) };
459     my %comps = map { $_ => 1 } @comps;
460     my $deprecatedcatalyst_component_names = 0;
461
462     my $app_locate_components_addr = refaddr(
463         $class->can('locate_components')
464     );
465     my $cat_locate_components_addr = refaddr(
466         Catalyst->can('locate_components')
467     );
468
469     if ($app_locate_components_addr != $cat_locate_components_addr) {
470         $class->log->warn(qq{You have overridden locate_components. That } .
471             qq{no longer works. Please refer to the documentation to achieve } .
472             qq{similar results.\n}
473         );
474     }
475
476     for my $component ( @comps ) {
477
478         # We pass ignore_loaded here so that overlay files for (e.g.)
479         # Model::DBI::Schema sub-classes are loaded - if it's in @comps
480         # we know M::P::O found a file on disk so this is safe
481
482         Catalyst::Utils::ensure_class_loaded( $component, { ignore_loaded => 1 } );
483     }
484
485     for my $component (@comps) {
486         $self->add_component( $component );
487         # FIXME - $instance->expand_modules() is broken
488         my @expanded_components = $self->expand_component_module( $component );
489
490         if (
491             !$deprecatedcatalyst_component_names &&
492             ($deprecatedcatalyst_component_names = $component =~ m/::[CMV]::/) ||
493             ($deprecatedcatalyst_component_names = grep { /::[CMV]::/ } @expanded_components)
494         ) {
495             # FIXME - should I be calling warn here?
496             # Maybe it's time to remove it, or become fatal
497             $class->log->warn(qq{Your application is using the deprecated ::[MVC]:: type naming scheme.\n}.
498                 qq{Please switch your class names to ::Model::, ::View:: and ::Controller: as appropriate.\n}
499             );
500         }
501
502         for my $component (@expanded_components) {
503             $self->add_component( $component )
504                 unless $comps{$component};
505         }
506     }
507 }
508
509 sub _fix_syntax {
510     my $config     = shift;
511     my @components = (
512         map +{
513             prefix => $_ eq 'Component' ? '' : $_ . '::',
514             values => delete $config->{ lc $_ } || delete $config->{ $_ }
515         },
516         grep { ref $config->{ lc $_ } || ref $config->{ $_ } }
517             qw( Component Model M View V Controller C Plugin )
518     );
519
520     foreach my $comp ( @components ) {
521         my $prefix = $comp->{ prefix };
522         foreach my $element ( keys %{ $comp->{ values } } ) {
523             $config->{ "$prefix$element" } = $comp->{ values }->{ $element };
524         }
525     }
526 }
527
528 sub _config_substitutions {
529     my ( $self, $name, $subs, $arg ) = @_;
530
531     $subs->{ HOME } ||= sub { shift->path_to( '' ); };
532     $subs->{ ENV } ||=
533         sub {
534             my ( $c, $v ) = @_;
535             if (! defined($ENV{$v})) {
536                 Catalyst::Exception->throw( message =>
537                     "Missing environment variable: $v" );
538                 return "";
539             } else {
540                 return $ENV{ $v };
541             }
542         };
543     $subs->{ path_to } ||= sub { shift->path_to( @_ ); };
544     $subs->{ literal } ||= sub { return $_[ 1 ]; };
545     my $subsre = join( '|', keys %$subs );
546
547     $arg =~ s{__($subsre)(?:\((.+?)\))?__}{ $subs->{ $1 }->( $name, $2 ? split( /,/, $2 ) : () ) }eg;
548     return $arg;
549 }
550
551 sub get_component_from_sub_container {
552     my ( $self, $sub_container_name, $name, $c, @args ) = @_;
553
554     my $sub_container = $self->get_sub_container( $sub_container_name );
555
556     if (!$name) {
557         my $default = $sub_container->default_component;
558
559         return $sub_container->get_component( $default, $c, @args )
560             if $default && $sub_container->has_service( $default );
561
562         # FIXME - should I be calling $c->log->warn here?
563         # this is never a controller, so this is safe
564         $c->log->warn( "Calling \$c->$sub_container_name() is not supported unless you specify one of:" );
565         $c->log->warn( "* \$c->config(default_$sub_container_name => 'the name of the default $sub_container_name to use')" );
566         $c->log->warn( "* \$c->stash->{current_$sub_container_name} # the name of the view to use for this request" );
567         $c->log->warn( "* \$c->stash->{current_${sub_container_name}_instance} # the instance of the $sub_container_name to use for this request" );
568
569         return;
570     }
571
572     return $sub_container->get_component_regexp( $name, $c, @args )
573         if ref $name;
574
575     return $sub_container->get_component( $name, $c, @args )
576         if $sub_container->has_service( $name );
577
578     $c->log->warn(
579         "Attempted to use $sub_container_name '$name', " .
580         "but it does not exist"
581     );
582
583     return;
584 }
585
586 sub find_component {
587     my ( $self, $component, @args ) = @_;
588     my ( $type, $name ) = _get_component_type_name($component);
589     my @result;
590
591     return $self->get_component_from_sub_container(
592         $type, $name, @args
593     ) if $type;
594
595     my $query = ref $component
596               ? $component
597               : qr{^$component$}
598               ;
599
600     for my $subcontainer_name (qw/model view controller/) {
601         my $subcontainer = $self->get_sub_container( $subcontainer_name );
602         my @components   = $subcontainer->get_service_list;
603         @result          = grep { m{$component} } @components;
604
605         return map { $subcontainer->get_component( $_, @args ) } @result
606             if @result;
607     }
608
609     # one last search for things like $c->comp(qr/::M::/)
610     @result = $self->_find_component_regexp(
611         $component, @args
612     ) if !@result and ref $component;
613
614     # it expects an empty list on failed searches
615     return @result;
616 }
617
618 sub _find_component_regexp {
619     my ( $self, $component, $ctx, @args ) = @_;
620     my @result;
621
622     my @components = grep { m{$component} } keys %{ $self->get_all_components($ctx) };
623
624     for (@components) {
625         my ($type, $name) = _get_component_type_name($_);
626
627         push @result, $self->get_component_from_sub_container(
628             $type, $name, $ctx, @args
629         ) if $type;
630     }
631
632     return @result;
633 }
634
635 sub get_all_components {
636     my ($self, $class) = @_;
637     my %components;
638
639     # FIXME - if we're getting from these containers, we need to either:
640     #   - pass 'ctx' and 'accept_context_args' OR
641     #   - make these params optional
642     # big problem when setting up the dispatcher - this method is called
643     # as $container->get_all_components('MyApp'). What to do with Request
644     # life cycles?
645     foreach my $type (qw/model view controller /) {
646         my $container = $self->get_sub_container($type);
647
648         for my $component ($container->get_service_list) {
649             my $comp_service = $container->get_service($component);
650
651             warn "getting  $component in $class, type $type";
652
653             $components{$comp_service->catalyst_component_name} = $comp_service->get(ctx => $class);
654         }
655     }
656
657     return lock_hash %components;
658 }
659
660 sub add_component {
661     my ( $self, $component ) = @_;
662     my ( $type, $name ) = _get_component_type_name($component);
663
664     return unless $type;
665
666     # The 'component' sub-container will create the object, and store it's
667     # instance, which, by default, will live throughout the application.
668     # The model/view/controller sub-containers only reference the instance
669     # held in the aforementioned sub-container, and execute the ACCEPT_CONTEXT
670     # sub every time they are called, when it exists.
671     my $instance_container       = $self->get_sub_container('component');
672     my $accept_context_container = $self->get_sub_container($type);
673
674     # Custom containers might have added the service already
675     # We don't want to override that
676     return if $accept_context_container->has_service( $name );
677
678     my $component_service_name = "${type}_${name}";
679
680     $instance_container->add_service(
681         Catalyst::IOC::ConstructorInjection->new(
682             name      => $component_service_name,
683             catalyst_component_name => $component,
684             class     => $component,
685             lifecycle => 'Singleton',
686             dependencies => [
687                 depends_on( '/application_name' ),
688             ],
689         )
690     );
691
692     $accept_context_container->add_service(
693         Catalyst::IOC::BlockInjection->new(
694             name         => $name,
695             catalyst_component_name => $component,
696             dependencies => [
697                 depends_on( "/component/$component_service_name" ),
698             ],
699             block => sub { shift->param($component_service_name) },
700         )
701     );
702 }
703
704 # FIXME: should this sub exist?
705 # should it be moved to Catalyst::Utils,
706 # or replaced by something already existing there?
707 sub _get_component_type_name {
708     my ( $component ) = @_;
709     my $result;
710
711     while ( !$result and (my $index = index $component, '::') > 0 ) {
712         my $type   = lc substr $component, 0, $index;
713         $component = substr $component, $index + 2;
714         $result    = first { $type eq $_ or $type eq substr($_, 0, 1) }
715                          qw{ model view controller };
716     }
717
718     return ($result, $component);
719 }
720
721 sub expand_component_module {
722     my ( $class, $module ) = @_;
723     return Devel::InnerPackage::list_packages( $module );
724 }
725
726 1;
727
728 __END__
729
730 =pod
731
732 =head1 NAME
733
734 Catalyst::Container - IOC for Catalyst components
735
736 =head1 SYNOPSIS
737
738 =head1 DESCRIPTION
739
740 =head1 METHODS
741
742 =head1 Building Containers
743
744 =head2 build_component_subcontainer
745
746 Container that stores all components, i.e. all models, views and controllers
747 together. Each service is an instance of the actual component, and by default
748 it lives while the application is running. Retrieving components from this
749 subcontainer will instantiate the component, if it hasn't been instantiated
750 already, but will not execute ACCEPT_CONTEXT.
751
752 =head2 build_model_subcontainer
753
754 Container that stores references for all models that are inside the components
755 subcontainer. Retrieving a model triggers ACCEPT_CONTEXT, if it exists.
756
757 =head2 build_view_subcontainer
758
759 Same as L<build_model_subcontainer>, but for views.
760
761 =head2 build_controller_subcontainer
762
763 Same as L<build_model_subcontainer>, but for controllers.
764
765 =head1 Building Services
766
767 =head2 build_application_name_service
768
769 Name of the application (such as MyApp).
770
771 =head2 build_driver_service
772
773 Config options passed directly to the driver being used.
774
775 =head2 build_file_service
776
777 ?
778
779 =head2 build_substitutions_service
780
781 This method substitutes macros found with calls to a function. There are a
782 number of default macros:
783
784 =over
785
786 =item * C<__HOME__> - replaced with C<$c-E<gt>path_to('')>
787
788 =item * C<__ENV(foo)__> - replaced with the value of C<$ENV{foo}>
789
790 =item * C<__path_to(foo/bar)__> - replaced with C<$c-E<gt>path_to('foo/bar')>
791
792 =item * C<__literal(__FOO__)__> - leaves __FOO__ alone (allows you to use
793 C<__DATA__> as a config value, for example)
794
795 =back
796
797 The parameter list is split on comma (C<,>). You can override this method to
798 do your own string munging, or you can define your own macros in
799 C<< <MyApp->config( 'Plugin::ConfigLoader' => { substitutions => { ... } } ) >>.
800 Example:
801
802     MyApp->config( 'Plugin::ConfigLoader' => {
803         substitutions => {
804             baz => sub { my $c = shift; qux( @_ ); },
805         },
806     });
807
808 The above will respond to C<__baz(x,y)__> in config strings.
809
810 =head2 build_extensions_service
811
812 Config::Any's available config file extensions (e.g. xml, json, pl, etc).
813
814 =head2 build_prefix_service
815
816 The prefix, based on the application name, that will be used to lookup the
817 config files (which will be in the format $prefix.$extension). If the app is
818 MyApp::Foo, the prefix will be myapp_foo.
819
820 =head2 build_path_service
821
822 The path to the config file (or environment variable, if defined).
823
824 =head2 build_config_service
825
826 The resulting configuration for the application, after it has successfully
827 been loaded, and all substitutions have been made.
828
829 =head2 build_raw_config_service
830
831 The merge of local_config and global_config hashes, before substitutions.
832
833 =head2 build_global_files_service
834
835 Gets all files for config that don't have the local_suffix, such as myapp.conf.
836
837 =head2 build_local_files_service
838
839 Gets all files for config that have the local_suffix, such as myapp_local.conf.
840
841 =head2 build_global_config_service
842
843 Reads config from global_files.
844
845 =head2 build_local_config_service
846
847 Reads config from local_files.
848
849 =head2 build_class_config_service
850
851 Reads config set from the application's class attribute config,
852 i.e. MyApp->config( name => 'MyApp', ... )
853
854 =head2 build_config_path_service
855
856 Splits the path to the config file, and returns on array ref containing
857 the path to the config file minus the extension in the first position,
858 and the extension in the second.
859
860 =head2 build_config_local_suffix_service
861
862 Determines the suffix of files used to override the main config. By default
863 this value is C<local>, which will load C<myapp_local.conf>.  The suffix can
864 be specified in the following order of preference:
865
866 =over
867
868 =item * C<$ENV{ MYAPP_CONFIG_LOCAL_SUFFIX }>
869
870 =item * C<$ENV{ CATALYST_CONFIG_LOCAL_SUFFIX }>
871
872 =back
873
874 The first one of these values found replaces the default of C<local> in the
875 name of the local config file to be loaded.
876
877 For example, if C< $ENV{ MYAPP_CONFIG_LOCAL_SUFFIX }> is set to C<testing>,
878 ConfigLoader will try and load C<myapp_testing.conf> instead of
879 C<myapp_local.conf>.
880
881 =head2 build_locate_components_service
882
883 This method is meant to provide a list of component modules that should be
884 setup for the application.  By default, it will use L<Module::Pluggable>.
885
886 Specify a C<setup_components> config option to pass additional options directly
887 to L<Module::Pluggable>.
888
889 =head1 Other methods
890
891 =head2 get_component_from_sub_container($sub_container, $name, $c, @args)
892
893 Looks for components in a given subcontainer (such as controller, model or
894 view), and returns the searched component. If $name is undef, it returns the
895 default component (such as default_view, if $sub_container is 'view'). If
896 $name is a regexp, it returns an array of matching components. Otherwise, it
897 looks for the component with name $name.
898
899 =head2 get_all_components
900
901 Fetches all the components, in each of the sub_containers model, view and
902 controller, and returns a readonly hash. The keys are the class names, and
903 the values are the blessed objects. This is what is returned by $c->components.
904
905 =head2 add_component
906
907 Adds a component to the appropriate subcontainer. The subcontainer is guessed
908 by the component name given.
909
910 =head2 find_component
911
912 Searches for components in all containers. If $component is the full class
913 name, the subcontainer is guessed, and it gets the searched component in there.
914 Otherwise, it looks for a component with that name in all subcontainers. If
915 $component is a regexp it calls _find_component_regexp and matches all
916 components against that regexp.
917
918 =head2 expand_component_module
919
920 Components found by C<locate_components> will be passed to this method, which
921 is expected to return a list of component (package) names to be set up.
922
923 =head2 setup_components
924
925 =head1 AUTHORS
926
927 Catalyst Contributors, see Catalyst.pm
928
929 =head1 COPYRIGHT
930
931 This library is free software. You can redistribute it and/or modify it under
932 the same terms as Perl itself.
933
934 =cut