perloopaccessor

Default Perl accessor for objects


If I have a Perl class eg

package Foo;

sub new {
    my ($class,$hashref) = @_;
    my $self = bless $hashref, $class;
}

and initialised with

my $foo = Foo->new( { bar => 2, othervar => 8 } );

I can do

print $foo->{ bar };

which feels clunky, and

print $foo->bar

feels more preferable. However, if there are a lot of keys, I'd prefer not to have to write an accessor for every key (or is that best practice) ?

So, I can include

our $AUTOLOAD;
sub AUTOLOAD {
    my $self = shift;

    my $called =  $AUTOLOAD =~ s/.*:://r;

    die "No such attribute: $called"
        unless exists $self->{$called};

    return $self->{$called};
}

sub DESTROY { } # see below

In perldoc perlobj it says # XXX - this is a terrible way to implement accessors

Are there any good ways to implement accessors like this, without using other packages, eg Moose, Class::Accessor ? I'm just after something light as its just one class that has a lot of keys.


Solution

  • Are there any good ways to implement accessors like this, without using other packages ...

    If you insist, then write those subs directly to the package symbol table

    package AutoAccessors;
    
    use warnings;
    use strict;
    use feature 'say';
    
    my @attr_names;
    
    BEGIN {
        @attr_names = qw(name mode etc);
        no strict 'refs';
        foreach my $accessor (@attr_names) {
            *{$accessor} = sub { do {
                if    (@_ == 1) { $_[0]->{$accessor} }
                elsif (@_ == 2) { $_[0]->{$accessor} = $_[1] }
                #elsif ...
            } };
        }
    };
    
    
    sub new {
        my ($class, $args) = @_;
        my $self;
        foreach my $attribute (@attr_names) {
            # Check, initialize, set from $args, etc 
            $self->{$attribute} = $args->{$attribute} if $args->{$attribute};
        }   
        return bless $self, $class;
    }   
    
    1;
    

    Then

    use warnings;
    use strict;
    use feature 'say';
    
    use AutoAccessors;
    
    my $obj = AutoAccessors->new({ mode => '007' }); 
    
    $obj->name('Bond');
    say "name's ", $obj->name;
    say "mode:  ", $obj->mode;
    

    This is done in a number of CPAN packages (and it's usually more elaborate).

    Having said that, I see no good reason to avoid good libraries, far more carefully written and tested and complete. For instance, Moo as a full system comes in at around 5 kloc (if I recall correctly) and has barely a handful of dependencies, while Class::Accessor is just over 200 loc with one dependency that I can see.