metaperl has asked for the wisdom of the Perl Monks concerning the following question:


Issues with CGI::Prototype

reading the code is clearer than reading the docs

I found that looking through the test suite and the actual code was a lot easier than reading the docs! You might do the same before asking any questions. However, even after such research, I still have a few ants in my pants before getting down to business with CGI::Prototype.

emphasize slots not arguments

When I read the docs, I was wondering why none of the methods appeared to have arguments. For example, I read this:

The render method uses the results from engine and template to process a selected template through Template Toolkit.

and I was wondering why the arguments were not shown alongside the method name like:

render ($engine, $template)

and it is because render() uses the slots engine and template from the default CGI::Prototype prototype object:

sub render { my $self = shift; my $tt = $self->engine; my $self_object = $self->reflect->object; # in case we have a classn +ame $tt->process($self->template, { self => $self_object }, \my $output) or die $tt->error; # passes Template::Exception upward $self->display($output); }

but in retrospect, nothing about this method requires $self to be an object with slots. $self could simply be a class name.

CGI protocol information should be in separate phase

CGI::Prototype ships with this template method:

sub template { '[% self.CGI.header %]This page intentionally left blank.'; }

This is mixing concerns. Some of this output is for negotiating the CGI protocol and some is page-specific output. Also, some of this output is constant from page to page while another part is page-local.

It would have been nice to show the utility of CGI::Prototype's fine-grained series of callbacks by separating raw template output from output required for negotiating the CGI protocol.

I think render_enter would be a good place for the CGI information, while the page-local information does belong in template.

control_enter's docs are confusing:

The docs say:

Called when a page gains control, either at the beginning for a response, or in the middle when switched for rendering. Defaults to + nothing.

There are two ways I became confused about this. The first was from trying to read it and understand it. The second was after looking at the activate() method.

I'm going to work through this phrase-by-phrase, to show you my confusion:

  • ``Called when a page gains control''
  • What you mean is a page (synonomous with package in your docs) gains control when dispatch returns a string representing it. For example, given the ``pages'' Local::Page::Welcome, Local::Page::News, Local::Page::Webmail, we issue control to one of these pages like so:
    sub dispatch { my $self = shift; $self->param('rm') eq 'Webmail' and return 'Local::Page::Webmail' ; return 'Local::Page::Welcome'; }

    However, I was not able to gain this knowledge until I studied the CGIP-flow.t test case closely. The docs are really too terse on this point.

  • ``...Switched for rendering?''
  • What is switched for rendering? Under what conditions is it switched for rendering? Even after looking at activate() and the test suite, I don't know what you mean by switched for rendering and when such a switch might occur.

  • ``either at the beginning for a response, or in the middle when switched for rendering. ''
  • Am I to take this to mean that a page can gain control at either of these times? How do I know which of the times (beginning of response or middle) it is begin called? Also, you say ``in the middle''... in the middle of what? Also, you say ``either''... when you say either what leads to one of the selections being made.

    Perhaps you could provide some checkpoints into sub activate showing where each possibility conjectured by the word ``either'' occur. Code is here for your convenience:

    sub activate { my $self = shift; eval { $self->app_enter; my $this_page = $self->dispatch; $this_page->control_enter; $this_page->respond_enter; my $next_page = $this_page->respond; $this_page->respond_leave; if ($this_page ne $next_page) { $this_page->control_leave; $next_page->control_enter; } $next_page->render_enter; $next_page->render; $next_page->render_leave; $next_page->control_leave; $self->app_leave; }; $self->error($@) if $@; # failed something, go to safe mode }

render broke with the slot-oriented tradition

render() should push its output into a slot so that so that render_leave can get it and process it.

Besides breaking with the slot-orientation that pervades this module's design up to now, render() calls display () instead of display being called at the top level from activate().

Just in case render_leave() wants to do some touch-up, no display should be done until all rendering phases are complete.

Of course, the nice thing about CGI::Prototype is that everything I am talking about can be done in my root class for my application based on CGI::Prototype.

Core slot CGI

The docs say ``Invoking $self->CGI gives you access to the CGI.pm object representing the incoming parameters''

but it not clear how $self is obtained or what namespace it exists in.

And that leads to my biggest issue with CGI::Prototype --- the use of prototyped instead of class-based objects. It is not clear what this approach added. Actually let me help you out here. Instead of having to haul in a module to handle lazy-loading: http://www.metaperl.com/article-pod/Catalog-lazy_loaders/lazy_loaders.html you could simply defer this functionality an protoyped-objects autoload slot.

Fewer people are familiar with this approach to objects. And the use of them was confusing to me as I explain next

prototype-based objects

I read your article:

http://www.stonehenge.com/merlyn/LinuxMag/col56.html as well as the Class::Prototyped docs. However I still have some issues with the implementation of CGI::Prototype

First, you have a normal Perl package which has subroutines which are written just like any normal class-based object-oriented Perl package would have. But you also do this:

our $_mirror = __PACKAGE__->reflect; # for slots that aren't subs

and then access the mirror twice:

$_mirror->addSlot ([qw(CGI FIELD autoload)] => sub { # must be reset in mod_perl require CGI; CGI->new; });
$_mirror->addSlot ([qw(engine FIELD autoload)] => sub { my $self = shift; require Template; Template->new or die "Creating tt: $Template::ERROR\n"; + });

am I to understand that you have created a prototyped-based object with

app_enter app_leave control_enter control_leave render_enter render_leave respond_enter respond_leave

as slots created by normal Perl subroutines and two additional on-demand slots named CGI and engine?

If so, the please tell me how I can overwrite the engine method in my subclasses.

I wrote a simple script which returned a simple page:, but when I attempted to access the object and grok with it's slots, I had some problems, which are explained in the script.

I think the docs could use some information like this but would appreciate some feedback on this very quickly please

#!/usr/bin/perl BEGIN { my $path='~/perl/lib:~/perl:~/perl/share/perl/5.8.4:~/perl/lib/perl/ +5.8.4'; for my $p (split ':', $path) { $p =~ s!~!/home/terry!; warn $p; unshift @INC, $p; } warn "@INC"; } use base qw(CGI::Prototype); my $o = __PACKAGE__->reflect; # The next line returns # Class::Prototyped::Mirror=REF(0x816811c) at nullapp.pl line 23. # Why does it not return CGI::Prorotype::Mirror=REF # or main::MIRROR=REF warn $o; # Why does this fail? warn $o->engine; warn $o->display('hi'); CGI::Prototype->activate;


THE FUTURE

Even though I am having some problems on the learning curve with CGI::Prototype, I cannot wait until I can cleanly handle authentication-based rendering and business logic in the appropriate callbacks.

Replies are listed 'Best First'.
Re: CGI::Prototype: questions and feedback
by cbatjesmond (Novice) on Jan 29, 2005 at 17:02 UTC

    > but it not clear how $self is obtained or what namespace it exists in.

    I've been using $self in the callback methods which all start:

    sub respond { my $self = shift

    I'm not sure I understand your questions, but agree about reading code: The documentation I refer to most frequently is the definition of sub activate -- just to remind myself what happens, when.

      The documentation I refer to most frequently is the definition of sub activate -- just to remind myself what happens, when.
      Yes, the code is meant to be small, directly understandable, and extensible. That's why it's a lot of small methods instead of one humungous subroutine.

      That's not a cop-out for not providing better docs. My secret partner is feeding me examples and more tests, and I'm incorporating them in a new release probably in two weeks.

      -- Randal L. Schwartz, Perl hacker
      Be sure to read my standard disclaimer if this is a reply.

        why dont you create GMANE-based mailing list for your module merlyn? you know it's going to be popular. No need to answer questions privately when we could all benefit from the answers.
Re: CGI::Prototype: questions and feedback
by dynamo (Chaplain) on Jan 29, 2005 at 00:24 UTC
    I have not yet RTFA, but from glancing over it, please - read the formatting guidelines - specifically check out the <readmore> tag. If you had used it, I would have read your post by now.
Re: CGI::Prototype: questions and feedback
by metaperl (Curate) on Jan 29, 2005 at 16:19 UTC
    my $o = __PACKAGE__->reflect; # The next line returns # Class::Prototyped::Mirror=REF(0x816811c) at nullapp.pl line 23. # Why does it not return CGI::Prorotype::Mirror=REF # or main::MIRROR=REF warn $o; # Why does this fail? warn $o->engine; warn $o->display('hi'); CGI::Prototype->activate;
    it fails because you did not call object(). You only called reflect(). The docs for Class::Prototyped state that reflect should be called when you want to modify the structure of an object, but to get the object you must call object()
Re: CGI::Prototype: questions and feedback
by cbatjesmond (Novice) on Jan 31, 2005 at 01:25 UTC

    Trying to work through your questions in the hope of enlightenment for ... someone:

    control_enter's docs are confusing:

    I was also confused until I looked at activate():

    $this_page->control_enter; ... my $next_page = $this_page->respond; ... if ($this_page ne $next_page) { ... $next_page->control_enter; }

    So when you activate the $this_page package, it runs $this_page::control_enter. Then, IFF $this_page::respond returns a different package, we do $next_page::control_enter before $next_page::render*


    Just in case render_leave() wants to do some touch-up, no display should be done until all rendering phases are complete.

    Isn't this what render is for? I'm treating all the *_enter as "do before * starts" and *_leave as "do after * has finished".


    please tell me how I can overwrite the engine method in my subclasses

    ?? This is something that is in the docs. I C+P'd the example into my top-level package to set the Template defaults.


    my $o = __PACKAGE__->reflect; ...

    That's not anything to do with CGI::Prototype that I can see:-

    chrisb@taf:~ $ perldoc CGI::Prototype | grep -i reflect chrisb@taf:~ $

    I'm interested in CGI::Prototype because I'm tired of writing my own call-tables :-) but don't want to have to learn yet another API with dozens of calls.

    So far I've got 14 packages inheriting from CGI::Prototype. The main package declares its own app_enter, app_leave, dispatch, engine, error. Of the other packages, 11 declare template(), 11 have overridden respond()s and 5 overridden render_enter()s. There's a dozen or so subs scattered around, used for callbacks from templating.

    I've caught myself getting confused a couple of times and had to remind myself that the "usual" flow control (for me) is login::activate, login::respond(do nothing), login::render, login::respond then menu::render, or login::render. From menu::render, menu::respond, opt1::render, opt1::respond, then opt1::render or menu::render.

    I've having more trouble getting Class::DBI (which has copious, clear docs) to do what I want :-( This is also being used for the first time on this project, 'cause I'm tired of writing explicit SQL :-)

    Hope this helps.

Re: CGI::Prototype: questions and feedback
by metaperl (Curate) on Jan 29, 2005 at 16:32 UTC
    If so, the please tell me how I can overwrite the engine method in my subclasses.
    I quote from the Class::Prototyped docs:
    addSlots() - Add or replace slot definitions
    This would also be clear simply from studying the SYNOPSIS of Class::Prototyped.
Re: CGI::Prototype: questions and feedback
by metaperl (Curate) on Jan 30, 2005 at 02:46 UTC
    a page (synonomous with package in your docs)
    No, a page is something we can call CGI::Prototype methods on. It might be a package, but could just as well be a prototyped object.