moved setup_home to the container
[catagits/Catalyst-Runtime.git] / lib / Catalyst / IOC / Container.pm
index 0a0407d..cb82f69 100644 (file)
@@ -1,13 +1,15 @@
 package Catalyst::IOC::Container;
-use Bread::Board;
+use Bread::Board qw/depends_on/;
 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 Scalar::Util qw/refaddr/;
 use Catalyst::IOC::BlockInjection;
 use Catalyst::IOC::ConstructorInjection;
 use Module::Pluggable::Object ();
@@ -39,12 +41,6 @@ has substitutions => (
     default => sub { +{} },
 );
 
-has application_name => (
-    is       => 'ro',
-    isa      => 'Str',
-    required => 1,
-);
-
 has sub_container_class => (
     isa     => LoadableClass,
     is      => 'ro',
@@ -64,7 +60,7 @@ sub BUILD {
         substitutions
         file
         driver
-        application_name
+        catalyst_application
         prefix
         extensions
         path
@@ -78,6 +74,8 @@ sub BUILD {
         config_local_suffix
         config_path
         locate_components
+        home
+        root_dir
     /;
 
     my $config = $self->resolve( service => 'config' );
@@ -107,6 +105,14 @@ sub BUILD {
     $self->add_sub_container(
         $self->build_model_subcontainer( @default_model )
     );
+
+    {
+        no strict 'refs';
+        no warnings 'once';
+        my $class = ref $self;
+        ${ $class . '::customise_container' }->($self)
+            if ${ $class . '::customise_container' };
+    }
 }
 
 sub build_model_subcontainer {
@@ -141,10 +147,52 @@ sub build_component_subcontainer {
     );
 }
 
-sub build_application_name_service {
+sub build_home_service {
     my $self = shift;
 
-    return Bread::Board::Literal->new( name => 'application_name', value => $self->application_name );
+    return Bread::Board::BlockInjection->new(
+        lifecycle => 'Singleton',
+        name => 'home',
+        block => sub {
+            my $self  = shift;
+            my $class = $self->param('catalyst_application');
+
+            if ( my $env = Catalyst::Utils::env_value( $class, 'HOME' ) ) {
+                return $env;
+            }
+
+            if ( my $home = $self->param('home_flag') ) {
+                return $home;
+            }
+
+            return Catalyst::Utils::home($class);
+        },
+        parameters   => {
+            home_flag => { is => 'ro', isa => 'Str|Undef', required => 0 }
+        },
+        dependencies => [ depends_on('catalyst_application') ],
+    );
+}
+
+sub build_root_dir_service {
+    my $self = shift;
+
+    return Bread::Board::BlockInjection->new(
+        lifecycle => 'Singleton',
+        name => 'root_dir',
+        block => sub {
+            my $self = shift;
+
+            return Path::Class::Dir->new( $self->param('home') )->subdir('root');
+        },
+        dependencies => [ depends_on('home') ],
+    );
+}
+
+sub build_catalyst_application_service {
+    my $self = shift;
+
+    return Bread::Board::Literal->new( name => 'catalyst_application', value => $self->name );
 }
 
 sub build_driver_service {
@@ -184,9 +232,9 @@ sub build_prefix_service {
         lifecycle => 'Singleton',
         name => 'prefix',
         block => sub {
-            return Catalyst::Utils::appprefix( shift->param('application_name') );
+            return Catalyst::Utils::appprefix( shift->param('catalyst_application') );
         },
-        dependencies => [ depends_on('application_name') ],
+        dependencies => [ depends_on('catalyst_application') ],
     );
 }
 
@@ -199,11 +247,11 @@ sub build_path_service {
         block => sub {
             my $s = shift;
 
-            return Catalyst::Utils::env_value( $s->param('application_name'), 'CONFIG' )
+            return Catalyst::Utils::env_value( $s->param('catalyst_application'), 'CONFIG' )
             || $s->param('file')
-            || $s->param('application_name')->path_to( $s->param('prefix') );
+            || $s->param('catalyst_application')->path_to( $s->param('prefix') );
         },
-        dependencies => [ depends_on('file'), depends_on('application_name'), depends_on('prefix') ],
+        dependencies => [ depends_on('file'), depends_on('catalyst_application'), depends_on('prefix') ],
     );
 }
 
@@ -219,13 +267,13 @@ sub build_config_service {
             my $v = Data::Visitor::Callback->new(
                 plain_value => sub {
                     return unless defined $_;
-                    return $self->_config_substitutions( $s->param('application_name'), $s->param('substitutions'), $_ );
+                    return $self->_config_substitutions( $s->param('catalyst_application'), $s->param('substitutions'), $_ );
                 }
 
             );
             $v->visit( $s->param('raw_config') );
         },
-        dependencies => [ depends_on('application_name'), depends_on('raw_config'), depends_on('substitutions') ],
+        dependencies => [ depends_on('catalyst_application'), depends_on('raw_config'), depends_on('substitutions') ],
     );
 }
 
@@ -317,7 +365,7 @@ sub build_class_config_service {
         name => 'class_config',
         block => sub {
             my $s   = shift;
-            my $app = $s->param('application_name');
+            my $app = $s->param('catalyst_application');
 
             # Container might be called outside Catalyst context
             return {} unless Class::MOP::is_class_loaded($app);
@@ -325,7 +373,7 @@ sub build_class_config_service {
             # config might not have been defined
             return $app->config || {};
         },
-        dependencies => [ depends_on('application_name') ],
+        dependencies => [ depends_on('catalyst_application') ],
     );
 }
 
@@ -402,11 +450,11 @@ sub build_config_local_suffix_service {
         name => 'config_local_suffix',
         block => sub {
             my $s = shift;
-            my $suffix = Catalyst::Utils::env_value( $s->param('application_name'), 'CONFIG_LOCAL_SUFFIX' ) || $self->config_local_suffix;
+            my $suffix = Catalyst::Utils::env_value( $s->param('catalyst_application'), 'CONFIG_LOCAL_SUFFIX' ) || $self->config_local_suffix;
 
             return $suffix;
         },
-        dependencies => [ depends_on('application_name') ],
+        dependencies => [ depends_on('catalyst_application') ],
     );
 }
 
@@ -418,7 +466,7 @@ sub build_locate_components_service {
         name      => 'locate_components',
         block     => sub {
             my $s      = shift;
-            my $class  = $s->param('application_name');
+            my $class  = $s->param('catalyst_application');
             my $config = $s->param('config')->{ setup_components };
 
             Catalyst::Exception->throw(
@@ -436,17 +484,32 @@ sub build_locate_components_service {
 
             return [ $locator->plugins ];
         },
-        dependencies => [ depends_on('application_name'), depends_on('config') ],
+        dependencies => [ depends_on('catalyst_application'), depends_on('config') ],
     );
 }
 
 sub setup_components {
     my $self = shift;
-    my $class = $self->resolve( service => 'application_name' );
+    my $class = $self->resolve( service => 'catalyst_application' );
     my @comps = @{ $self->resolve( service => 'locate_components' ) };
     my %comps = map { $_ => 1 } @comps;
     my $deprecatedcatalyst_component_names = 0;
 
+    my $app_locate_components_addr = refaddr(
+        $class->can('locate_components')
+    );
+    my $cat_locate_components_addr = refaddr(
+        Catalyst->can('locate_components')
+    );
+
+    if ($app_locate_components_addr != $cat_locate_components_addr) {
+        # FIXME - why not just say: @comps = $class->locate_components() ?
+        $class->log->warn(qq{You have overridden locate_components. That } .
+            qq{no longer works. Please refer to the documentation to achieve } .
+            qq{similar results.\n}
+        );
+    }
+
     for my $component ( @comps ) {
 
         # We pass ignore_loaded here so that overlay files for (e.g.)
@@ -581,7 +644,7 @@ sub find_component {
     }
 
     # one last search for things like $c->comp(qr/::M::/)
-    @result = $self->find_component_regexp(
+    @result = $self->_find_component_regexp(
         $component, @args
     ) if !@result and ref $component;
 
@@ -589,17 +652,17 @@ sub find_component {
     return @result;
 }
 
-sub find_component_regexp {
-    my ( $self, $component, @args ) = @_;
+sub _find_component_regexp {
+    my ( $self, $component, $ctx, @args ) = @_;
     my @result;
 
-    my @components = grep { m{$component} } keys %{ $self->get_all_components };
+    my @components = grep { m{$component} } keys %{ $self->get_all_components($ctx) };
 
     for (@components) {
         my ($type, $name) = _get_component_type_name($_);
 
         push @result, $self->get_component_from_sub_container(
-            $type, $name, @args
+            $type, $name, $ctx, @args
         ) if $type;
     }
 
@@ -607,17 +670,23 @@ sub find_component_regexp {
 }
 
 sub get_all_components {
-    my $self = shift;
+    my ($self, $class) = @_;
     my %components;
 
-    my $container = $self->get_sub_container('component');
+    # FIXME - if we're getting from these containers, we need to either:
+    #   - pass 'ctx' and 'accept_context_args' OR
+    #   - make these params optional
+    # big problem when setting up the dispatcher - this method is called
+    # as $container->get_all_components('MyApp'). What to do with Request
+    # life cycles?
+    foreach my $type (qw/model view controller /) {
+        my $container = $self->get_sub_container($type);
 
-    for my $component ($container->get_service_list) {
-        my $comp = $container->resolve(
-            service => $component
-        );
-        my $comp_name = ref $comp || $comp;
-        $components{$comp_name} = $comp;
+        for my $component ($container->get_service_list) {
+            my $comp_service = $container->get_service($component);
+
+            $components{$comp_service->catalyst_component_name} = $comp_service->get(ctx => $class);
+        }
     }
 
     return lock_hash %components;
@@ -629,8 +698,6 @@ sub add_component {
 
     return unless $type;
 
-    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
@@ -639,30 +706,41 @@ sub add_component {
     my $instance_container       = $self->get_sub_container('component');
     my $accept_context_container = $self->get_sub_container($type);
 
+    # Custom containers might have added the service already
+    # We don't want to override that
+    return if $accept_context_container->has_service( $name );
+
+    my $component_service_name = "${type}_${name}";
+
     $instance_container->add_service(
         Catalyst::IOC::ConstructorInjection->new(
             name      => $component_service_name,
+            catalyst_component_name => $component,
             class     => $component,
             lifecycle => 'Singleton',
             dependencies => [
-                depends_on( '/application_name' ),
-                depends_on( '/config' ),
+                depends_on( '/catalyst_application' ),
             ],
-        )
-    ) unless $instance_container->has_service( $component_service_name );
-    # ^ custom containers might have added the service already.
-    # we don't want to override that.
+        ),
+    );
+    # XXX - FIXME - We have to explicitly build the service here,
+    #               causing the COMPONENT method to be called early here, as otherwise
+    #               if the component method defines other classes (e.g. the
+    #               ACCEPT_CONTEXT injection Model::DBIC::Schema does)
+    #               then they won't be found by Devel::InnerPackage
+    # see also t/aggregate/unit_core_component_loading.t
+    $instance_container->get_service($component_service_name)->get;
 
     $accept_context_container->add_service(
         Catalyst::IOC::BlockInjection->new(
             name         => $name,
+            catalyst_component_name => $component,
             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?
@@ -670,23 +748,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 (scalar @parts > 1) {
-        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 {
@@ -694,6 +765,8 @@ sub expand_component_module {
     return Devel::InnerPackage::list_packages( $module );
 }
 
+__PACKAGE__->meta->make_immutable;
+
 1;
 
 __END__
@@ -710,20 +783,20 @@ Catalyst::Container - IOC for Catalyst components
 
 =head1 METHODS
 
-=head1 Building Containers
+=head1 Methods for 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
+sub-container will instantiate the component, if it hasn't been instantiated
 already, but will not execute ACCEPT_CONTEXT.
 
 =head2 build_model_subcontainer
 
 Container that stores references for all models that are inside the components
-subcontainer. Retrieving a model triggers ACCEPT_CONTEXT, if it exists.
+sub-container. Retrieving a model triggers ACCEPT_CONTEXT, if it exists.
 
 =head2 build_view_subcontainer
 
@@ -733,12 +806,23 @@ Same as L<build_model_subcontainer>, but for views.
 
 Same as L<build_model_subcontainer>, but for controllers.
 
-=head1 Building Services
+=head1 Methods for Building Services
 
-=head2 build_application_name_service
+=head2 build_catalyst_application_service
 
 Name of the application (such as MyApp).
 
+=head2 build_home_service
+
+The application home directory. All the files (including classes, scripts, etc)
+created for this application are in this directory, or in a sub-directory below
+this one.
+
+=head2 build_root_dir_service
+
+Inside the application home (as explained in L</build_home_service>), there is
+a root directory. This is where all templates and static files are.
+
 =head2 build_driver_service
 
 Config options passed directly to the driver being used.
@@ -784,7 +868,7 @@ Config::Any's available config file extensions (e.g. xml, json, pl, etc).
 
 =head2 build_prefix_service
 
-The prefix, based on the application name, that will be used to lookup the
+The prefix, based on the application name, that will be used to look-up the
 config files (which will be in the format $prefix.$extension). If the app is
 MyApp::Foo, the prefix will be myapp_foo.
 
@@ -861,40 +945,30 @@ to L<Module::Pluggable>.
 
 =head2 get_component_from_sub_container($sub_container, $name, $c, @args)
 
-Looks for components in a given subcontainer (such as controller, model or
+Looks for components in a given sub-container (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.
 
-=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
-controller, and returns a readonly hash. The keys are the class names, and
+controller, and returns a read-only hash. The keys are the class names, and
 the values are the blessed objects. This is what is returned by $c->components.
 
 =head2 add_component
 
-Adds a component to the appropriate subcontainer. The subcontainer is guessed
+Adds a component to the appropriate sub-container. The sub-container is guessed
 by the component name given.
 
 =head2 find_component
 
 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.
+name, the sub-container is guessed, and it gets the searched component in there.
+Otherwise, it looks for a component with that name in all sub-containers. If
+$component is a regexp it calls _find_component_regexp and matches all
+components against that regexp.
 
 =head2 expand_component_module
 
@@ -903,6 +977,9 @@ is expected to return a list of component (package) names to be set up.
 
 =head2 setup_components
 
+Uses locate_components service to list the components, and adds them to the
+appropriate sub-containers, using add_component().
+
 =head1 AUTHORS
 
 Catalyst Contributors, see Catalyst.pm