[Catalyst] Config-only components

Dagfinn Ilmari Mannsåker ilmari at ilmari.org
Tue Feb 12 07:09:46 GMT 2008


Hi all,

Castaway mentioned the idea of having components defined entirely by
config entries, without the need for any actual class files on disk, and
I was bored, so I went ahead and implemented it.

For each config key matching ^([MVC]|Model|View|Controller):: it checks
if the corresponding component already exists, and if it doesn't it
creates it on the fly. The base class is set to
->config->{$component}->{base_class} if it exists, Catalyst::$component
(with [MVC] expanded to the full component type) otherwise.

Here's the patch against 5.70/trunk, feedback welcome.

 lib/Catalyst.pm                       |   51 ++++++++++++++++++++++++++--------
 lib/Catalyst/Utils.pm                 |   23 +++++++++++++++
 t/lib/Catalyst/Component/Implicit.pm  |   16 ++++++++++
 t/lib/Catalyst/Controller/Implicit.pm |    4 ++
 t/lib/Catalyst/Model/Implicit.pm      |    4 ++
 t/lib/Catalyst/View/Implicit.pm       |    4 ++
 t/unit_core_component_dynamic.t       |   31 ++++++++++++++++++++
 7 files changed, 121 insertions(+), 12 deletions(-)

=== lib/Catalyst/Utils.pm
==================================================================
--- lib/Catalyst/Utils.pm	(revision 36505)
+++ lib/Catalyst/Utils.pm	(local)
@@ -79,6 +79,29 @@
     return $class;
 }
 
+=head2 expand_component_type( $class );
+
+Replaces M, V and C components of the class name with Model, View and
+Controller, respectively.
+
+     MyApp::C::Foo becomes MyApp::Controller::Foo
+     My::App::C::Foo becomes Mapp::Controller::Foo
+
+=cut
+
+sub expand_component_type {
+    my ($class) = @_;
+
+    my %expand = qw/ M Model
+                     V View
+                     C Controller
+                   /;
+
+    $class =~ s/(?<=::)([MVC])(?=::)/$expand{$1}/;
+
+    return $class;
+}
+
 =head2 class2env($class);
 
 Returns the environment name for class.
=== lib/Catalyst.pm
==================================================================
--- lib/Catalyst.pm	(revision 36505)
+++ lib/Catalyst.pm	(local)
@@ -1878,19 +1878,46 @@
 
         Catalyst::Utils::ensure_class_loaded( $component, { ignore_loaded => 1 } );
 
-        my $module  = $class->setup_component( $component );
-        my %modules = (
-            $component => $module,
-            map {
-                $_ => $class->setup_component( $_ )
-            } grep { 
-              not exists $comps{$_}
-            } Devel::InnerPackage::list_packages( $component )
-        );
+        $class->_load_component_and_children( $component, \%comps );
+    }
+
+    # For component-esque config keys with no corresponding module yet
+    # loaded, try to create it on the fly.
+
+    my $prefix = join '|', map { m/::(.*)/ } @paths;
+    my @config_comps =  grep /^(?:$prefix)::/, keys %{ $class->config };
+    $comps{"$class\::$_"} = 1 for @config_comps;
+
+    for my $suffix ( @config_comps  ) {
+        my $component = "$class\::$suffix";
+
+        next if $class->components->{ $component };
+
+        my $base = delete $class->config->{ $suffix }->{ base_class }
+            || Catalyst::Utils::expand_component_type("Catalyst::$suffix");
+
+        Catalyst::Utils::ensure_class_loaded( $base );
+        { no strict 'refs'; unshift @{"$component\::ISA"}, $base }
+
+        $class->_load_component_and_children( $component, \%comps );
+    }
+}
+
+sub _load_component_and_children {
+    my ($class, $component, $comps) = @_;
+
+    my $module  = $class->setup_component( $component );
+    my %modules = (
+        $component => $module,
+        map {
+            $_ => $class->setup_component( $_ )
+        } grep { 
+            not exists $comps->{$_}
+        } Devel::InnerPackage::list_packages( $component )
+    );
         
-        for my $key ( keys %modules ) {
-            $class->components->{ $key } = $modules{ $key };
-        }
+    for my $key ( keys %modules ) {
+        $class->components->{ $key } = $modules{ $key };
     }
 }
 
=== t/lib/Catalyst/Component	(new directory)
==================================================================
=== t/lib/Catalyst/Component/Implicit.pm
==================================================================
--- t/lib/Catalyst/Component/Implicit.pm	(revision 36505)
+++ t/lib/Catalyst/Component/Implicit.pm	(local)
@@ -0,0 +1,16 @@
+package Catalyst::Component::Implicit;
+use base qw/Catalyst::Component/;
+
+use Catalyst::Utils;
+
+sub COMPONENT {
+    my ($self, $c, $arguments) = @_;
+    my $class = ref $self || $self;
+
+    # Inject an inner package intoto the subclass
+    { no strict 'refs'; @{"$class\::Sub::ISA"} = @{"$class\::ISA"} }
+
+    return $self->NEXT::COMPONENT( $c, $arguments );
+}
+
+1;
=== t/lib/Catalyst/Controller	(new directory)
==================================================================
=== t/lib/Catalyst/Controller/Implicit.pm
==================================================================
--- t/lib/Catalyst/Controller/Implicit.pm	(revision 36505)
+++ t/lib/Catalyst/Controller/Implicit.pm	(local)
@@ -0,0 +1,4 @@
+package Catalyst::Controller::Implicit;
+use base qw/Catalyst::Component::Implicit Catalyst::Controller/;
+
+1;
=== t/lib/Catalyst/Model	(new directory)
==================================================================
=== t/lib/Catalyst/Model/Implicit.pm
==================================================================
--- t/lib/Catalyst/Model/Implicit.pm	(revision 36505)
+++ t/lib/Catalyst/Model/Implicit.pm	(local)
@@ -0,0 +1,4 @@
+package Catalyst::Model::Implicit;
+use base qw/Catalyst::Component::Implicit Catalyst::Model/;
+
+1;
=== t/lib/Catalyst/View	(new directory)
==================================================================
=== t/lib/Catalyst/View/Implicit.pm
==================================================================
--- t/lib/Catalyst/View/Implicit.pm	(revision 36505)
+++ t/lib/Catalyst/View/Implicit.pm	(local)
@@ -0,0 +1,4 @@
+package Catalyst::View::Implicit;
+use base qw/Catalyst::Component::Implicit Catalyst::View/;
+
+1;
=== t/unit_core_component_dynamic.t
==================================================================
--- t/unit_core_component_dynamic.t	(revision 36505)
+++ t/unit_core_component_dynamic.t	(local)
@@ -0,0 +1,31 @@
+use Test::More tests => 24;
+
+use strict;
+use warnings;
+
+use FindBin;
+use lib "$FindBin::Bin/lib";
+
+{
+    package MyApp;
+
+    use base qw/Catalyst/;
+
+    __PACKAGE__->config(
+        map { +"$_\::Explicit" => { base_class => "Catalyst::$_" },
+              +"$_\::Explicit::Sub" => { base_class => "Catalyst::$_" },
+              +"$_\::Implicit" => {},
+            } qw/Model View Controller/
+    );
+    __PACKAGE__->setup;
+}
+
+for my $comp (qw/Model View Controller/) {
+    my  $method = lc $comp;
+    for my $type (qw/Explicit Implicit/) {
+        isa_ok(MyApp->$method("$type"), "MyApp::$comp\::$type");
+        isa_ok(MyApp->$method("$type"), "Catalyst::$comp");
+        isa_ok(MyApp->$method("$type\::Sub"), "MyApp::$comp\::$type\::Sub");
+        isa_ok(MyApp->$method("$type\::Sub"), "Catalyst::$comp");
+    }
+}



-- 
ilmari
"A disappointingly low fraction of the human race is,
 at any given time, on fire." - Stig Sandbeck Mathisen





More information about the Catalyst mailing list