use
vars
qw($VERSION $AUTOLOAD)
;
$VERSION
=
'0.13'
;
sub
VERSION {
return
$VERSION
unless
ref
(
$_
[0]);
scalar
@_
> 1 ?
$_
[0]->{
'VERSION'
} =
$_
[1] :
$_
[0]->{
'VERSION'
};
}
sub
can {
die
"Usage: UNIVERSAL::can(object-ref, method)"
unless
@_
== 2;
my
(
$invocant
,
$method
) =
@_
;
my
$cls
=
ref
(
$invocant
) ||
$invocant
;
no
strict
'refs'
;
return
sub
{
my
$v
;
if
(
scalar
@_
> 1) {
$v
=
$_
[0]->{
$method
} =
$_
[1];
return
undef
unless
defined
$v
;
}
else
{
$v
=
$_
[0]->{
$method
};
}
if
(
ref
(
$v
) eq
'HASH'
) {
bless
$v
,
$cls
;
}
else
{
$v
;
}
};
}
sub
import
{
return
unless
ref
(
$_
[0]);
scalar
@_
> 1 ?
$_
[0]->{
'import'
} =
$_
[1] :
$_
[0]->{
'import'
};
}
sub
AUTOLOAD {
my
$invocant
=
shift
;
my
$key
=
$AUTOLOAD
;
if
(
defined
$key
) {
$key
=~ s/.*:://;
}
else
{
$key
=
'AUTOLOAD'
;
}
undef
$AUTOLOAD
;
if
(
ref
(
$invocant
) eq
''
) {
if
(
$key
eq
'import'
) {
return
;
}
elsif
(
$key
eq
'new'
) {
my
$elems
=
scalar
(
@_
) == 1
?
shift
: {
@_
}
;
return
bless
$elems
,
$invocant
;
}
else
{
die
"Can't invoke class method '$key' on a Hash::AsObject object"
;
}
}
elsif
(
$key
eq
'DESTROY'
) {
return
$invocant
->{
'DESTROY'
}
unless
scalar
@_
or
exists
$invocant
->{
'DESTROY'
};
}
die
"Too many arguments"
if
scalar
(
@_
) > 1;
if
(
$key
eq
'AUTOLOAD'
) {
return
scalar
(
@_
) ?
$invocant
->{
'AUTOLOAD'
} =
shift
:
$invocant
->{
'AUTOLOAD'
};
}
else
{
my
$cls
=
ref
(
$invocant
) ||
$invocant
;
no
strict
'refs'
;
*{
"${cls}::$key"
} =
sub
{
my
$v
;
if
(
scalar
@_
> 1) {
$v
=
$_
[0]->{
$key
} =
$_
[1];
return
undef
unless
defined
$v
;
}
else
{
$v
=
$_
[0]->{
$key
};
}
if
(
ref
(
$v
) eq
'HASH'
) {
bless
$v
,
$cls
;
}
else
{
$v
;
}
};
unshift
@_
,
$invocant
;
goto
&{
"${cls}::$key"
};
}
}
1;
=head1 NAME
Hash::AsObject - treat hashes as objects,
with
arbitrary accessors/mutators
=head1 SYNOPSIS
$h
= Hash::AsObject->new;
$h
->foo(123);
print
$h
->foo;
print
$h
->{
'foo'
};
$h
->{
'bar'
}{
'baz'
} = 456;
print
$h
->bar->baz;
=head1 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 C<DESTROY> to get or set hash elements
with
that name. See below
for
more information.
=head1 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 C<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.
=over 4
=item B<new>
$h
= Hash::AsObject->new;
$h
= Hash::AsObject->new(\
%some_hash
);
$h
= Hash::AsObject->new(
%some_other_hash
);
Create a new L<Hash::AsObject|Hash::AsObject>.
If called as an instance method, this accesses a hash element
'new'
:
$h
->{
'new'
} = 123;
$h
->new;
$h
->new(456);
=item B<isa>
This method cannot be used to access a hash element
'isa'
, because
Hash::AsObject doesn't attempt to handle it specially.
=item B<can>
Similarly, this can
't be used to access a hash element '
can'.
=item B<AUTOLOAD>
$h
->{
'AUTOLOAD'
} =
'abc'
;
$h
->AUTOLOAD;
$h
->AUTOLOAD(
'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.
=item B<DESTROY>
$h
->{
'DESTROY'
} = [];
$h
->DESTROY;
$h
->DESTROY({})
C<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.
=item B<VERSION>
When called as a class method, this returns C<
$Hash::AsObject::VERSION
>;
when
called as an instance method, it gets or sets the hash element
'VERSION'
;
=item B<
import
>
Since L<Hash::AsObject|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.
=back
The methods C<can()> and C<isa()> are special, because they're
defined
in the
C<UNIVERSAL> class that all packages automatically inherit from. Unfortunately,
this means that you can't
use
L<Hash::AsObject|Hash::AsObject> to access elements
'can'
and
'isa'
.
=head1 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 C<<
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 C<<
$h
->foo(
$foo
) >> will
return
a plain hash reference, not
an object:
$h
->foo(
$foo
)->bar;
Well, I can make C<<
$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'
};
print
$h
->foo->bar;
I suppose I could fix I<that>, but that's an awful lot of work
for
little
apparent benefit.
Let me know
if
you have any thoughts on this.
=head1 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.
=head1 TO DO
=over 4
=item *
Add the capability to
delete
elements, perhaps like this:
$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 B<from> anywhere since the function would be constructed
by hand. Alternatively, it could work like this:
$h
= Hash::AsObject->new({
'one'
=> 1,
'two'
=> 2});
$h
->
kill
(
'one'
);
But, again, what
if
the hash contained an element named
'kill'
?
=item *
Define multiple classes in C<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...
=back
=head1 VERSION
0.06
=head1 AUTHOR
Paul Hoffman <nkuitse AT cpan DOT org>
=head1 CREDITS
Andy Wardley
for
L<Template::Stash|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
);
=head1 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.