[Catalyst] Wierd config behavior solved (was Re: Creating my own controller....)

John Napiorkowski jjn1056 at yahoo.com
Fri Apr 28 19:42:03 CEST 2006


I spent a lot of time digging into the trouble with
using __PACKAGE__->config with subclassed controllers.
 The short story is that the solution from Matt (using
"__PACKAGE__->_config({ %{ __PACKAGE__->_config }
});") did work, although it's not so pretty since you
need to put it in front of EVERY controller that is
subclassing.

I thought I'd show my test case so that other people
having this trouble might be helped.  I also hope I
can find a more elegant solution eventually.

Here's the example.

Say you have a base controller like the following in
/lib (but not under /Controller since this is just a
base class to encapsulate some functionality):

------------------------------------------
package test_controller;

use strict;
use warnings;

use base 'Catalyst::Controller';

__PACKAGE__->config( anything	=> 'any' );

return 1;
------------------------------------------

and then you have two controllers that are under
/Controllers and are actually registered actions that
Catalyst can find:

------------------------------------------
package MyApp::Controller::test1;

use strict;
use warnings;
use Data::Dumper;

use base 'test_controller';

__PACKAGE__->config( key => 'value' );
		
sub default : Private
{
	my ($self, $c)	= @_;
	
	$c->response->body(Dumper($self));
}

return 1;
------------------------------------------

------------------------------------------
package MyApp::Controller::test2;

use strict;
use warnings;
use Data::Dumper;

use base 'test_controller';
		
sub default : Private
{
	my ($self, $c)	= @_;
	
	$c->response->body(Dumper($self));
}

return 1;
------------------------------------------

Now, the controllers test1 and test2 should expect to
inherit config data from the base test_controller, and
as well test1 adds it's own config data.  So the
output of each test1 and test2 would reflect that. 
However that is not what happens.  test2 (and any
other controllers that are alphanumerically ordered
AFTER test1) inherit config from both the base
test_controller class and from test1 controller.

Here's the actually output of the above two
controllers:

[http://localhost/test1]

$VAR1 = bless( {
                 'anything' => 'any',
                 'key' => 'value'
               }, 'MyApp::Controller::test1' );

[http://localhost/test2]

$VAR1 = bless( {
                 'anything' => 'any',
                 'key' => 'value'
               }, 'MyApp::Controller::test2' );

As you can see /test2 is getting some stuff from the
previous controller.  However if I made a /test0
controller exactly like test1 this would not happen. 
Only controllers that sort post the /test1 would be
affected.

Now the solution that was given was to add some
"__PACKAGE__->_config({ %{ __PACKAGE__->_config } });"
prior to the normal config call.  This has to be added
to EVERY single controller that is inheriting from a
base controller.

For example:

------------------------------------------
package MyApp::Controller::test1;

use strict;
use warnings;
use Data::Dumper;

use base 'test_controller';

__PACKAGE__->_config({ %{ __PACKAGE__->_config } });
__PACKAGE__->config( key => 'value' );
		
sub default : Private
{
	my ($self, $c)	= @_;
	
	$c->response->body(Dumper($self));
}

return 1;
------------------------------------------

------------------------------------------
package MyApp::Controller::test2;

use strict;
use warnings;
use Data::Dumper;

use base 'test_controller';

__PACKAGE__->_config({ %{ __PACKAGE__->_config } });
	
sub default : Private
{
	my ($self, $c)	= @_;
	
	$c->response->body(Dumper($self));
}

return 1;
------------------------------------------

And now everything will JUST WORK.  it works by
resetting the private _config method (which takes hash
ref) to whatever it's value is supposed to be.

Although this solution does the trick, I looked into
this further, since I really think this solution is a
bit undesirable.  Personally I prefer to encapsulate
common behavior to base classes (as I am sure many of
you do) and having that line stuck in the top of every
controller is just going to cause me trouble sooner or
later.  For example I am sure that myself or another
programming will forget to do this, or not understand
why it is there in the first place and remove it.

The config method is inherited from
Catalyst::Component.  It's actually not a lot of
lines, so I'll just paste it here for our help:

sub config {
    my $self = shift;
    $self->_config( {} ) unless $self->_config;
    if (@_) {
        my $config = @_ > 1 ? {@_} : $_[0];
        while ( my ( $key, $val ) = each %$config ) {
            $self->_config->{$key} = $val;
        }
    }
    return $self->_config;
}

The idea here is to let you add stuff to config
without clobbering the hash that is already there.  I
can't see from this code what would be causing the
trouble.  I figure it's somewhere deeper, like in the
part of Catalyst that loads up and get's everything
going.  I think somewhere something is getting cached
(maybe for performance reasons?) that shouldn't.

One thing I did learn, the overlapping configs are set
up right from the start when Catalyst initialize
everything.  So it's in that part of the program.

Anyway, at least I have a solution I can live with for
now.  Hopefully my post will save someone else the
trouble I had, and just maybe someone smarter than me
at this (most of you I imagine :) ) will read this and
have an 'ah ha' moment and grasp the trouble.

Thanks for your help!

--john napiorkowski

__________________________________________________
Do You Yahoo!?
Tired of spam?  Yahoo! Mail has the best spam protection around 
http://mail.yahoo.com 



More information about the Catalyst mailing list