Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl: the Markov chain saw

CGI::Application, inheritance trees, and 'the right way'

by geektron (Curate)
on Nov 01, 2004 at 21:37 UTC ( #404428=perlquestion: print w/replies, xml ) Need Help??

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

I have a new app that I'm trying to make sure I've created the right inheritance tree for common/ reused methods. Right now, it looks like this:
       |                    |
   EventManager        EmailManager
where really just holds utility function, common modules, etc. When I first put this together, it only required the EmailManager module, so I didn't have the SiteManager or EventManager pieces. now that the site is growing in functionality, I want to break out common methods , but i still want to run the application through one main CGI::Application-type  $obj->run() method.

Right now, both EmailManager and EventManager  use base qw# SiteManager #, which would make sense if i were running 2 different CGI-base scripts (which i've done previously).

i know that i could probably get away with instantiating one or the other object in the cgiapp_init method, but that doesn't feel like 'the right way'.

should i adjust my inheritance somehow? should i build a separate controller module that  uses both EmailManager and EventManager (and 'imports' their methods via the cgiapp_init() method), and have that be a subclass of SiteManager?

Replies are listed 'Best First'.
Re: CGI::Application, inheritance trees, and 'the right way'
by saberworks (Curate) on Nov 01, 2004 at 22:01 UTC
    Since I work with a fairly large set of scripts, I actually use one "main" CGI::Application to control say the "task" run-mode, and then have multiple chilren CGI::Applications to control the "subtask" run modes. This way, common functionality resides together and the code is all in manageable chunks. The main script is in charge of deciding which CGI::Application module to run.
        I don't inherit because there isn't really a parent-child relationship between the different modules. I'll explain in detail what I'm doing in my current project. It's done this way not because I designed it this way from scratch, but because I'm refactoring existing functionality from a mess of spagetti-code and using CGI::Application modules to split it up into managable pieces seemed like a good way to go.

        Say I have 3 main pieces of code for a shopping cart - user management, product management, and order management. In this case, I'd have one CGI::Application module that works on the run mode "action" variable. So we'd have three run-modes: user, product, and order.

        My "Main" CGI::Application would be using the "action" variable to decide the run-mode. It's only choices would be user, product, and order. Depending on which of these it is, it would instantiate another CGI::Application module of that type. So if the main run-mode is "user," it would do something like:

        my $user_app = new UserApp(...);

        The nice thing is that since each run-mode in "Main" is related to a different thing (user, product, or order), I can use those run-modes to set up the environment for each of those sub-run-modes differently. For example, if I have a "user" class which is used to do database interaction with the users table, I can create a user object and pass it to the User CGI::Application module as a parameter. That user object won't be created for the other modes if they don't need it.

        I should probably post some code, I'll try to write it as I do it, but this is untested:

        use strict; use warnings; package MainApp; sub setup { my $self = shift; $self->tmpl_path('./templates/'); $self->mode_param('action'); $self->run_modes( product => 'product', order => 'order', user => 'user' ); } sub product { my $self = shift; use Product; use ProductApp; my $p = new Product(); # Say this is an object with methods to int +eract with the products database my $pa = new ProductApp( PARAMS => { p => $p } # more args... ); return $pa->run(); } sub order { # Similar to product() } sub user { # Similar to product() } package ProductApp; sub setup { my $self = shift; $self->tmpl_path('./templates/'); $self->mode_param('task'); $self->run_modes( add => 'add', edit => 'edit', delete => 'delete' ); } sub delete { my $self = shift; my $cgi = $self->query(); my $p = $self->param('p'); if($p->delete($cgi->param('product_id'))) { return "The product was deleted!"; } else { return "There was a huge error."; } } 1;
        The thing to notice is that the sub-modules work off a different run-mode name ("task" vs. "action"), so if I wanted to grab that delete method, I could go like this:


        And of course to actually get it to delete something you'd have to pass a product ID:


        The reason I have different classes/objects to do database interaction is because we do a lot of reports and processing with command-line scripts in addition to our CGI::Application modules. If they're abstracted out like that, we can use those interfaces to do stuff from command line scripts as well without having to rewrite the same DB queries in multiple places.

        Anyway, I know this doesn't really address your question about inheritance, but I, like other people here, really don't like to use inheritance unless there is really a parent-child relationship. Generally, at least for me, there isn't such a relationshiop among various CGI::Application modules, as those don't contain much "real" logic - they are just interfaces to the real DB work that goes behind the scenes.
Re: CGI::Application, inheritance trees, and 'the right way'
by FoxtrotUniform (Prior) on Nov 01, 2004 at 22:44 UTC

    It sounds like is a repository for utility code. If that's the case, then it shouldn't inherit from CGI::Application. Going by the names you have, it sounds like EventManager and EmailManager aren't really kinds of SiteManagers; maybe all three are Managers. Inheritance should model "is-a" — if the child class doesn't quite fit, don't force it. That way lies madness.

    Don't inherit from a superclass just for code reuse: there are plenty of good ways to reuse code; inheritance is one, but building libraries with clean interfaces is perfectly good, too, and doesn't complicate your life with weird dispatch semantics. If you have to ask "Should foo really subclass bar?", the answer is probably no.

    Yours in pedantry,
    F o x t r o t U n i f o r m

    "Anything you put in comments is not tested and easily goes out of date." -- tye

      well, the big reason i have SiteManager as a parent class is because the authentication lives there, and it's really simple auth.

      other functions are utility functions.

Re: CGI::Application, inheritance trees, and 'the right way'
by weierophinney (Pilgrim) on Nov 02, 2004 at 04:03 UTC
    I'm not quite sure I completely understand where you're going, but I'll sketch what I can see from here, and one way I might approach the issues.

    I get the impression that you want to have a single module that then delegates to other sub-modules, all of which are CGI::Application-based. While I understand the desire to do that, I must say that when I have attempted this in the past, code maintenance has become a bit of a nightmare.

    If you look through the CGI::Application mailing list and the wiki, you'll see a lot of people posting a rule of thumb: if you have more than 7 run modes, you should probably refactor into another module. Experience shows that if you get more than that, it becomes harder to maintain the code -- it's more difficult to scan through it to determine what happens when.

    That said... what I'd recommend is keeping SiteManager as a CGI::Application superclass. Then have EventManager and EmailManager each inherit from it. If they need to extend or override functionality from SiteManager, let them. That means making SiteManager configurable -- don't have it doing things in cgiapp_init(), cgiapp_prerun(), cgiapp_postrun(), and teardown() that can't be overridden. Consider:

    package SiteManager; sub cgiapp_init { my $self = shift; my $params = @_; if ($self->param('start_event')) { $event = new Event(); $this->event = \$event; } } package EmailManager; sub cgiapp_init { my $self = shift; my $params = @_; $this->param('start_event', 1); parent::cgiapp_init(); $self->SUPER::cgiapp_init($params); $email = new Email(); $this->email = \$email; }
    In this case, SiteManager has made creation of the event propery optional by asking for a parameter to be set before it will do so. The parameter could be set by either the instance script or, as in this case, the child class.

    Typically, if you do something like this, you want to only put items in SiteManager that you're going to use over several child classes. So think about what you might need from a superclass that you'll use in your child classes and don't want to muck about with more than once: authentication, logging, insertion of content into a sitewide template, navigation, etc.

    And don't be conned into doing the single script "paradigm"; it'll only cause headaches down the road. :-)

      OK -- so i'm trying to get my head around this, and it's just confusing me.
      1. where is  $this coming from, and what is it supposed to reference?
      2. how does SiteManager dispatch sub-requests in this case? i can see that a new EventManager is instantiated, but when I tried it, i got an infinite loop
      maybe a better explanation of what i'm doing (or trying to do) is a better idea ...

      essentially, the admin side of this site has various management functions ( email editing, email sending, event calendar population ), and i'd like to keep on URL for the client so that he can just bookmark (or remember ) that and be done with it.

      because each 'set' of admin functions does different things, i'm trying to break those out into modules so that what got called for what set of functions is easier to find.

      i'm anticipating a menu (top or left, doesn't matter) where links like "manage email" and "manage events" will be, and those are the 'sets' of functions i'm using.

      i can see the links going to other CGI scripts, but the only thing that saves me, i think, is the confusion of trying the 'dispatch tables' i'm working towards now ...

        $this was a typo; should have been $self.

        In the example, SiteManager isn't dispatching sub-requests; I should have used different names than those you'd given. I was considering the modules that SiteManager might load being not additional CGI::Application modules or modules that create output but API level things -- grabbing events for a calendar that needs to be displayed on every page, or a site navigation that needs to be custom generated on each page.

        You can still have a single page that your client can bookmark, but this might be more of a homepage with links to other applications. The applications would all inherit from SiteManager so that you get:

        • Centralized authentication
        • Centralized navigation
        • Sitewide template shell
        • etc.

        Another possibility would be to have an application that would dynamically instantiate and run() other CGI::Applications:

        package SiteManager; use base qw/CGI::Application/; use EventManager; use EmailManager; sub setup { $self = shift; $self->run_modes( 'events' => 'events', 'email' => 'email ); } sub events { $self = shift; # Initialize a params hash or grab it from a config file and t +hen... $events = new EventManager($params); $events->run(); } sub email { $self = shift; # Initialize a params hash or grab it from a config file and t +hen... $email = new EmailManager($params); $email->run(); }

        However, even with this example, it might not be a bad idea to have a base class that takes care of authentication, site template, etc.

        $this is probably a typo for $self - C++ & PHP use the name "this" rather than the perl way, which allows you to name it whatever you want.
      this is pretty much *exactly* what i'm looking to do.

      SiteManager has code like "login", a dbh() method ( to eliminate code like $self->param('DBH')->prepare() ), etc ... plus some "utility" functions.

      i already have a few apps with more than 7 run_modes, and they are a nightmare to maintain, so i've been following my gut and refactoring for ease of maintenance. I've inherited most of them, but a few i think i've authored new.

Re: CGI::Application, inheritance trees, and 'the right way'
by dragonchild (Archbishop) on Nov 02, 2004 at 14:05 UTC
    There are a huge number of plugins for CGI::Application. The one you're looking for is CGI::Application::Dispatch. It will handle all those things for you in a simple fashion.

    Being right, does not endow the right to be rude; politeness costs nothing.
    Being unknowing, is not the same as being stupid.
    Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
    Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

      Seconded. See my earlier reply to saberworks post. I've started using it in a real-world application and it works quite nicely. I have a couple patches into the author for some enhancements I needed, who seems willing to put them in. :-) If you're interested the patches include:
      • Passing in the pre-translated application name to the application. Useful if you want to programatically create links between applications.
      • Under CGI mode, pass-along any params set via new() in the instance script.
      It makes it very easy to setup multiple applications w/o having to add <Location> (mod_perl) or instance scripts (CGI) for each one.
      that, in fact, does look like the cleanest solution.

      from the perldoc, i guess my  form action would just have the module name on it? like:

      <form name='add' method='post' action='/cgi-bin/index.cgi/EventMana +ger/'>
      and the hidden fields for RM would still apply?
        I'm not quite sure how that works, as I've never used it myself. Try it out with a few toy classes and see what happens. Alternatively, you can ask on the CGI::Application mailing list, which is where I heard about this.

        Being right, does not endow the right to be rude; politeness costs nothing.
        Being unknowing, is not the same as being stupid.
        Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
        Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

Log In?

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://404428]
Approved by kutsu
Front-paged by kutsu
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others wandering the Monastery: (8)
As of 2023-05-30 10:20 GMT
Find Nodes?
    Voting Booth?

    No recent polls found