Category: Miscellaneous
Author/Contact Info /msg runrig
Description: An experiment in AUTOLOAD and lvalue subs which resulted in a sort of combination of Hash::AsObject and Tie::SecureHash (update: actually more simply like Tie::Hash::FixedKeys or Hash::Util::lockkeys). The hash is 'secure' as long as you only access it as an object using methods, but you are free to add keys when directly accessing it as a hash ref. In the interest of keeping it simple, I'm not going to worry about handling keys like 'isa', 'can', 'AUTOLOAD', 'DESTROY', or '*::*'. And this definitely breaks can().

Updated/fixed code so that lvalue methods work better.(see reply node)


package Hash::AsObj;

my %sub_class;

sub new {
  my $class = shift;
  my $href = { @_ };
  my $sub_class;
  # Keep hashes with the same set of keys in
  # the same package
  my $class_key = join "~", sort keys %$href;
  if ( exists $sub_class{$class_key} ) {
    $sub_class = $sub_class{$class_key};
  } else {
   ( $sub_class = "$href" ) =~ tr/()/__/;
   $sub_class{$class_key} = $sub_class;
  @{"${class}::Data::${sub_class}::ISA"} = "Hash::AsObj::Data";
  bless $href, "${class}::Data::$sub_class";

package Hash::AsObj::Data;
use Carp ();

sub DESTROY { 1 }

sub AUTOLOAD : lvalue {
  my ( $class, $method ) = $AUTOLOAD =~ /^(.*)::(.+)$/
    or Carp::croak "Invalid call to $AUTOLOAD";
  Carp::croak "Can't locate object method $method via package $class"
    unless exists $_[0]->{$method};
  *$AUTOLOAD = sub : lvalue {
    my $self = shift;
    if (@_) {
      $self->{$method} = shift;
      return $self;
  goto &$AUTOLOAD;

package main;
use strict;
use warnings;

my $hobj = Hash::AsObj->new( a=>1, b=>2, c=>3 ,e=>5 );
print $hobj->a, "\n";
# It's an lvalue method!
print $hobj->a, "\n";
# Or supply an argument to set values
# I like method chaining - Your opinion may differ
print "b: ", $hobj->b, " c:", $hobj->c, "\n";
# Error - this key doesn't exist
print $hobj->d;