FIXME's and comments
[catagits/Catalyst-Runtime.git] / lib / Catalyst / IOC / Container.pm
index 28a38a4..20ea288 100644 (file)
@@ -4,10 +4,12 @@ use Moose;
 use Config::Any;
 use Data::Visitor::Callback;
 use Catalyst::Utils ();
+use List::Util qw(first);
 use Devel::InnerPackage ();
 use Hash::Util qw/lock_hash/;
 use MooseX::Types::LoadableClass qw/ LoadableClass /;
 use Moose::Util;
+use Catalyst::IOC::BlockInjection;
 use Catalyst::IOC::ConstructorInjection;
 use Module::Pluggable::Object ();
 use namespace::autoclean;
@@ -39,9 +41,9 @@ has substitutions => (
 );
 
 has application_name => (
-    is      => 'ro',
-    isa     => 'Str',
-    default => 'MyApp',
+    is       => 'ro',
+    isa      => 'Str',
+    required => 1,
 );
 
 has sub_container_class => (
@@ -81,20 +83,30 @@ sub BUILD {
 
     my $config = $self->resolve( service => 'config' );
 
+    # don't force default_component to be undef if the config wasn't set
+    my @default_view  = $config->{default_view}
+                      ? ( default_component => $config->{default_view} )
+                      : ( )
+                      ;
+    my @default_model = $config->{default_model}
+                      ? ( default_component => $config->{default_model} )
+                      : ( )
+                      ;
+
+    $self->add_sub_container(
+        $self->build_component_subcontainer
+    );
+
     $self->add_sub_container(
         $self->build_controller_subcontainer
     );
 
     $self->add_sub_container(
-        $self->build_view_subcontainer(
-            default_component => $config->{default_view},
-        )
+        $self->build_view_subcontainer( @default_view )
     );
 
     $self->add_sub_container(
-        $self->build_model_subcontainer(
-            default_component => $config->{default_model},
-        )
+        $self->build_model_subcontainer( @default_model )
     );
 }
 
@@ -122,6 +134,14 @@ sub build_controller_subcontainer {
     );
 }
 
+sub build_component_subcontainer {
+    my $self = shift;
+
+    return Bread::Board::Container->new(
+        name => 'component',
+    );
+}
+
 sub build_application_name_service {
     my $self = shift;
 
@@ -423,6 +443,7 @@ sub build_locate_components_service {
 
 sub setup_components {
     my $self = shift;
+    warn("Setting up default components");
     my $class = $self->resolve( service => 'application_name' );
     my @comps = @{ $self->resolve( service => 'locate_components' ) };
     my %comps = map { $_ => 1 } @comps;
@@ -459,9 +480,6 @@ sub setup_components {
                 unless $comps{$component};
         }
     }
-
-    $self->get_sub_container('model')->make_single_default;
-    $self->get_sub_container('view')->make_single_default;
 }
 
 sub _fix_syntax {
@@ -542,12 +560,12 @@ sub get_component_from_sub_container {
 }
 
 sub find_component {
-    my ( $self, $component, $c, @args ) = @_;
+    my ( $self, $component, @args ) = @_;
     my ( $type, $name ) = _get_component_type_name($component);
     my @result;
 
     return $self->get_component_from_sub_container(
-        $type, $name, $c, @args
+        $type, $name, @args
     ) if $type;
 
     my $query = ref $component
@@ -560,25 +578,24 @@ sub find_component {
         my @components   = $subcontainer->get_service_list;
         @result          = grep { m{$component} } @components;
 
-        return map { $subcontainer->get_component( $_, $c, @args ) } @result
+        return map { $subcontainer->get_component( $_, @args ) } @result
             if @result;
     }
 
-    # FIXME - I guess I shouldn't be calling $c->components here
     # one last search for things like $c->comp(qr/::M::/)
-    @result = $self->find_component_regexp(
-        $c->components, $component, $c, @args
+    @result = $self->_find_component_regexp(
+        $component, @args
     ) if !@result and ref $component;
 
     # it expects an empty list on failed searches
     return @result;
 }
 
-sub find_component_regexp {
-    my ( $self, $components, $component, @args ) = @_;
+sub _find_component_regexp {
+    my ( $self, $component, @args ) = @_;
     my @result;
 
-    my @components = grep { m{$component} } keys %{ $components };
+    my @components = grep { m{$component} } keys %{ $self->get_all_components };
 
     for (@components) {
         my ($type, $name) = _get_component_type_name($_);
@@ -591,39 +608,35 @@ sub find_component_regexp {
     return @result;
 }
 
-# FIXME - t0m, how do you feel about this name?
-# also, do you think I should draw it here, or just return the data structure?
-sub get_components_names_types {
-    my ( $self ) = @_;
-    my @comps_names_types;
-
-    for my $sub_container_name (qw/model view controller/) {
-        my $sub_container = $self->get_sub_container($sub_container_name);
-        for my $service ( $sub_container->get_service_list ) {
-            my $comp     = $self->resolve(service => $service);
-            my $compname = ref $comp || $comp;
-            my $type     = ref $comp ? 'instance' : 'class';
-            push @comps_names_types, [ $compname, $type ];
-        }
-    }
-
-    return @comps_names_types;
-}
-
 sub get_all_components {
     my $self = shift;
     my %components;
 
-    my $containers = {
-        map { $_ => $self->get_sub_container($_) } qw(model view controller)
-    };
+    # FIXME - if we're getting from these containers, we need to either:
+    #   - pass 'ctx' and 'accept_context_args' OR
+    #   - make these params optional
+    foreach my $type (qw/model view controller /) {
+        my $container = $self->get_sub_container($type);
 
-    for my $container (keys %$containers) {
-        for my $component ($containers->{$container}->get_service_list) {
-            my $comp = $containers->{$container}->resolve(
+        for my $component ($container->get_service_list) {
+            my $comp = $container->resolve(
                 service => $component
             );
-            my $comp_name = ref $comp || $comp;
+            my $comp_name = ref $comp || $comp; # THIS IS WRONG! :)
+                                                # Just as it is called Model::Foo
+                                                # does not mean it has to be
+                                                # an instance of model::foo
+            # (AndrĂ©'s answer)
+            # t0m, you're absolutely right, I really hadn't thought about it.
+            # But then, we have a problem: suppose there is a component called
+            # MyApp::M::Foo, for instance. The service name would be 'Foo',
+            # and it would be stored in the 'model' sub container. So we have
+            # $app_name . '::' . uc_first($type) . '::' . $service_name
+            # that would return MyApp::Model::Foo. It would get really, really
+            # ugly to check MyApp::M::Foo. So, either we change the hash key,
+            # or we drop support for ::[CMV]::, or I don't know, maybe you
+            # have a better solution? :)
+
             $components{$comp_name} = $comp;
         }
     }
@@ -637,27 +650,40 @@ sub add_component {
 
     return unless $type;
 
-    $self->get_sub_container($type)->add_service(
+    my $component_service_name = "${type}_${name}";
+
+    # The 'component' sub-container will create the object, and store it's
+    # instance, which, by default, will live throughout the application.
+    # The model/view/controller sub-containers only reference the instance
+    # held in the aforementioned sub-container, and execute the ACCEPT_CONTEXT
+    # sub every time they are called, when it exists.
+    my $instance_container       = $self->get_sub_container('component');
+    my $accept_context_container = $self->get_sub_container($type);
+
+    $instance_container->add_service(
         Catalyst::IOC::ConstructorInjection->new(
-            name      => $name,
+            name      => $component_service_name,
+            catalyst_component_name => $component,
             class     => $component,
+            lifecycle => 'Singleton',
             dependencies => [
                 depends_on( '/application_name' ),
-                depends_on( '/config' ),
             ],
-            parameters => {
-                suffix => {
-                    isa => 'Str',
-                    default => Catalyst::Utils::class2classsuffix( $component ),
-                },
-                accept_context_args => {
-                    isa => 'ArrayRef|Undef',
-                    required => 0,
-                    default => undef,
-                },
-            },
         )
-    );
+    ) unless $instance_container->has_service( $component_service_name );
+    # ^ custom containers might have added the service already.
+    # we don't want to override that.
+
+    $accept_context_container->add_service(
+        Catalyst::IOC::BlockInjection->new(
+            name         => $name,
+            dependencies => [
+                depends_on( "/component/$component_service_name" ),
+            ],
+            block => sub { shift->param($component_service_name) },
+        )
+    ) unless $accept_context_container->has_service( $name );
+    # ^ same as above
 }
 
 # FIXME: should this sub exist?
@@ -665,21 +691,16 @@ sub add_component {
 # or replaced by something already existing there?
 sub _get_component_type_name {
     my ( $component ) = @_;
+    my $result;
 
-    my @parts = split /::/, $component;
-
-    while (my $type = shift @parts) {
-        return ('controller', join '::', @parts)
-            if $type =~ /^(c|controller)$/i;
-
-        return ('model', join '::', @parts)
-            if $type =~ /^(m|model)$/i;
-
-        return ('view', join '::', @parts)
-            if $type =~ /^(v|view)$/i;
+    while ( !$result and (my $index = index $component, '::') > 0 ) {
+        my $type   = lc substr $component, 0, $index;
+        $component = substr $component, $index + 2;
+        $result    = first { $type eq $_ or $type eq substr($_, 0, 1) }
+                         qw{ model view controller };
     }
 
-    return (undef, $component);
+    return ($result, $component);
 }
 
 sub expand_component_module {
@@ -705,17 +726,26 @@ Catalyst::Container - IOC for Catalyst components
 
 =head1 Building Containers
 
+=head2 build_component_subcontainer
+
+Container that stores all components, i.e. all models, views and controllers
+together. Each service is an instance of the actual component, and by default
+it lives while the application is running. Retrieving components from this
+subcontainer will instantiate the component, if it hasn't been instantiated
+already, but will not execute ACCEPT_CONTEXT.
+
 =head2 build_model_subcontainer
 
-Container that stores all models.
+Container that stores references for all models that are inside the components
+subcontainer. Retrieving a model triggers ACCEPT_CONTEXT, if it exists.
 
 =head2 build_view_subcontainer
 
-Container that stores all views.
+Same as L<build_model_subcontainer>, but for views.
 
 =head2 build_controller_subcontainer
 
-Container that stores all controllers.
+Same as L<build_model_subcontainer>, but for controllers.
 
 =head1 Building Services
 
@@ -751,12 +781,14 @@ C<__DATA__> as a config value, for example)
 
 The parameter list is split on comma (C<,>). You can override this method to
 do your own string munging, or you can define your own macros in
-C<MyApp-E<gt>config-E<gt>{ 'Plugin::ConfigLoader' }-E<gt>{ substitutions }>.
+C<< <MyApp->config( 'Plugin::ConfigLoader' => { substitutions => { ... } } ) >>.
 Example:
 
-    MyApp->config->{ 'Plugin::ConfigLoader' }->{ substitutions } = {
-        baz => sub { my $c = shift; qux( @_ ); }
-    }
+    MyApp->config( 'Plugin::ConfigLoader' => {
+        substitutions => {
+            baz => sub { my $c = shift; qux( @_ ); },
+        },
+    });
 
 The above will respond to C<__baz(x,y)__> in config strings.
 
@@ -799,6 +831,11 @@ Reads config from global_files.
 
 Reads config from local_files.
 
+=head2 build_class_config_service
+
+Reads config set from the application's class attribute config,
+i.e. MyApp->config( name => 'MyApp', ... )
+
 =head2 build_config_path_service
 
 Splits the path to the config file, and returns on array ref containing
@@ -844,12 +881,6 @@ 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.
 
-=head2 get_components_names_types
-
-Gets all components from all containers and returns them as an array of
-arrayrefs containing the component name and the component type (i.e., whether
-it's an instance or a class).
-
 =head2 get_all_components
 
 Fetches all the components, in each of the sub_containers model, view and
@@ -866,12 +897,8 @@ by the component name given.
 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.
-
-=head2 find_component_regexp
-
-Finds components that match a given regexp. Used internally, by find_component.
+$component is a regexp it calls _find_component_regexp and matches all
+components against that regexp.
 
 =head2 expand_component_module