Name
Util::H2O - Hash to Object: turns hashrefs into objects with accessors for keys
Synopsis
use Util::H2O;
my $hash = h2o { foo => "bar", x => "y" }, qw/ more keys /;
print $hash->foo, "\n"; # accessor
$hash->x("z"); # change value
$hash->more("cowbell"); # additional keys
my $struct = { hello => { perl => "world!" } };
h2o -recurse, $struct; # objectify nested hashrefs as well
print $struct->hello->perl, "\n";
my $obj = h2o -meth, { # code references become methods
what => "beans",
cool => sub {
my $self = shift;
print $self->what, "\n";
} };
$obj->cool; # prints "beans"
h2o -classify=>'Point', { # whip up a class
angle => sub { my $self = shift; atan2($self->y, $self->x) }
}, qw/ x y /;
my $one = Point->new(x=>1, y=>2);
my $two = Point->new(x=>3, y=>4);
printf "%.3f\n", $two->angle; # prints 0.927
Description
This module allows you to turn hashrefs into objects, so that instead of $hash->{key}
you can write $hash->key
, plus you get protection from typos. In addition, options are provided that allow you to whip up really simple classes.
You can still use the hash like a normal hashref as well, as in $hash->{key}
, keys %$hash
, and so on, but note that by default this function also locks the hash's keyset to prevent typos there too.
This module exports a single function by default.
h2o @opts, $hashref, @additional_keys
@opts
If you specify an option with a value multiple times, only the last one will take effect.
-recurse
-
Nested hashes are objectified as well. The only options that are passed down to nested hashes are
-lock
and-ro
. None of the other options will be applied to the nested hashes, including@additional_keys
. Nested arrayrefs are not recursed into.Versions of this module before v0.12 did not pass down the
-lock
option, meaning that if you used-nolock, -recurse
on those versions, the nested hashes would still be locked. -meth
-
Any code references present in the hash at the time of this function call will be turned into methods. Because these methods are installed into the object's package, they can't be changed later by modifying the hash.
To avoid confusion when iterating over the hash, the hash entries that were turned into methods are removed from the hash. The key is also removed from the "allowed keys" (see the
-lock
option), unless you specify it in@additional_keys
. In that case, you can change the value of that key completely independently of the method with the same name. -class => classname
-
Specify the class name into which to bless the object (as opposed to the default: a generated, unique package name in
Util::H2O::
).Note: If you use this option,
-clean
defaults to false, meaning that the package will stay in Perl's symbol table and use memory accordingly, and since this function installs the accessors in the package every time it is called, if you re-use the same package name, you will get "redefined" warnings. Therefore, if you want to create multiple objects in the same package, you should probably use-new
. -classify => classname_string or $hashref
-
In the form
-classify => classname_string
, this is simply the short form of the options-new, -meth, -class => classname_string
.As of v0.16, in the special form
-classify => $hashref
, where the-classify
must be the last option in@opts
before the$hashref
, it is the same as-new, -meth, -class => __PACKAGE__, $hashref
- that is, the current package's name is used as the custom class name. It does not make sense to use this outside of an explicit package, since your class will be namedmain
. With this option, thePoint
example in the "Synopsis" can be written like the following, which can be useful if you want to add more things to thepackage
, or perhaps if you want to write your methods as regularsub
s:{ package Point; use Util::H2O; h2o -classify, { angle => sub { my $self = shift; atan2($self->y, $self->x) } }, qw/ x y /; }
Note
h2o
will remain in the package's namespace, one possibility is that you could load namespace::clean after you load this module. -isa => arrayref or scalar
-
Convenience option to set the
@ISA
variable in the package of the object, so that the object inherits from that/those package(s). This option was added in v0.14. -new
-
Generates a constructor named
new
in the package. The constructor works as a class and instance method, and dies if it is given any arguments that it doesn't know about. If you want more advanced features, like required arguments, validation, or other initialization, you should probably switch to something like Moo instead. -destroy => coderef
-
Allows you to specify a custom destructor. This coderef will be called from the object's actual
DESTROY
in void context with the first argument being the same as the first argument to theDESTROY
method. Errors will be converted to warnings. This option was added in v0.14. -clean => bool
-
Whether or not to clean up the generated package when the object is destroyed. Defaults to false when
-class
is specified, true otherwise. If this is false, be aware that the packages will stay in Perl's symbol table and use memory accordingly.As of v0.16, this module will refuse to delete the package if it is named
main
. -lock => bool
-
Whether or not to use Hash::Util's
lock_ref_keys
to prevent modifications to the hash's keyset. Defaults to true. The-nolock
option is provided as a short form of-lock=>0
.Keysets of objects created by the constructor generated by the
-new
option are also locked. Versions of this module before v0.12 did not lock the keysets of new objects.Note that on really old Perls, that is, before Perl v5.8.9, Hash::Util and its
lock_ref_keys
are not available, so the hash is never locked on those versions of Perl. Versions of this module before v0.06 did not lock the keyset. Versions of this module as of v0.12 issue a warning on old Perls. -nolock
-
Short form of the option
-lock=>0
. -ro
-
Makes the entire hash read-only using Hash::Util's
lock_hashref
and the generated accessors will also throw an error if you try to change values. In other words, this makes the object and the underlying hash immutable.You cannot specify any
@additional_keys
with this option enabled unless you also use the-new
option - the additional keys will then only be useful as arguments to the constructor. This option can't be used with-nolock
or-lock=>0
.This option was added in v0.12. Using this option will not work and cause a warning when used on really old Perls (before v5.8.9), because this functionality was not yet available there.
$hashref
You must supply a plain (unblessed) hash reference here. Be aware that this function does modify the original hashref(s) by blessing it and locking its keyset (the latter can be disabled with the -lock
option), and if you use -meth
or -classify
, keys whose values are code references will be removed.
An accessor will be set up for each key in the hash; note that the keys must of course be valid Perl identifiers for you to be able to call the method normally.
The following keys will be treated specially by this module. Please note that there are further keys that are treated specially by Perl and/or that other code may expect to be special, such as UNIVERSAL's isa
. See also perlsub and the references therein.
new
-
This key is not allowed in the hash if the
-new
option is on. DESTROY
-
This key is not allowed except if all of the following apply:
-destroy
is not used,-clean
is off (which happens by default when you use-class
),-meth
is on, andthe value of the key
DESTROY
is a coderef.
Versions of this module before v0.14 allowed a
DESTROY
key in more circumstances (whenever-clean
was off). AUTOLOAD
-
If your hash contains a key named
AUTOLOAD
, or this key is present in@additional_keys
, this module will set up a method calledAUTOLOAD
, which is subject to Perl's normal autoloading behavior - see "Autoloading" in perlsub and "AUTOLOAD" in perlobj. Without the-meth
option, you will get a "catch-all" accessor to which all method calls to unknown method names will go, and with-meth
enabled (which is implied by-classify
), you can install your own customAUTOLOAD
handler by passing a coderef as the value for this key - see "An Autoloading Example". However, it is important to note that enabling autoloading removes any typo protection on method names!
@additional_keys
Methods will be set up for these keys even if they do not exist in the hash.
Please see the list of keys that are treated specially above.
Returns
The (now blessed and optionally locked) $hashref
.
Cookbook
Using with Config::Tiny
One common use case for this module is to make accessing hashes nicer, like for example those you get from Config::Tiny. Here's how you can create a new h2o
object from a configuration file, and if you have Config::Tiny v2.27 or newer, the second part of the example for writing the configuration file back out will work too:
use Util::H2O;
use Config::Tiny;
my $config = h2o -recurse, {%{ Config::Tiny->read($config_filename) }};
say $config->foo->bar; # prints the value of "bar" in section "[foo]"
$config->foo->bar("Hello, World!"); # change value
# write file back out, requires Config::Tiny v2.27 or newer
Config::Tiny->new({%$config})->write($config_filename);
Please be aware that since the above code only uses shallow copies, the nested hashes are actually not copied, and the second Config::Tiny object's nested hashes will still be h2o
objects - but Config::Tiny doesn't mind this.
Debugging
Because the packages generated by h2o
are dynamic, note that any debugging dumps of these objects will be somewhat incomplete because they won't show the methods. However, if you'd like somewhat nicer looking dumps of the data contained in the objects, one way you can do that is with Data::Dump::Filtered:
use Util::H2O;
use Data::Dump qw/dd/;
use Data::Dump::Filtered qw/add_dump_filter/;
add_dump_filter( sub {
my ($ctx, $obj) = @_;
return { bless=>'', comment=>'Util::H2O::h2o()' }
if $ctx->class=~/^Util::H2O::/;
return undef; # normal Data::Dump processing for all other objects
});
my $x = h2o -recurse, { foo => "bar", quz => { abc => 123 } };
dd $x;
Outputs:
# Util::H2O::h2o()
{
foo => "bar",
quz => # Util::H2O::h2o()
{ abc => 123 },
}
An Autoloading Example
If you wanted to create a class where (almost!) every method call is automatically translated to a hash access of the corresponding key, here's how you could do that:
h2o -classify=>'HashLikeObj', -nolock, {
AUTOLOAD => sub {
my $self = shift;
our $AUTOLOAD;
( my $key = $AUTOLOAD ) =~ s/.*:://;
$self->{$key} = shift if @_;
return $self->{$key};
} };
Upgrading to Moo
Let's say you've used this module to whip up two simple classes:
h2o -classify => 'My::Class', {}, qw/ foo bar details /;
h2o -classify => 'My::Class::Details', {}, qw/ a b /;
But now you need more features and would like to upgrade to a better OO system like Moo. Here's how you'd write the above code using that, with some Type::Tiny thrown in:
package My::Class2 {
use Moo;
use Types::Standard qw/ InstanceOf /;
use namespace::clean; # optional but recommended
has foo => (is=>'rw');
has bar => (is=>'rw');
has details => (is=>'rw', isa=>InstanceOf['My::Class2::Details']);
}
package My::Class2::Details {
use Moo;
use namespace::clean;
has a => (is=>'rw');
has b => (is=>'rw');
}
See Also
Inspired in part by lock_keys
from Hash::Util.
Many, many other modules exist to simplify object creation in Perl. This one is mine ;-P
Similar modules include Object::Adhoc, Object::Anon, Hash::AsObject, Object::Result, and Hash::Wrap, the latter of which also contains a comprehensive list of similar modules.
For real OO work, I like Moo and Type::Tiny (see "Upgrading to Moo").
Author, Copyright, and License
Copyright (c) 2020-2021 Hauke Daempfling (haukex@zero-g.net).
This library is free software; you can redistribute it and/or modify it under the same terms as Perl 5 itself.
For more information see the Perl Artistic License, which should have been distributed with your copy of Perl. Try the command perldoc perlartistic
or see http://perldoc.perl.org/perlartistic.html.