NAME¶
Hash::AsObject - treat hashes as objects, with arbitrary accessors/mutators
SYNOPSIS¶
$h = Hash::AsObject->new;
$h->foo(123);
print $h->foo; # prints 123
print $h->{'foo'}; # prints 123
$h->{'bar'}{'baz'} = 456;
print $h->bar->baz; # prints 456
DESCRIPTION¶
A Hash::AsObject is a blessed hash that provides read-write access to its
elements using accessors. (Actually, they're both accessors and mutators.)
It's designed to act as much like a plain hash as possible; this means, for
example, that you can use methods like "DESTROY" to get or set hash
elements with that name. See below for more information.
METHODS¶
The whole point of this module is to provide arbitrary methods. For the most
part, these are defined at runtime by a specially written "AUTOLOAD"
function.
In order to behave properly in all cases, however, a number of special methods
and functions must be supported. Some of these are defined while others are
simply emulated in AUTOLOAD.
- new
-
$h = Hash::AsObject->new;
$h = Hash::AsObject->new(\%some_hash);
$h = Hash::AsObject->new(%some_other_hash);
Create a new Hash::AsObject.
If called as an instance method, this accesses a hash element 'new':
$h->{'new'} = 123;
$h->new; # 123
$h->new(456); # 456
- isa
- This method cannot be used to access a hash element 'isa', because
Hash::AsObject doesn't attempt to handle it specially.
- can
- Similarly, this can't be used to access a hash element 'can'.
- AUTOLOAD
-
$h->{'AUTOLOAD'} = 'abc';
$h->AUTOLOAD; # 'abc'
$h->AUTOLOAD('xyz') # 'xyz'
Hash::AsObject::AUTOLOAD recognizes when AUTOLOAD is begin called as an
instance method, and treats this as an attempt to get or set the
'AUTOLOAD' hash element.
- DESTROY
-
$h->{'DESTROY'} = [];
$h->DESTROY; # []
$h->DESTROY({}) # {}
"DESTROY" is called automatically by the Perl runtime when an
object goes out of scope. A Hash::AsObject can't distinguish this from a
call to access the element $h->{'DESTROY'}, and so it blithely gets (or
sets) the hash's 'DESTROY' element; this isn't a problem, since the Perl
interpreter discards any value that DESTROY returns when called
automatically.
- VERSION
- When called as a class method, this returns $Hash::AsObject::VERSION; when
called as an instance method, it gets or sets the hash element
'VERSION';
- import
- Since Hash::AsObject doesn't export any symbols, this method has no
special significance and you can safely call it as a method to get or set
an 'import' element.
When called as a class method, nothing happens.
The methods "can()" and "isa()" are special, because they're
defined in the "UNIVERSAL" class that all packages automatically
inherit from. Unfortunately, this means that you can't use Hash::AsObject to
access elements 'can' and 'isa'.
CAVEATS¶
No distinction is made between non-existent elements and those that are present
but undefined. Furthermore, there's no way to delete an element without
resorting to "delete $h->{'foo'}".
Storing a hash directly into an element of a Hash::AsObject instance has the
effect of blessing that hash into Hash::AsObject.
For example, the following code:
my $h = Hash::AsObject->new;
my $foo = { 'bar' => 1, 'baz' => 2 };
print ref($foo), "\n";
$h->foo($foo);
print ref($foo), "\n";
Produces the following output:
HASH
Hash::AsObject
I could fix this, but then code like the following would throw an exception,
because "$h->foo($foo)" will return a plain hash reference, not
an object:
$h->foo($foo)->bar;
Well, I can make "$h->foo($foo)->bar" work, but then code like
this won't have the desired effect:
my $foo = { 'bar' => 123 };
$h->foo($foo);
$h->foo->bar(456);
print $foo->{'bar'}; # prints 123
print $h->foo->bar; # prints 456
I suppose I could fix
that, but that's an awful lot of work for little
apparent benefit.
Let me know if you have any thoughts on this.
BUGS¶
Autovivification is probably not emulated correctly.
The blessing of hashes stored in a Hash::AsObject might be considered a bug. Or
a feature; it depends on your point of view.
TO DO¶
- •
- Add the capability to delete elements, perhaps like this:
use Hash::AsObject 'deleter' => 'kill';
$h = Hash::AsObject->new({'one' => 1, 'two' => 2});
kill $h, 'one';
That might seem to violate the prohibition against exporting functions from
object-oriented packages, but then technically it wouldn't be exporting it
from anywhere since the function would be constructed by hand.
Alternatively, it could work like this:
use Hash::AsObject 'deleter' => 'kill';
$h = Hash::AsObject->new({'one' => 1, 'two' => 2});
$h->kill('one');
But, again, what if the hash contained an element named 'kill'?
- •
- Define multiple classes in "Hash/AsObject.pm"? For example,
there could be one package for read-only access to a hash, one for hashes
that throw exceptions when accessors for non-existent keys are called,
etc. But this is hard to do fully without (a) altering the underlying
hash, or (b) defining methods besides AUTOLOAD. Hmmm...
VERSION¶
0.06
AUTHOR¶
Paul Hoffman <nkuitse AT cpan DOT org>
CREDITS¶
Andy Wardley for Template::Stash, which was my inspiration. Writing template
code like this:
[% foo.bar.baz(qux) %]
Made me yearn to write Perl code like this:
foo->bar->baz($qux);
COPYRIGHT¶
Copyright 2003-2007 Paul M. Hoffman. All rights reserved.
This program is free software; you can redistribute it and modify it under the
same terms as Perl itself.