[Catalyst] Catalyst best practices?

John Napiorkowski jjn1056 at yahoo.com
Mon Jul 31 05:13:55 CEST 2006


Jonathan,

I think that since Catalyst is so rapidly developing (and typically developing along the direction of the 'itch you need to scratch') some of these questions are a bit hard to answer.  Here's what I am doing:

1)  I'd like to see the search path for stuff evolve as well.  I know the M/V/C directories are just a starting point and that in the end maybe something else will work better for us.  I think having multiple roots for seaching for components arranged something like the way perl seperates CPAN libraries could help sort things out a lot.

/lib
    /vendor    #ready to go catalyst apps that a vendor provides for you
    /site        #Catalyst stuff that you install from cpan
    /local
        /Controller
       /Model
        /View

Personally I create a directory under /lib for my pluggable modules so I can keep the lib root clean.

I feel your pain about needing to split View up a bit.  Axkit and Coccoon have this really nice rendering pipeline that splits things up this way.  Of course those projects have it a bit easier since they are dedicated XML processing systems.

I think one of my biggest headaches is that there are a couple of ways stuff moves to a view from the controller.  Typically data is stored in the stash under a particular key (like the xlst view looks for data under stash->{xml}).  However the Template toolkit just takes the whole stash.  So you end up with more hard coupling between the view and the controller.  I don't see much if any way around this.  The stash is really useful because it's an 'anything goes' place to drop stuff.  However that is a weakness too.  The same controller can't really pass information to either a template toolkit view or a xslt view since both those views expect something very different.  Also if you are too lazy and make the stash large that could case performance trouble.

For what it's worth my view directory looks like this.

/View
    /Serializers      #populates the response body somehow, typically from the stash or a passed object
    /Transformers  #performs some sort of transform on the response body
    /Filters                #perfoms filtering on the response body

But I think this could use a lot of work and thought.  Cocoon does it a bit differently, but again they are using SAX pipes.

2)  I'm typically using the stash as a sort of private namespace to keep myself from having to remember to forward parameters.  If you can get Controller::BindLex to work for you this makes it even easier;

my $rdf :Stashed = RDF->new(...);  #same as $c->stash->{rdf} =...

I use Catalyst actions for methods when I want catalyst to know about them and I use 'normal' methods
primarily to encapsulate behaviors like creating htmlwidgets or doing validation.  I tend to make a lot of
custom controllers with private methods.  Gives me an excuse to learn MOOSE :)

I'm using continuations when I'm on a controller that keeps returning to itself until a certain condition is met,
like a form has properly validated.


3)  I'd like some help understand return stuff as well.  It would be really cool if catalyst actions would
use a return system similar to the way mod_perl works so as to make it easy to set the http response

sub demo :Public
{
    # your stuff for the controller

    if($not_found)
    {
        return HTTP_NOT_FOUND
    }
}

Right now I find I have to set the response status and then in my custom end controller I have to look for it
and decide what to do.  However doing it this way would seriously break things so I guess it's a pipe dream
The new RenderView helps a bit but it doesn't really do what I need either since my view pipeline is a bit
more complicated.

Looking at the source code seems to suggest that the return value is not so important at this time.  But
maybe in the future if we all want to define it than it could change I suppose.

4.  This bothered me as well.  It seems ugly to me to say $self->foo($c) but I think there is a way around it if
you can understand this whole ACCEPT_CONTEXT things mentioned several places in the documentation.

I think the problem comes down to the fact that the context is constantly changing in response to the way
actions are acting, so if you have a method that is not a catalyst action and it needs the context you will
probably have to pass it.  I haven't found a good way around it either.  So I just accept it :)

5)  I dont' feel qualified to say anything about unicode other than what you already said.


----- Original Message ----
From: Jonathan Rockway <jon at jrock.us>
To: catalyst at lists.rawmode.org
Sent: Monday, July 31, 2006 8:58:12 AM
Subject: [Catalyst] Catalyst best practices?

I have a few questions about coding style and whatnot.  I've developed a
"Catalyst coding style", but since I don't work on other people's
applications, I don't know if anyone else does things like I do.  So I
ask, if you do -- why?  If you don't -- why?

1. Plugins

Most of my applications' internals are pluggable.  This leads to a
directory hierarchy like:

lib/MyApp/Controller
lib/MyApp/Model
lib/MyApp/View
lib/MyApp/Formatter
lib/MyApp/Filter
lib/MyApp/Signature.pm
lib/MyApp/User.pm

C<Formatter>s are text formatters that are pluggable with
L<Module::Pluggable> (so you could put the Formatter in your site_perl
directory or get one from CPAN, if desired).  Filters are where my local
TT filters live (since some things are hard to do in TT, but are not
appropriate tasks for the controller).

Then I have some modules that are local to my app and don't fit
anywhere.  User is a user (based on their PGP key), Signature is a
digital signature object.  They're specific to my application and
useless to the outside world, so they're in lib/MyApp. Seems logical to
me, but maybe lib/MyApp should *only* contain M/ V/ and C/ ?

2. Stash / call / forward

Inside controllers, there are a variety of ways to pass data and control
around.  You can put stuff in the stash and C<< $c->forward >> somewhere
and get your result back out of the stash.  You can forward with
arguments and get a return value back.  You can call the method (in
plain perl), providing C<$c>, and get results back from the stash.  You
can use closures, you can use references, etc., etc.  Lots of ways to do
things. :)  Which is best?  (stash, forward, retrieve from stash seems
like it's the "catalyst way", but who knows.  I've become fond of using
forward as a function call, but that seems non-optimal.)

BTW, shouldn't "forward" be called "call"?  When you "forward" e-mail
you don't get the message back.  When you "forward" a user to a
different webpage, they don't come back.  But when you "forward" control
in Catalyst, you get a result back :)

3. Return values from methods inside the controller.

I know about the conventions in C<auto>, but what about regular
methods?  Should they return a true value if nothing bad happens? 
Should they return false if something bad happens?  Should they throw
exceptions?  Should forwards be in eval {} blocks?  (I don't think
Catalyst propagates exceptions like this, but maybe it should?)

Does Catalyst care one way or the other?  Obviously C<die> has a bad
effect on the continued execution of your request, but what about
"return" or "return 0"?  I haven't noticed anything strange when
returning nothing (i.e. a plain "return"), which I usually do to avoid
returning something I didn't want to return.

(Side note:
Explicitly C<return>-ing prevents problems anyway:

     sub foo {
       my ($self, $c) = @_;
       $c->do_something
     }

Is do_something called in void, array, or scalar context?  You don't know!)

4. Internal methods:

If you have some sort of utility function (like C<_calculate_average> or
something, that you call on data in the course of your processing) in
your controller, should it be a method (instead of subroutine)?  Should
it be able to be forwarded to?  If it's not a class method, should it be
allowed to touch the catalyst context?  (so you have something like sub
_foo { my $c = shift; ... } _foo($c).  I do this, but it's pretty ugly. 
I don't think $c should be touched unless Catalyst calls the method for
you.)

5. Unicode

An earlier discussion today prompts this question: why do you have to
use C::P::Unicode and V::TT::ForceUTF8 to get "proper" Unicode support? 
In an ideal world, Unicode would be the default.  Hacks like
C::P::Unicode and ForceUTF8 just lead to problems, IMHO.  I think the
best thing would be for Catalyst to read the incoming headers, properly
convert everything to perl's internal unicode format (even if the input
is in some other locale).  The Controller would then use perl's
functions on the unicode data (which should "just work"), and the View
would do the same.  When it came time to send the response to the
client, an appropriate charset would be selected (probably UTF-8), and
the response would be re-coded from perl's internal format to what the
browser wants.  Appropriate headers would be generated, and the response
would be sent out.

(BTW, V::TT::ForceUTF8 [or rather, Template::Provider::Encoding] doesn't
work for me.  It changes TT's internal semantics, resulting in [% foo |
uri %] not being uri-encoded correctly, if foo is in utf8.   TT assumes
that utf8 characters are allowed in URIs if the encoding is utf8, but
the W3C apparently disagrees.  To work around this, I'm using regular
View::TT now, and that's working fine, as long as my in-source utf8
constants are encoded properly.  A lot of effort on my part, and it
feels to me like it's hanging on a thin thread that will break if my
incoming data was ja_JP.EUC instead of en_US.UTF-8.  Probably not (since
my incoming data comes from the filesystem, and perl does locale stuff
properly), but I'm a little uneasy.  And wow that's a lot of parentheses
))))) :)

Right now Unicode seems like an afterthought (try submitting
UTF8-encoded POD to search.cpan.org.  Doesn't work.)  I don't think we
can trust Joe Average Programmer to get it right.  I've spent a lot of
time and effort chasing down the "Wide character in print" warnings and
exercise adequate caution to prevent double-utf8 encoding (that's fun),
but I still have problems now and again.

Sorry about that -- sounds more like a rant than a question :) So the
question is -- how does your unicode support work?  And I<does> it work? :)

- - -

OK, this is all I can think of right now, but I'm sure I'm missing
something :)  Anyway, I'm very interested to hear what you all have to
say!  Thanks in advance for your commentary.

Regards,
Jonathan Rockway


_______________________________________________
List: Catalyst at lists.rawmode.org
Listinfo: http://lists.rawmode.org/mailman/listinfo/catalyst
Searchable archive: http://www.mail-archive.com/[email protected]/
Dev site: http://dev.catalyst.perl.org/






More information about the Catalyst mailing list