in reply to Using AUTOLOAD to create class methods

As a general rule, you should avoid AUTOLOAD like the plague. there are many problems with it (not the least of which is that eval you have going on). However, if you must use AUTOLOAD, you should consider providing a can method. If you don't, the module will lie when asked about its capabilities.

Another way of approaching your problem would be to use a delegation class (simplified for clarity).

package TestModule; require DBI; use Class::Delegator send => [qw/prepare do execute .../], to => '_dbi'; sub _dbi { shift->{DBI} } sub new { my $class = shift; return unless @_; return bless { DBI => DBI->connect(@_) }, $class; }

With this approach you get fine-grained control over the exposed interface and you don't have to wonder about whether your AUTOLOAD is exposing something it shouldn't or if your eval is secure.

Cheers,
Ovid

New address of my CGI Course.

Replies are listed 'Best First'.
Re^2: Using AUTOLOAD to create class methods
by radiantmatrix (Parson) on Nov 03, 2005 at 16:36 UTC

    Hm, yes, I am aware of the potential problems with AUTOLOAD. The approach above does work -- but it has one problem that the use of AUTOLOAD solves: I want to always have all of a DBH's methods sent to my DBH. So, it looks like I'm faced with a tradeoff.

    On the one hand, I could do as you suggest above, but I'd have to maintain a list of the methods on my {DBH} attribute. If I miss one, then I have a bug. This also raises the complexity of my test suite.

    On the other hand, I could continue to use AUTOLOAD, and deal with the issue. My current version of the AUTOLOAD sub:

    # sets up autoload subs that 'inherit' DBI's DBH methods sub AUTOLOAD { my $op = $AUTOLOAD; my $self = shift; $op =~ s/^.*:://; if ( DBI::db->can($op) ) { # create a wrapper for a DBH method eval "sub $op { return shift->{DBH}->$op(\@_); }"; $op->($self, @_); } elsif ( $op =~ /^_/ ) { # return the appropriate attribute $op =~ s/^_//; exists $self->{$op} && return $self->{$op}; return $self->error("Can't autoload for attribute _$op"); } else { return $self->error("Cannot autoload $op"); } }

    I don't worry too much about the security of my eval in this case, since this is a module, and doesn't directly process user input. My scripts never use user input to pass to AUTOLOAD either. Finally, I don't eval anything until I've checked that it's a DBH method. That, all in all, seems pretty safe, though I'd love to be enlightened if I'm missing something there.

    I like the idea of Class::Delegator (and, actually, it solved another issue I've been pondering recently, so thanks!), but I'm wondering if there's a way to get the important feature that AUTOLOAD is providing me (making all of the DBI dbh methods available) without the issues AUTOLOAD raises.

    Any ideas?

    <-radiant.matrix->
    A collection of thoughts and links from the minds of geeks
    The Code that can be seen is not the true Code
    "In any sufficiently large group of people, most are idiots" - Kaa's Law

      There's no way you can do it perfectly reliably without AUTOLOAD because of Perl's poor introspection. And, as you note, if you miss a method you have a bug. You can either publish which methods you support (thus turning the bug into a "feature") or have your AUTOLOAD handle it. You might want to just check can (which returns a code ref). The following untested bit might help:

      if (my $sub = $self->{DBI}->can($op)) { no strict 'refs'; *$op = $sub; }

      You'd have to customize that for your needs, but it has the benefit of installing the subroutine directly into your namespace so you only take the AUTOLOAD hit on the first call.

      Cheers,
      Ovid

      New address of my CGI Course.

        Do you have any information that compares getting repeated hits to AUTOLOAD vs once and losing the method cache?