moved code block from ->components to the container
[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 /;
a6c13ff4 9use Catalyst::IOC::BlockInjection;
8b749525 10use namespace::autoclean;
b4a6fa62 11
12extends 'Bread::Board::Container';
13
14has config_local_suffix => (
442ab13e 15 is => 'ro',
b4a6fa62 16 isa => 'Str',
17 default => 'local',
18);
19
20has driver => (
442ab13e 21 is => 'ro',
b4a6fa62 22 isa => 'HashRef',
23 default => sub { +{} },
24);
25
26has file => (
442ab13e 27 is => 'ro',
b4a6fa62 28 isa => 'Str',
29 default => '',
30);
31
32has substitutions => (
442ab13e 33 is => 'ro',
b4a6fa62 34 isa => 'HashRef',
35 default => sub { +{} },
36);
37
38has name => (
442ab13e 39 is => 'ro',
b4a6fa62 40 isa => 'Str',
41 default => 'TestApp',
42);
43
2bb0da6d 44has sub_container_class => (
45 isa => LoadableClass,
46 is => 'ro',
47 coerce => 1,
a6c13ff4 48 default => 'Catalyst::IOC::SubContainer',
8b749525 49 handles => {
50 new_sub_container => 'new',
51 }
2bb0da6d 52);
53
b4a6fa62 54sub BUILD {
a2c0d071 55 my ( $self, $params ) = @_;
b4a6fa62 56
292277c1 57 $self->add_service(
58 $self->${\"build_${_}_service"}
59 ) for qw/
7451d1ea 60 substitutions
61 file
62 driver
63 name
64 prefix
65 extensions
66 path
67 config
68 raw_config
69 global_files
70 local_files
71 global_config
72 local_config
73 config_local_suffix
74 config_path
75 /;
f04816ce 76
292277c1 77 $self->add_sub_container(
a2c0d071 78 $self->build_controller_subcontainer
79 );
80
2921bab3 81 my $config = $self->resolve( service => 'config' );
82 my $default_view = $params->{default_view} || $config->{default_view};
83 my $default_model = $params->{default_model} || $config->{default_model};
84
a2c0d071 85 $self->add_sub_container(
86 $self->build_view_subcontainer(
2921bab3 87 default_component => $default_view,
a2c0d071 88 )
89 );
90
91 $self->add_sub_container(
92 $self->build_model_subcontainer(
2921bab3 93 default_component => $default_model,
a2c0d071 94 )
95 );
f04816ce 96}
97
98sub build_model_subcontainer {
99 my $self = shift;
100
a2c0d071 101 return $self->new_sub_container( @_,
5a53ef3d 102 name => 'model',
b06ded69 103 );
f04816ce 104}
105
106sub build_view_subcontainer {
107 my $self = shift;
108
a2c0d071 109 return $self->new_sub_container( @_,
5a53ef3d 110 name => 'view',
b06ded69 111 );
f04816ce 112}
113
114sub build_controller_subcontainer {
115 my $self = shift;
116
b06ded69 117 return $self->new_sub_container(
5a53ef3d 118 name => 'controller',
b06ded69 119 );
f04816ce 120}
121
f04816ce 122sub build_name_service {
123 my $self = shift;
292277c1 124
125 return Bread::Board::Literal->new( name => 'name', value => $self->name );
f04816ce 126}
127
128sub build_driver_service {
129 my $self = shift;
292277c1 130
131 return Bread::Board::Literal->new( name => 'driver', value => $self->driver );
f04816ce 132}
133
134sub build_file_service {
135 my $self = shift;
292277c1 136
137 return Bread::Board::Literal->new( name => 'file', value => $self->file );
f04816ce 138}
139
140sub build_substitutions_service {
141 my $self = shift;
292277c1 142
143 return Bread::Board::Literal->new( name => 'substitutions', value => $self->substitutions );
f04816ce 144}
145
146sub build_extensions_service {
147 my $self = shift;
292277c1 148
149 return Bread::Board::BlockInjection->new(
150 name => 'extensions',
151 block => sub {
152 return \@{Config::Any->extensions};
153 },
f04816ce 154 );
155}
b4a6fa62 156
f04816ce 157sub build_prefix_service {
158 my $self = shift;
292277c1 159
160 return Bread::Board::BlockInjection->new(
161 name => 'prefix',
162 block => sub {
163 return Catalyst::Utils::appprefix( shift->param('name') );
164 },
165 dependencies => [ depends_on('name') ],
f04816ce 166 );
167}
b4a6fa62 168
f04816ce 169sub build_path_service {
170 my $self = shift;
292277c1 171
172 return Bread::Board::BlockInjection->new(
173 name => 'path',
174 block => sub {
175 my $s = shift;
176
177 return Catalyst::Utils::env_value( $s->param('name'), 'CONFIG' )
178 || $s->param('file')
179 || $s->param('name')->path_to( $s->param('prefix') );
180 },
181 dependencies => [ depends_on('file'), depends_on('name'), depends_on('prefix') ],
f04816ce 182 );
183}
b4a6fa62 184
f04816ce 185sub build_config_service {
186 my $self = shift;
292277c1 187
188 return Bread::Board::BlockInjection->new(
189 name => 'config',
190 block => sub {
191 my $s = shift;
192
193 my $v = Data::Visitor::Callback->new(
194 plain_value => sub {
195 return unless defined $_;
196 return $self->_config_substitutions( $s->param('name'), $s->param('substitutions'), $_ );
197 }
198
199 );
200 $v->visit( $s->param('raw_config') );
201 },
202 dependencies => [ depends_on('name'), depends_on('raw_config'), depends_on('substitutions') ],
f04816ce 203 );
204}
b4a6fa62 205
f04816ce 206sub build_raw_config_service {
207 my $self = shift;
292277c1 208
209 return Bread::Board::BlockInjection->new(
210 name => 'raw_config',
211 block => sub {
212 my $s = shift;
213
214 my @global = @{$s->param('global_config')};
215 my @locals = @{$s->param('local_config')};
216
217 my $config = {};
218 for my $cfg (@global, @locals) {
219 for (keys %$cfg) {
220 $config = Catalyst::Utils::merge_hashes( $config, $cfg->{$_} );
b4a6fa62 221 }
292277c1 222 }
223 return $config;
224 },
225 dependencies => [ depends_on('global_config'), depends_on('local_config') ],
f04816ce 226 );
227}
b4a6fa62 228
f04816ce 229sub build_global_files_service {
230 my $self = shift;
b4a6fa62 231
292277c1 232 return Bread::Board::BlockInjection->new(
233 name => 'global_files',
234 block => sub {
235 my $s = shift;
b4a6fa62 236
292277c1 237 my ( $path, $extension ) = @{$s->param('config_path')};
b4a6fa62 238
292277c1 239 my @extensions = @{$s->param('extensions')};
240
241 my @files;
242 if ( $extension ) {
243 die "Unable to handle files with the extension '${extension}'" unless grep { $_ eq $extension } @extensions;
244 push @files, $path;
245 } else {
246 @files = map { "$path.$_" } @extensions;
247 }
248 return \@files;
249 },
250 dependencies => [ depends_on('extensions'), depends_on('config_path') ],
f04816ce 251 );
252}
b4a6fa62 253
f04816ce 254sub build_local_files_service {
255 my $self = shift;
292277c1 256
257 return Bread::Board::BlockInjection->new(
258 name => 'local_files',
259 block => sub {
260 my $s = shift;
261
262 my ( $path, $extension ) = @{$s->param('config_path')};
263 my $suffix = $s->param('config_local_suffix');
264
265 my @extensions = @{$s->param('extensions')};
266
267 my @files;
268 if ( $extension ) {
269 die "Unable to handle files with the extension '${extension}'" unless grep { $_ eq $extension } @extensions;
270 $path =~ s{\.$extension}{_$suffix.$extension};
271 push @files, $path;
272 } else {
273 @files = map { "${path}_${suffix}.$_" } @extensions;
274 }
275 return \@files;
276 },
277 dependencies => [ depends_on('extensions'), depends_on('config_path'), depends_on('config_local_suffix') ],
f04816ce 278 );
279}
b4a6fa62 280
f04816ce 281sub build_global_config_service {
282 my $self = shift;
292277c1 283
284 return Bread::Board::BlockInjection->new(
285 name => 'global_config',
286 block => sub {
287 my $s = shift;
288
289 return Config::Any->load_files({
290 files => $s->param('global_files'),
291 filter => \&_fix_syntax,
292 use_ext => 1,
293 driver_args => $s->param('driver'),
294 });
295 },
296 dependencies => [ depends_on('global_files') ],
f04816ce 297 );
298}
b4a6fa62 299
f04816ce 300sub build_local_config_service {
301 my $self = shift;
292277c1 302
303 return Bread::Board::BlockInjection->new(
304 name => 'local_config',
305 block => sub {
306 my $s = shift;
307
308 return Config::Any->load_files({
309 files => $s->param('local_files'),
310 filter => \&_fix_syntax,
311 use_ext => 1,
312 driver_args => $s->param('driver'),
313 });
314 },
315 dependencies => [ depends_on('local_files') ],
f04816ce 316 );
317}
b4a6fa62 318
f04816ce 319sub build_config_path_service {
320 my $self = shift;
b4a6fa62 321
292277c1 322 return Bread::Board::BlockInjection->new(
323 name => 'config_path',
324 block => sub {
325 my $s = shift;
b4a6fa62 326
292277c1 327 my $path = $s->param('path');
328 my $prefix = $s->param('prefix');
b4a6fa62 329
292277c1 330 my ( $extension ) = ( $path =~ m{\.(.{1,4})$} );
331
332 if ( -d $path ) {
333 $path =~ s{[\/\\]$}{};
334 $path .= "/$prefix";
335 }
b4a6fa62 336
292277c1 337 return [ $path, $extension ];
338 },
339 dependencies => [ depends_on('prefix'), depends_on('path') ],
f04816ce 340 );
341}
b4a6fa62 342
f04816ce 343sub build_config_local_suffix_service {
344 my $self = shift;
292277c1 345
346 return Bread::Board::BlockInjection->new(
347 name => 'config_local_suffix',
348 block => sub {
349 my $s = shift;
350 my $suffix = Catalyst::Utils::env_value( $s->param('name'), 'CONFIG_LOCAL_SUFFIX' ) || $self->config_local_suffix;
351
352 return $suffix;
353 },
354 dependencies => [ depends_on('name') ],
f04816ce 355 );
b4a6fa62 356}
357
358sub _fix_syntax {
359 my $config = shift;
360 my @components = (
361 map +{
362 prefix => $_ eq 'Component' ? '' : $_ . '::',
363 values => delete $config->{ lc $_ } || delete $config->{ $_ }
364 },
365 grep { ref $config->{ lc $_ } || ref $config->{ $_ } }
366 qw( Component Model M View V Controller C Plugin )
367 );
368
369 foreach my $comp ( @components ) {
370 my $prefix = $comp->{ prefix };
371 foreach my $element ( keys %{ $comp->{ values } } ) {
372 $config->{ "$prefix$element" } = $comp->{ values }->{ $element };
373 }
374 }
375}
376
377sub _config_substitutions {
6682389c 378 my ( $self, $name, $subs, $arg ) = @_;
b4a6fa62 379
380 $subs->{ HOME } ||= sub { shift->path_to( '' ); };
381 $subs->{ ENV } ||=
382 sub {
383 my ( $c, $v ) = @_;
384 if (! defined($ENV{$v})) {
385 Catalyst::Exception->throw( message =>
386 "Missing environment variable: $v" );
387 return "";
388 } else {
389 return $ENV{ $v };
390 }
391 };
392 $subs->{ path_to } ||= sub { shift->path_to( @_ ); };
393 $subs->{ literal } ||= sub { return $_[ 1 ]; };
394 my $subsre = join( '|', keys %$subs );
395
6682389c 396 $arg =~ s{__($subsre)(?:\((.+?)\))?__}{ $subs->{ $1 }->( $name, $2 ? split( /,/, $2 ) : () ) }eg;
397 return $arg;
b4a6fa62 398}
399
a17e0ff8 400sub get_component_from_sub_container {
401 my ( $self, $sub_container_name, $name, $c, @args ) = @_;
402
403 my $sub_container = $self->get_sub_container( $sub_container_name );
404
0e747f0c 405 if (!$name) {
a2c0d071 406 my $default = $sub_container->default_component;
0e747f0c 407
408 return $sub_container->get_component( $default, $c, @args )
409 if $default && $sub_container->has_service( $default );
410
411 # this is never a controller, so this is safe
412 $c->log->warn( "Calling \$c->$sub_container_name() is not supported unless you specify one of:" );
413 $c->log->warn( "* \$c->config(default_$sub_container_name => 'the name of the default $sub_container_name to use')" );
414 $c->log->warn( "* \$c->stash->{current_$sub_container_name} # the name of the view to use for this request" );
415 $c->log->warn( "* \$c->stash->{current_${sub_container_name}_instance} # the instance of the $sub_container_name to use for this request" );
a2c0d071 416
417 return;
0e747f0c 418 }
419
a17e0ff8 420 return $sub_container->get_component_regexp( $name, $c, @args )
421 if ref $name;
422
423 return $sub_container->get_component( $name, $c, @args )
424 if $sub_container->has_service( $name );
425
426 $c->log->warn(
427 "Attempted to use $sub_container_name '$name', " .
428 "but it does not exist"
429 );
430
431 return;
432}
433
c4aedec7 434sub find_component {
435 my ( $self, $component, @args ) = @_;
436 my @result;
437
438 my $query = ref $component
439 ? $component
440 : qr{^$component$}
441 ;
442
443 for my $subcontainer_name (qw/model view controller/) {
444 my $subcontainer = $self->get_sub_container($subcontainer_name);
445 my @components = $subcontainer->get_service_list;
446 @result = grep { m{$component} } @components;
447
448 return map { $subcontainer->get_component( $_, @args ) } @result
449 if @result;
450 }
451
452 # it expects an empty list on failed searches
453 return @result;
454}
455
4e2b302e 456sub find_component_regexp {
457 my ( $self, $components, $component, @args ) = @_;
458 my @result;
459
460 my @components = grep { m{$component} } keys %{ $components };
461
462 for (@components) {
463 # FIXME this is naughty enough being called inside Catalyst.pm
464 # find some alternative for this sub and remember to delete here
465 my ($type, $name) = Catalyst::_get_component_type_name($_);
466
467 push @result, $self->get_component_from_sub_container(
468 $type, $name, @args
469 ) if $type;
470 }
471
472 return @result;
473}
474
409db9cb 475# FIXME sorry for the name again :)
be80b0a5 476sub get_components_types {
477 my ( $self ) = @_;
478 my @comps_types;
479
480 for my $sub_container_name (qw/model view controller/) {
481 my $sub_container = $self->get_sub_container($sub_container_name);
482 for my $service ( $sub_container->get_service_list ) {
483 my $comp = $self->resolve(service => $service);
484 my $compname = ref $comp || $comp;
485 my $type = ref $comp ? 'instance' : 'class';
486 push @comps_types, [ $compname, $type ];
487 }
488 }
489
490 return @comps_types;
491}
c4aedec7 492
b4410fc3 493sub get_all_components {
494 my $self = shift;
495 my %components;
496
497 my $containers = {
498 map { $_ => $self->get_sub_container($_) } qw(model view controller)
499 };
500
501 for my $container (keys %$containers) {
502 for my $component ($containers->{$container}->get_service_list) {
503 my $comp = $containers->{$container}->resolve(
504 service => $component
505 );
506 my $comp_name = ref $comp || $comp;
507 $components{$comp_name} = $comp;
508 }
509 }
510
511 return lock_hash %components;
512}
513
d057ddb9 5141;
515
516__END__
517
518=pod
519
520=head1 NAME
521
522Catalyst::Container - IOC for Catalyst components
523
524=head1 METHODS
525
526=head2 build_model_subcontainer
527
528=head2 build_view_subcontainer
529
530=head2 build_controller_subcontainer
531
532=head2 build_name_service
533
534=head2 build_driver_service
535
536=head2 build_file_service
537
538=head2 build_substitutions_service
539
540=head2 build_extensions_service
541
542=head2 build_prefix_service
543
544=head2 build_path_service
545
546=head2 build_config_service
547
548=head2 build_raw_config_service
549
550=head2 build_global_files_service
551
552=head2 build_local_files_service
553
554=head2 build_global_config_service
555
556=head2 build_local_config_service
557
558=head2 build_config_path_service
559
560=head2 build_config_local_suffix_service
561
8dc2fca3 562=head2 get_component_from_sub_container
563
409db9cb 564=head2 get_components_types
565
b4410fc3 566=head2 get_all_components
567
c4aedec7 568=head2 find_component
569
4e2b302e 570=head2 find_component_regexp
571
d057ddb9 572=head2 _fix_syntax
573
574=head2 _config_substitutions
575
bf3c8088 576=head1 AUTHORS
577
e8ed391e 578Catalyst Contributors, see Catalyst.pm
bf3c8088 579
e8ed391e 580=head1 COPYRIGHT
bf3c8088 581
582This library is free software. You can redistribute it and/or modify it under
583the same terms as Perl itself.
584
585=cut