[ Team LiB ] Previous Section Next Section

Recipe 13.12 Generating Attribute Methods Using AUTOLOAD

13.12.1 Problem

Your object needs accessor methods to set or get its data fields, and you're tired of writing them all out one at a time.

13.12.2 Solution

Carefully use Perl's AUTOLOAD mechanism as something of a proxy method generator so you don't have to create them all yourself each time you want to add a new data field.

13.12.3 Discussion

Perl's AUTOLOAD mechanism intercepts all possible undefined method invocations. To disallow arbitrary data names, we store the list of permitted fields in a hash. The AUTOLOAD method checks for whether the accessed field name is in that hash.

package Person;
use strict;
use Carp;
our(%ok_field);

# Authorize four attribute fields
for my $attr ( qw(name age peers parent) ) { $ok_field{$attr}++; }

sub AUTOLOAD {
    my $self = shift;
    my $attr = our $AUTOLOAD;
    $attr =~ s/.*:://;
    return unless $attr =~ /[^A-Z]/;  # skip DESTROY and all-cap methods
    croak "invalid attribute method: ->$attr( )" unless $ok_field{$attr};
    $self->{uc $attr} = shift if @_;
    return $self->{uc $attr};
}
sub new {
    my $proto  = shift;
    my $class  = ref($proto) || $proto;
    my $parent = ref($proto) && $proto;
    my $self = {  };
    bless($self, $class);
    $self->parent($parent);
    return $self;
}
1;

This class supports a constructor method named new, plus four attribute methods: name, age, peers, and parent. Use the module this way:

use Person;
my ($dad, $kid);
$dad = Person->new;
$dad->name("Jason");
$dad->age(23);
$kid = $dad->new;
$kid->name("Rachel");
$kid->age(2);
printf "Kid's parent is %s\n", $kid->parent->name;
Kid's parent is Jason

This is tricky when producing inheritance trees. Suppose you'd like an Employee class that had every data attribute of the Person class, plus two new ones, like salary and boss. Class Employee can't rely upon an inherited Person::AUTOLOAD to determine what Employee's attribute methods are. So each class would need its own AUTOLOAD function. This would check just that class's known attribute fields, but instead of croaking when incorrectly triggered, it would invoke overridden superclass version.

Here's a version that takes this into consideration:

sub AUTOLOAD {
    my $self = shift;
    my $attr = our $AUTOLOAD;
    $attr =~ s/.*:://;
    return if $attr eq "DESTROY";

    if ($ok_field{$attr}) {
        $self->{uc $attr} = shift if @_;
        return $self->{uc $attr};
    } else {
        my $superior = "SUPER::$attr";
        $self->$superior(@_);
    }
}

If the attribute isn't in our OK list, we'll pass it up to our superior, hoping that it can cope with it. But you can't inherit this AUTOLOAD; each class has to have its own, because it is unwisely accessing class data directly, not through the object. Even worse, if a class A inherits from two classes B and C, both of which define their own AUTOLOAD, an undefined method invoked on A will hit the AUTOLOAD in only one of the two parent classes.

We could try to cope with these limitations, but AUTOLOAD eventually begins to feel like a kludge piled on a hack piled on a workaround. There are better approaches for the more complex situations.

One further proviso: the UNIVERSAL::can method will not normally report as invokable a method that would only trigger a class's AUTOLOAD. If you prefer that it do so, declare the methods without defining them. For example:

sub eat;
sub drink;
sub be_merry;
sub AUTOLOAD  {
    my $self = shift;
    my $funcname = our $AUTOLOAD;
    $funcname =~ s/.*:://;
    ...
}

You don't normally need to declare functions to trigger AUTOLOAD. If you had an object of that class:

$man->be_merry( );

the AUTOLOAD would still trigger, even without the declarations. However, you need the declarations to make the can method notice them:

$man->be_merry( ) if $man->can("be_merry");

13.12.4 See Also

The examples using AUTOLOAD in perltoot(1); Chapter 10 of Programming Perl; Recipe 10.15

    [ Team LiB ] Previous Section Next Section