merge gsoc_breadboard_moved_setup_component
[catagits/Catalyst-Runtime.git] / lib / Catalyst / IOC / Container.pm
CommitLineData
a6c13ff4 1package Catalyst::IOC::Container;
b4a6fa62 2use Bread::Board;
3use Moose;
4use Config::Any;
5use Data::Visitor::Callback;
6use Catalyst::Utils ();
b4410fc3 7use Hash::Util qw/lock_hash/;
2bb0da6d 8use MooseX::Types::LoadableClass qw/ LoadableClass /;
0dff29e2 9use Moose::Util;
a6c13ff4 10use Catalyst::IOC::BlockInjection;
8b749525 11use namespace::autoclean;
b4a6fa62 12
13extends 'Bread::Board::Container';
14
15has config_local_suffix => (
442ab13e 16 is => 'ro',
b4a6fa62 17 isa => 'Str',
18 default => 'local',
19);
20
21has driver => (
442ab13e 22 is => 'ro',
b4a6fa62 23 isa => 'HashRef',
24 default => sub { +{} },
25);
26
27has file => (
442ab13e 28 is => 'ro',
b4a6fa62 29 isa => 'Str',
30 default => '',
31);
32
33has substitutions => (
442ab13e 34 is => 'ro',
b4a6fa62 35 isa => 'HashRef',
36 default => sub { +{} },
37);
38
39has name => (
442ab13e 40 is => 'ro',
b4a6fa62 41 isa => 'Str',
a0146296 42 default => 'MyApp',
b4a6fa62 43);
44
2bb0da6d 45has sub_container_class => (
46 isa => LoadableClass,
47 is => 'ro',
48 coerce => 1,
a6c13ff4 49 default => 'Catalyst::IOC::SubContainer',
8b749525 50 handles => {
51 new_sub_container => 'new',
52 }
2bb0da6d 53);
54
b4a6fa62 55sub BUILD {
a2c0d071 56 my ( $self, $params ) = @_;
b4a6fa62 57
292277c1 58 $self->add_service(
59 $self->${\"build_${_}_service"}
60 ) for qw/
7451d1ea 61 substitutions
62 file
63 driver
64 name
65 prefix
66 extensions
67 path
68 config
69 raw_config
70 global_files
71 local_files
72 global_config
73 local_config
74 config_local_suffix
75 config_path
76 /;
f04816ce 77
292277c1 78 $self->add_sub_container(
a2c0d071 79 $self->build_controller_subcontainer
80 );
81
a0146296 82 # FIXME - the config should be merged at this point
2921bab3 83 my $config = $self->resolve( service => 'config' );
84 my $default_view = $params->{default_view} || $config->{default_view};
85 my $default_model = $params->{default_model} || $config->{default_model};
86
a2c0d071 87 $self->add_sub_container(
88 $self->build_view_subcontainer(
2921bab3 89 default_component => $default_view,
a2c0d071 90 )
91 );
92
93 $self->add_sub_container(
94 $self->build_model_subcontainer(
2921bab3 95 default_component => $default_model,
a2c0d071 96 )
97 );
f04816ce 98}
99
100sub build_model_subcontainer {
101 my $self = shift;
102
a2c0d071 103 return $self->new_sub_container( @_,
5a53ef3d 104 name => 'model',
b06ded69 105 );
f04816ce 106}
107
108sub build_view_subcontainer {
109 my $self = shift;
110
a2c0d071 111 return $self->new_sub_container( @_,
5a53ef3d 112 name => 'view',
b06ded69 113 );
f04816ce 114}
115
116sub build_controller_subcontainer {
117 my $self = shift;
118
b06ded69 119 return $self->new_sub_container(
5a53ef3d 120 name => 'controller',
b06ded69 121 );
f04816ce 122}
123
f04816ce 124sub build_name_service {
125 my $self = shift;
292277c1 126
127 return Bread::Board::Literal->new( name => 'name', value => $self->name );
f04816ce 128}
129
130sub build_driver_service {
131 my $self = shift;
292277c1 132
133 return Bread::Board::Literal->new( name => 'driver', value => $self->driver );
f04816ce 134}
135
136sub build_file_service {
137 my $self = shift;
292277c1 138
139 return Bread::Board::Literal->new( name => 'file', value => $self->file );
f04816ce 140}
141
142sub build_substitutions_service {
143 my $self = shift;
292277c1 144
145 return Bread::Board::Literal->new( name => 'substitutions', value => $self->substitutions );
f04816ce 146}
147
148sub build_extensions_service {
149 my $self = shift;
292277c1 150
151 return Bread::Board::BlockInjection->new(
152 name => 'extensions',
153 block => sub {
154 return \@{Config::Any->extensions};
155 },
f04816ce 156 );
157}
b4a6fa62 158
f04816ce 159sub build_prefix_service {
160 my $self = shift;
292277c1 161
162 return Bread::Board::BlockInjection->new(
163 name => 'prefix',
164 block => sub {
165 return Catalyst::Utils::appprefix( shift->param('name') );
166 },
167 dependencies => [ depends_on('name') ],
f04816ce 168 );
169}
b4a6fa62 170
f04816ce 171sub build_path_service {
172 my $self = shift;
292277c1 173
174 return Bread::Board::BlockInjection->new(
175 name => 'path',
176 block => sub {
177 my $s = shift;
178
179 return Catalyst::Utils::env_value( $s->param('name'), 'CONFIG' )
180 || $s->param('file')
181 || $s->param('name')->path_to( $s->param('prefix') );
182 },
183 dependencies => [ depends_on('file'), depends_on('name'), depends_on('prefix') ],
f04816ce 184 );
185}
b4a6fa62 186
f04816ce 187sub build_config_service {
188 my $self = shift;
292277c1 189
190 return Bread::Board::BlockInjection->new(
191 name => 'config',
192 block => sub {
193 my $s = shift;
194
195 my $v = Data::Visitor::Callback->new(
196 plain_value => sub {
197 return unless defined $_;
198 return $self->_config_substitutions( $s->param('name'), $s->param('substitutions'), $_ );
199 }
200
201 );
202 $v->visit( $s->param('raw_config') );
203 },
204 dependencies => [ depends_on('name'), depends_on('raw_config'), depends_on('substitutions') ],
f04816ce 205 );
206}
b4a6fa62 207
f04816ce 208sub build_raw_config_service {
209 my $self = shift;
292277c1 210
211 return Bread::Board::BlockInjection->new(
212 name => 'raw_config',
213 block => sub {
214 my $s = shift;
215
216 my @global = @{$s->param('global_config')};
217 my @locals = @{$s->param('local_config')};
218
219 my $config = {};
220 for my $cfg (@global, @locals) {
221 for (keys %$cfg) {
222 $config = Catalyst::Utils::merge_hashes( $config, $cfg->{$_} );
b4a6fa62 223 }
292277c1 224 }
225 return $config;
226 },
227 dependencies => [ depends_on('global_config'), depends_on('local_config') ],
f04816ce 228 );
229}
b4a6fa62 230
f04816ce 231sub build_global_files_service {
232 my $self = shift;
b4a6fa62 233
292277c1 234 return Bread::Board::BlockInjection->new(
235 name => 'global_files',
236 block => sub {
237 my $s = shift;
b4a6fa62 238
292277c1 239 my ( $path, $extension ) = @{$s->param('config_path')};
b4a6fa62 240
292277c1 241 my @extensions = @{$s->param('extensions')};
242
243 my @files;
244 if ( $extension ) {
245 die "Unable to handle files with the extension '${extension}'" unless grep { $_ eq $extension } @extensions;
246 push @files, $path;
247 } else {
248 @files = map { "$path.$_" } @extensions;
249 }
250 return \@files;
251 },
252 dependencies => [ depends_on('extensions'), depends_on('config_path') ],
f04816ce 253 );
254}
b4a6fa62 255
f04816ce 256sub build_local_files_service {
257 my $self = shift;
292277c1 258
259 return Bread::Board::BlockInjection->new(
260 name => 'local_files',
261 block => sub {
262 my $s = shift;
263
264 my ( $path, $extension ) = @{$s->param('config_path')};
265 my $suffix = $s->param('config_local_suffix');
266
267 my @extensions = @{$s->param('extensions')};
268
269 my @files;
270 if ( $extension ) {
271 die "Unable to handle files with the extension '${extension}'" unless grep { $_ eq $extension } @extensions;
272 $path =~ s{\.$extension}{_$suffix.$extension};
273 push @files, $path;
274 } else {
275 @files = map { "${path}_${suffix}.$_" } @extensions;
276 }
277 return \@files;
278 },
279 dependencies => [ depends_on('extensions'), depends_on('config_path'), depends_on('config_local_suffix') ],
f04816ce 280 );
281}
b4a6fa62 282
f04816ce 283sub build_global_config_service {
284 my $self = shift;
292277c1 285
286 return Bread::Board::BlockInjection->new(
287 name => 'global_config',
288 block => sub {
289 my $s = shift;
290
291 return Config::Any->load_files({
292 files => $s->param('global_files'),
293 filter => \&_fix_syntax,
294 use_ext => 1,
295 driver_args => $s->param('driver'),
296 });
297 },
298 dependencies => [ depends_on('global_files') ],
f04816ce 299 );
300}
b4a6fa62 301
f04816ce 302sub build_local_config_service {
303 my $self = shift;
292277c1 304
305 return Bread::Board::BlockInjection->new(
306 name => 'local_config',
307 block => sub {
308 my $s = shift;
309
310 return Config::Any->load_files({
311 files => $s->param('local_files'),
312 filter => \&_fix_syntax,
313 use_ext => 1,
314 driver_args => $s->param('driver'),
315 });
316 },
317 dependencies => [ depends_on('local_files') ],
f04816ce 318 );
319}
b4a6fa62 320
f04816ce 321sub build_config_path_service {
322 my $self = shift;
b4a6fa62 323
292277c1 324 return Bread::Board::BlockInjection->new(
325 name => 'config_path',
326 block => sub {
327 my $s = shift;
b4a6fa62 328
292277c1 329 my $path = $s->param('path');
330 my $prefix = $s->param('prefix');
b4a6fa62 331
292277c1 332 my ( $extension ) = ( $path =~ m{\.(.{1,4})$} );
333
334 if ( -d $path ) {
335 $path =~ s{[\/\\]$}{};
336 $path .= "/$prefix";
337 }
b4a6fa62 338
292277c1 339 return [ $path, $extension ];
340 },
341 dependencies => [ depends_on('prefix'), depends_on('path') ],
f04816ce 342 );
343}
b4a6fa62 344
f04816ce 345sub build_config_local_suffix_service {
346 my $self = shift;
292277c1 347
348 return Bread::Board::BlockInjection->new(
349 name => 'config_local_suffix',
350 block => sub {
351 my $s = shift;
352 my $suffix = Catalyst::Utils::env_value( $s->param('name'), 'CONFIG_LOCAL_SUFFIX' ) || $self->config_local_suffix;
353
354 return $suffix;
355 },
356 dependencies => [ depends_on('name') ],
f04816ce 357 );
b4a6fa62 358}
359
360sub _fix_syntax {
361 my $config = shift;
362 my @components = (
363 map +{
364 prefix => $_ eq 'Component' ? '' : $_ . '::',
365 values => delete $config->{ lc $_ } || delete $config->{ $_ }
366 },
367 grep { ref $config->{ lc $_ } || ref $config->{ $_ } }
368 qw( Component Model M View V Controller C Plugin )
369 );
370
371 foreach my $comp ( @components ) {
372 my $prefix = $comp->{ prefix };
373 foreach my $element ( keys %{ $comp->{ values } } ) {
374 $config->{ "$prefix$element" } = $comp->{ values }->{ $element };
375 }
376 }
377}
378
379sub _config_substitutions {
6682389c 380 my ( $self, $name, $subs, $arg ) = @_;
b4a6fa62 381
382 $subs->{ HOME } ||= sub { shift->path_to( '' ); };
383 $subs->{ ENV } ||=
384 sub {
385 my ( $c, $v ) = @_;
386 if (! defined($ENV{$v})) {
387 Catalyst::Exception->throw( message =>
388 "Missing environment variable: $v" );
389 return "";
390 } else {
391 return $ENV{ $v };
392 }
393 };
394 $subs->{ path_to } ||= sub { shift->path_to( @_ ); };
395 $subs->{ literal } ||= sub { return $_[ 1 ]; };
396 my $subsre = join( '|', keys %$subs );
397
6682389c 398 $arg =~ s{__($subsre)(?:\((.+?)\))?__}{ $subs->{ $1 }->( $name, $2 ? split( /,/, $2 ) : () ) }eg;
399 return $arg;
b4a6fa62 400}
401
a17e0ff8 402sub get_component_from_sub_container {
403 my ( $self, $sub_container_name, $name, $c, @args ) = @_;
404
405 my $sub_container = $self->get_sub_container( $sub_container_name );
406
0e747f0c 407 if (!$name) {
a2c0d071 408 my $default = $sub_container->default_component;
0e747f0c 409
410 return $sub_container->get_component( $default, $c, @args )
411 if $default && $sub_container->has_service( $default );
412
a0146296 413 # FIXME - should I be calling $c->log->warn here?
0e747f0c 414 # this is never a controller, so this is safe
415 $c->log->warn( "Calling \$c->$sub_container_name() is not supported unless you specify one of:" );
416 $c->log->warn( "* \$c->config(default_$sub_container_name => 'the name of the default $sub_container_name to use')" );
417 $c->log->warn( "* \$c->stash->{current_$sub_container_name} # the name of the view to use for this request" );
418 $c->log->warn( "* \$c->stash->{current_${sub_container_name}_instance} # the instance of the $sub_container_name to use for this request" );
a2c0d071 419
420 return;
0e747f0c 421 }
422
a17e0ff8 423 return $sub_container->get_component_regexp( $name, $c, @args )
424 if ref $name;
425
426 return $sub_container->get_component( $name, $c, @args )
427 if $sub_container->has_service( $name );
428
429 $c->log->warn(
430 "Attempted to use $sub_container_name '$name', " .
431 "but it does not exist"
432 );
433
434 return;
435}
436
c4aedec7 437sub find_component {
d0f954b4 438 my ( $self, $component, $c, @args ) = @_;
f147e6c2 439 my ( $type, $name ) = _get_component_type_name($component);
c4aedec7 440 my @result;
441
d0f954b4 442 return $self->get_component_from_sub_container(
443 $type, $name, $c, @args
444 ) if $type;
445
c4aedec7 446 my $query = ref $component
447 ? $component
448 : qr{^$component$}
449 ;
450
451 for my $subcontainer_name (qw/model view controller/) {
a0146296 452 my $subcontainer = $self->get_sub_container( $subcontainer_name );
c4aedec7 453 my @components = $subcontainer->get_service_list;
454 @result = grep { m{$component} } @components;
455
d0f954b4 456 return map { $subcontainer->get_component( $_, $c, @args ) } @result
c4aedec7 457 if @result;
458 }
459
a0146296 460 # FIXME - I guess I shouldn't be calling $c->components here
d0f954b4 461 # one last search for things like $c->comp(qr/::M::/)
462 @result = $self->find_component_regexp(
463 $c->components, $component, $c, @args
464 ) if !@result and ref $component;
465
c4aedec7 466 # it expects an empty list on failed searches
467 return @result;
468}
469
4e2b302e 470sub find_component_regexp {
471 my ( $self, $components, $component, @args ) = @_;
472 my @result;
473
474 my @components = grep { m{$component} } keys %{ $components };
475
476 for (@components) {
f147e6c2 477 my ($type, $name) = _get_component_type_name($_);
4e2b302e 478
479 push @result, $self->get_component_from_sub_container(
480 $type, $name, @args
481 ) if $type;
482 }
483
484 return @result;
485}
486
409db9cb 487# FIXME sorry for the name again :)
be80b0a5 488sub get_components_types {
489 my ( $self ) = @_;
490 my @comps_types;
491
492 for my $sub_container_name (qw/model view controller/) {
493 my $sub_container = $self->get_sub_container($sub_container_name);
494 for my $service ( $sub_container->get_service_list ) {
495 my $comp = $self->resolve(service => $service);
496 my $compname = ref $comp || $comp;
497 my $type = ref $comp ? 'instance' : 'class';
498 push @comps_types, [ $compname, $type ];
499 }
500 }
501
502 return @comps_types;
503}
c4aedec7 504
b4410fc3 505sub get_all_components {
506 my $self = shift;
507 my %components;
508
509 my $containers = {
510 map { $_ => $self->get_sub_container($_) } qw(model view controller)
511 };
512
513 for my $container (keys %$containers) {
514 for my $component ($containers->{$container}->get_service_list) {
515 my $comp = $containers->{$container}->resolve(
516 service => $component
517 );
518 my $comp_name = ref $comp || $comp;
519 $components{$comp_name} = $comp;
520 }
521 }
522
523 return lock_hash %components;
524}
525
f147e6c2 526sub add_component {
0dff29e2 527 my ( $self, $component, $class ) = @_;
f147e6c2 528 my ( $type, $name ) = _get_component_type_name($component);
529
530 return unless $type;
531
532 $self->get_sub_container($type)->add_service(
533 Catalyst::IOC::BlockInjection->new(
534 name => $name,
10c4d3b0 535 block => sub { $self->setup_component( $component, $class ) },
f147e6c2 536 )
537 );
538}
539
540# FIXME: should this sub exist?
541# should it be moved to Catalyst::Utils,
542# or replaced by something already existing there?
543sub _get_component_type_name {
544 my ( $component ) = @_;
545
546 my @parts = split /::/, $component;
547
548 while (my $type = shift @parts) {
549 return ('controller', join '::', @parts)
550 if $type =~ /^(c|controller)$/i;
551
552 return ('model', join '::', @parts)
553 if $type =~ /^(m|model)$/i;
554
555 return ('view', join '::', @parts)
556 if $type =~ /^(v|view)$/i;
557 }
558
559 return (undef, $component);
560}
561
0dff29e2 562# FIXME ugly and temporary
563# Just moved it here the way it was, so we can work on it here in the container
564sub setup_component {
10c4d3b0 565 my ( $self, $component, $class ) = @_;
0dff29e2 566
567 unless ( $component->can( 'COMPONENT' ) ) {
568 return $component;
569 }
570
571 # FIXME I know this isn't the "Dependency Injection" way of doing things,
572 # its just temporary
573 my $suffix = Catalyst::Utils::class2classsuffix( $component );
10c4d3b0 574 my $config = $self->resolve(service => 'config')->{ $suffix } || {};
0dff29e2 575
576 # Stash catalyst_component_name in the config here, so that custom COMPONENT
577 # methods also pass it. local to avoid pointlessly shitting in config
578 # for the debug screen, as $component is already the key name.
579 local $config->{catalyst_component_name} = $component;
580
581 my $instance = eval { $component->COMPONENT( $class, $config ); };
582
583 if ( my $error = $@ ) {
584 chomp $error;
585 Catalyst::Exception->throw(
586 message => qq/Couldn't instantiate component "$component", "$error"/
587 );
588 }
589 elsif (!blessed $instance) {
590 my $metaclass = Moose::Util::find_meta($component);
591 my $method_meta = $metaclass->find_method_by_name('COMPONENT');
592 my $component_method_from = $method_meta->associated_metaclass->name;
593 my $value = defined($instance) ? $instance : 'undef';
594 Catalyst::Exception->throw(
595 message =>
596 qq/Couldn't instantiate component "$component", COMPONENT() method (from $component_method_from) didn't return an object-like value (value was $value)./
597 );
598 }
599
600 return $instance;
601}
602
603
d057ddb9 6041;
605
606__END__
607
608=pod
609
610=head1 NAME
611
612Catalyst::Container - IOC for Catalyst components
613
2c2ed473 614=head1 SYNOPSIS
615
616=head1 DESCRIPTION
617
d057ddb9 618=head1 METHODS
619
a0146296 620=head1 Containers
621
d057ddb9 622=head2 build_model_subcontainer
623
a0146296 624Container that stores all models.
625
d057ddb9 626=head2 build_view_subcontainer
627
a0146296 628Container that stores all views.
629
d057ddb9 630=head2 build_controller_subcontainer
631
a0146296 632Container that stores all controllers.
633
634=head1 Services
635
d057ddb9 636=head2 build_name_service
637
a0146296 638Name of the application.
639
d057ddb9 640=head2 build_driver_service
641
a0146296 642Config options passed directly to the driver being used.
643
d057ddb9 644=head2 build_file_service
645
a0146296 646?
647
d057ddb9 648=head2 build_substitutions_service
649
a0146296 650Executes all the substitutions in config. See L</_config_substitutions> method.
651
d057ddb9 652=head2 build_extensions_service
653
654=head2 build_prefix_service
655
656=head2 build_path_service
657
658=head2 build_config_service
659
660=head2 build_raw_config_service
661
662=head2 build_global_files_service
663
664=head2 build_local_files_service
665
666=head2 build_global_config_service
667
668=head2 build_local_config_service
669
670=head2 build_config_path_service
671
672=head2 build_config_local_suffix_service
673
a0146296 674Determines the suffix of files used to override the main config. By default
675this value is C<local>, which will load C<myapp_local.conf>. The suffix can
676be specified in the following order of preference:
677
678=over
679
680=item * C<$ENV{ MYAPP_CONFIG_LOCAL_SUFFIX }>
681
682=item * C<$ENV{ CATALYST_CONFIG_LOCAL_SUFFIX }>
683
684=back
685
686The first one of these values found replaces the default of C<local> in the
687name of the local config file to be loaded.
688
689For example, if C< $ENV{ MYAPP_CONFIG_LOCAL_SUFFIX }> is set to C<testing>,
690ConfigLoader will try and load C<myapp_testing.conf> instead of
691C<myapp_local.conf>.
692
693=head2 get_component_from_sub_container($sub_container, $name, $c, @args)
694
695Looks 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.
8dc2fca3 696
409db9cb 697=head2 get_components_types
698
b4410fc3 699=head2 get_all_components
700
a0146296 701Fetches 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.
702
f147e6c2 703=head2 add_component
704
a0146296 705Adds a component to the appropriate subcontainer. The subcontainer is guessed by the component name given.
706
c4aedec7 707=head2 find_component
708
a0146296 709Searches 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.
710
4e2b302e 711=head2 find_component_regexp
712
a0146296 713Finds components that match a given regexp. Used internally, by find_component.
714
0dff29e2 715=head2 setup_component
716
d057ddb9 717=head2 _fix_syntax
718
719=head2 _config_substitutions
720
a0146296 721This method substitutes macros found with calls to a function. There are a
722number of default macros:
723
724=over
725
726=item * C<__HOME__> - replaced with C<$c-E<gt>path_to('')>
727
728=item * C<__ENV(foo)__> - replaced with the value of C<$ENV{foo}>
729
730=item * C<__path_to(foo/bar)__> - replaced with C<$c-E<gt>path_to('foo/bar')>
731
732=item * C<__literal(__FOO__)__> - leaves __FOO__ alone (allows you to use
733C<__DATA__> as a config value, for example)
734
735=back
736
737The parameter list is split on comma (C<,>). You can override this method to
738do your own string munging, or you can define your own macros in
739C<MyApp-E<gt>config-E<gt>{ 'Plugin::ConfigLoader' }-E<gt>{ substitutions }>.
740Example:
741
742 MyApp->config->{ 'Plugin::ConfigLoader' }->{ substitutions } = {
743 baz => sub { my $c = shift; qux( @_ ); }
744 }
745
746The above will respond to C<__baz(x,y)__> in config strings.
747
bf3c8088 748=head1 AUTHORS
749
e8ed391e 750Catalyst Contributors, see Catalyst.pm
bf3c8088 751
e8ed391e 752=head1 COPYRIGHT
bf3c8088 753
754This library is free software. You can redistribute it and/or modify it under
755the same terms as Perl itself.
756
757=cut