NAME
Util::H2O::More - Provides baptise
, semantically and idiomatically just like bless
but creates accessors for you. It does other cool things to make Perl code easier to read and maintain, too.
SYNOPSIS
Below is an example of a traditional Perl OOP class constructor using baptise
to define a set of default accessors, in addition to any that are created by virtue of the %opts
passed.
use strict;
use warnings;
package Foo::Bar;
# exports 'h2o' also
use Util::H2O::More qw/baptise/;
sub new {
my $pkg = shift;
my %opts = @_;
# replaces bless, defines default constructures and creates
# constructors based on what's passed into %opts
my $self = baptise \%opts, $pkg, qw/bar haz herp derpes/;
return $self;
}
1;
Then on a caller script,
use strict;
use warnings;
use Foo::Bar;
my $foo = Foo::Bar->new(some => q{thing}, else => 4);
print $foo->some . qq{\n};
# set bar via default accessor
$foo->bar(1);
print $foo->bar . qq{\n};
# default accessors also available from the class defined
# above,
# $foo->haz, $foo->herp, $foo->derpes
# and from the supplied tuple,
# $foo->else
It is easy to create an OOP module using baptise
instead of bless
, which means it includes accessors (thanks to Util::H2O::h2o
). In most cases, baptise
can be used as a drop-in replacement for bless
.
For more examples, please look at the classes created for the unit tests contained in t/lib
.
NB: There are other useful methods, so please read all of the POD. This section simply covers baptise
, which was the first method based on Util::H2O::h2o
presented in this module.
DESCRIPTION
The primary method, baptise
, essentially provides the same interface as the core keyword bless
with an additional slurpy third parameter where one may specify a list of default accessors.
Ultimately h2o
provides a very compelling approach that allows one to incrementally add OOP into their Perl. At the very least it makes dealing with HASH
references much easier, and without the committment to a full Perl OOP framework. Perl is meant to be multi-paradigm, which means that it should be easy to mix the best of different methods into one glorious creation. Util::H2O, and by extension, Util::H2O::More
; seeks to accomplish making it possible for OOP concepts.
Util::H2O::h2o
is a deceptively powerful tool that, above all, makes it easy and fun to add accessors to ad hoc HASH
references that many Perl developers like to use and that get returned, unblessed by many popular modules. For example, HTTP::Tiny, Web::Scraper, and the more common select% methods DBI flavors implement. In particular, any JSON returned by a HTTP::Tiny web request is not just ublessed, but still serialized. Yet another great example is the configuration object returned by the very popular module, Config::Tiny.
Still more useful utilities may be built upon h2o
, e.g.; d2o
which is able to handle data structures that contain HASH
references buried or nested arbitrarily within ARRAY
references.
For example, d2o
cleans things up very nicely for dealing with web APIs:
my $response = h2o HTTP::Tiny->get($JSON_API_URL);
die if not $response->success;
my $JSON_data_with_accessors = d2o JSON::decode_json $response->content;
Finally, and what started this module; the usage pattern of h2o
begs it to be able to support being used as a drop in replacement for bless
. But is does a fine job as serving as the basis for a better bless.
This module also provides additional methods built using h2o
or o2h
from Util::H2O that allow for the incremental addition of OOP into existing or small scale Perl code without having to fully commit to a Perl OOP framework or compromise one's personal Perl style.
Util::H2O::More
now provides a wrapper method now, d2o
that will find and objectify all HASH
refs contained in ARRAY
s at any level, no matter how deep. This ability is very useful for dealing with modern services that return ARRAY
s of HASH
, traditional DBI queries, and other modules that can provide LIST
s of HASH
refs, such as Web::Scraper.
d2o
and o2d
would not have been added if the author of this module had been keeping up with the latest features of Util::H2O
. If nested data structure support is what you need, please see if h2o
's -array
is what you want; it tells h2o
to descende into ARRAY
s and applies h2o -recurse
to them if found; this is extremely useful for dealing with data structures generated from deserializing JSON (e.g.,).
This module provides some other compelling methods, such as those implementing the cookbook suggestions described in Util::H2O
. Which make it easier to deal with modules such as Config::Tiny (ini2h2o
, h2o2ini
), handle non-compliant keys tr4h2o
, or even provide convient access to Data::Dumper
(via ddd
).
You may have come here for the baptise
, but stay for the other stuff -all built with the purpose of showing people the way to cleaning up their Perl with Util::H2O
!
METHODS
baptise [-recurse] REF, PKG, LIST
Takes the same first 2 parameters as bless
; with the addition of a list that defines a set of default accessors that do not rely on the top level keys of the provided hash reference.
The -recurse option:
Like baptise
, but creates accessors recursively for a nested hash reference. Uses h2o
's -recurse
flag. I know it's weird having a -recurse option for a method called, baptise. I don't make the rule :-).
Note: The accessors created in the nested hashes are handled directly by h2o
by utilizing the -recurse
flag. This means that they will necessarily be blessed using the unchangable behavior of h2o
, which maintains the name space of Util::H2O::_$hash
even if h2o
is passed with the -isa
and -class
flags, which are both utilized to achieve the effective outcome of baptise
and bastise -recurse
.
tr4h2o REF
Replaces all characters not considered legal for subroutines or accessors methods with an underscore, _
. It is taken directly from the Util::H2O cookbook on dealing with keys that are not compliant. However the solution used internally to this module doesn't use List::Util
's pairmap
method due to some issues encountered during the development of tr4h2o
.
tr4h2o
provides a way to retreive the original LIST of offensive keys, __og_keys
.
The following example is sufficient to demonstrate it's use, and again, it is taken straight from the Util::H2O
POD.
use Util::H2O::More qw/h2o tr4h2o ddd/;
my $hash = { "foo bar" => 123, "quz-ba%z" => 456 };
my $obj = h2o tr4h2o $hash;
print $obj->foo_bar, $obj->quz_ba_z, "\n"; # prints "123456
# inspect new structure
ddd $obj; # Data::Dumper::Dumper
ddd $obj->__og_keys; # Data::Dumper::Dumper
Output:
123456
$VAR1 = bless( {
'__og_keys' => {
'foo_bar' => 'foo bar',
'quz_ba_z' => 'quz-ba%z'
},
'quz_ba_z' => 456,
'foo_bar' => 123
}, 'Util::H2O::_7a1ad8c03918' );
$VAR1 = {
'foo_bar' => 'foo bar',
'quz_ba_z' => 'quz-ba%z'
};
Note: this is not applied to recursive data structures, something like that would best be supported upstream by Util::H2O
using a flag like -tr
that assumes transliteration using, tr/a-zA-Z0-9/_/c
.
Getopt2h2o REF, REF, LIST
Wrapper around the idiom enabled buy opt2h2o
. It even will require
Getopt::Long
. Usage:
use Util::H2O::More qw/Getopt2h2o/;
my $opts_ref = Getopt2h2o \@ARGV, { n => 10 }, qw/f=s n=i/;
The first argument is the a refernece to the @ARGV
array (or equivalent), the second argument is the initial state of the hash to be objectified by h2o
, the rest of the arguments is an array containing the Getopt::Long
argument description.
This methods was created because even opt2h2o
was too much typing :-).
opt2h2o LIST
Handy function for working with Getopt::Long
, which takes a list of options meant for Getopt::Long
; and extracts the flag names so that they may be used to create default accessors without having more than one list. E.g.,
use Getopt::Long qw//;
my @opts = (qw/option1=s options2=s@ option3 option4=i o5|option5=s/);
my $o = h2o {}, opt2h2o(@opts);
Getopt::Long::GetOptionsFromArray( \@ARGV, $o, @opts ); # Note, @ARGV is passed by reference
# now options are all available as accessors, e.g.:
if ($o->option3) {
do_the_thing();
}
Note: default values for options may still be placed inside of the anonymous hash being objectified via h2o
. This will work perfectly well with baptise
and friends.
use Getopt::Long qw//;
my @opts = (qw/option1=s options2=s@ option3 option4=i o5|option5=s/);
my $o = h2o { option1 => q{foo} }, opt2h2o(@opts);
Getopt::Long::GetOptionsFromArray( \@ARGV, $o, @opts ); # Note, @ARGV is passed by reference
# ...
# now $o can be used to query all possible options, even if they were
# never passed at the commandline
HTTPTiny2h2o REF
This method is particularly handy when dealing with HTTP::Tiny responses, which are generally returned as HASH
references of the form,
{
status => 200,
content => q/some string, could be JSON, etc/,
...
}
The method is aware of this structure, and if the content
key exists, it will decode the contents of content
as JSON, and if successful will apply d2o -autoundef
to it. The ideal result is that the serialized JSON returned by the HTTP::Tiny
web request can be accessed directly in a chained way, e.g., Given a HTTP::Tiny
response HASH
of the form,
my $response = {
status => 200,
content => q/{"foo":"bar","baz":{"herp"=>"derp"}}/,
}
Then, the following will work like so:
use Util::H2O::More qw/HTTPTiny2h2o/;
my $response = HTTP::Tiny->new->get(...);
HTTPTiny2h2o $response;
say $response->content->herp; # says "derp"
Or more succinctly,
use Util::H2O::More qw/HTTPTiny2h2o/;
my $response = HTTPTiny2h2o HTTP::Tiny->new->get(...);
say $response->content->herp; # says "derp"
HTTPTiny2h2o
May die
!
If something is wrong with REF
, then HTTPTiny2h2o
will throw an exception via die
, so it's good to be confident of what you're passing to it.
Similarly, any exceptions thrown by JSON::MaybeXS's decode_json
is allowed to propagate up.
Note on the Serialization Format
At this time, only serialized JSON
content is handled. If another serialization format is needed, please let the module author know. This method also doesn't check any headers to see what type it's supposed to be. The validity of the JSON is solely determined by decode_json
.
yaml2h2o FILENAME_OR_YAML_STRING
Takes a single parameter that may be the name of a YAML file (in which case it uses YAML's LoadFile
) or as a string of YAML (in which case it uses YAML's Load
to parse it). If either YAML::LoadFile
or YAML::Load
fails and throws an exception, it will not be caught and propagate to the caller of yaml2h2o
.
Note: YAML
may contain more than one serialized object (separated by a line with ---\n
. Therefore yaml2h2o
should be treated as returning a list of d2o
'd objects.
For example, say the YAML file looks like the following, saved as myfile.yaml
:
---
database:
host: localhost
port: 3306
db: users
username: appuser
password: 1uggagel2345
---
devices:
copter1:
active: 1
macaddr: ab:ba:0b:a1:1a:d7
host: 192.168.1.11
port: 80
thingywhirl:
active: 1
macaddr: de:ad:0d:db:a1:11
host: 192.168.1.33
port: 80
Then the one may use the yaml2h2o
as follows:
my @objects_from_yaml = yaml2h2o q{/path/to/myfile.yaml>;
And @objects_from_yaml
will have 2 objects in it, but having been bless
ed with accessors via the d2o
command, which will detect and handle properly lists.
If known ahead of time how many serialized objects myfile.yaml
would have contained, then this is also a useful way to call it:
my ($dbconfig, $devices) = yaml2h2o q{/path/to/myfile.yaml};
More exotic YAML
files may break yaml2h2o
; this method was added to support simple YAML
. YAML can be used to encode a lot of things that we do not need.
If you just need, e.g., the $dbconfig
; then this trick would apply well also:
my ($dbconfig, undef) = yaml2h2o q{/path/to/myfile.yaml};
Similarly, the following works as expected. The only different is that $YAML
contains a string.
my $YAML =<< EOYAML;
---
database:
host: localhost
port: 3306
db: users
username: appuser
password: 1uggagel2345
---
devices:
copter1:
active: 1
macaddr: a0:ff:6b:14:19:6e
host: 192.168.0.14
port: 80
thingywhirl:
active: 1
macaddr: 00:88:fb:1a:5f:08
host: 192.168.0.14
port: 80
EOYAML
my ($dbconfig, $devices) = yaml2h2o $YAML;
yaml2h2o
May die
!
If whatever is passed to yaml2h2o
looks like neither a block of YAML or a file name, an exception will be thrown with an error saying as much.
Note on Serialization to YAML
There is no o2yaml
that serializes an object to a file. One may be provided at a later date. If you're reading this and want it, please create an issue at the Github repository to request it (or other things).
See also, o2h
, which returns the pure underlying Perl data structure that is objectified by h2o
, baptise
, ini2h2o
, yaml2h2o
, d2o
, etc.
yaml2o FILENAME
Alias to yaml2h2o
for backward compatibility. I made the same inconsistent choice when originally adding YAML
support in the naming, so this is just a shim to bridge that gap.
ini2h2o FILENAME
Takes the name of a file, uses Config::Tiny to open it, then gives it accessors using internally, o2h2o
, described below.
Given some configuration file using INI:
[section1]
var1=foo
var2=bar
[section2]
var3=herp
var4=derp
We can parse it with Config::Tiny and objectify it with h2o
:
use Util::H2O::More qw/ini2h2o/;
my $config = ini2h2o qq{/path/to/my/config.ini}
# ... $config now has accessors based Config::Tiny's read of config.ini
ini2o
is provided as a wrapper because this method was renamed in 0.2.4.
h2o2ini REF, FILENAME
Takes and object created via ini2h2o
and writes it back out to FILENAME
in the proper INI format, using Config::Tiny.
Given the example in ini2h2o
, we can go a step further and writ eout a new configuration file after reading it and modifying a value.
use Util::H2O::More qw/ini2h2o h2o2ini/;
my $config = ini2h2o q{/path/to/my/config.ini}
# update $config, write it out as a different file
$config->section1->var1("some new value");
h2o2ini $config, q{/path/to/my/other-config.ini};
o2ini
is provided as a wrapper because this method was renamed in 0.2.4.
o2h2o REF
Primarily inspired by Util::H2O's example for adding accessors to an reference that has already been blessed by another package. The motivating example is one that shows how to add accessors to a Config::Tiny object.
o2h REF
Uses Util::H2O::o2h
, so behaves identical to it. A new hash reference is returned, unlike h2o
or baptise
. See Util::H2O
's POD for a lot more information.
This method complements h2o
or baptise
very well in the sitution, e.g., when one is dealing with an ad hoc object that then needs to be sent as a serialzied JSON string. It's convenient to be able to get the un<bless>'d data structure most JSON encoding methods are expecting.
Implementation note:
Access to Util::H2O::o2h
, but adjusts $Util::H2O::_PACKAGE_REGEX
to accept package names that are generated by baptise
. This effectively is an unbless.
Historical Note:
Util::H2O::More::o2h
preceded Util::H2O::o2h
, and the author of the latter added it after seeing it's usefulness in cases where the ability to get a pure HASH
reference after having objectified was useful. A good example is the standard dislike JSON
modules' encode_json
method implementations have for blessed references; this also affects environments like Dancer2 or Mojo that employ automatic serialization steps beyond route handlers. Returning a blessed reference would cause the underlying serialization routines to warn or die
without using o2h
to return a pure HASH
reference.
d2o [-autoundef] REF
The optional flag -autoundef
will attach an AUTOLOAD
method to each individual HASH reference object that wwill allow uninitialized hash keys to be called as a method, and it will automatically return undef
. See the -autoundef section for more.
This method is essentially a wrapper around h2o
that will traverse an arbitrarily complex Perl data structure, applying h2o
to any HASH
references along the way.
A common usecase where d2o
is useful is a web API call that returns some list of HASH
references contained inside of an ARRAY
reference.
For example,
my $array_of_hashes = JSON::decode_json $json;
d2o $array_of_hashes;
my $co = $array_of_hashes->[3]->company->name;
Here, $array_of_hashes
is an ARRAY
reference that contains a set of elements that are HASH
references; a pretty common situation when dealing with records from an API or database call. [3]
, refers the 4th element, which is a HASH
reference. This HASH
reference has an accessor via d2o
, and this returns another HASH
that has an accessor called name
.
The structure of $array_of_hashes
is based on JSON, e.g., that is of the form:
[
{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "Sincere@april.biz",
"address": {
"street": "Kulas Light",
"suite": "Apt. 556",
"city": "Gwenborough",
"zipcode": "92998-3874",
"geo": {
"lat": "-37.3159",
"lng": "81.1496"
}
},
"phone": "1-770-736-8031 x56442",
"website": "hildegard.org",
"company": {
"name": "Romaguera-Crona",
"catchPhrase": "Multi-layered client-server neural-net",
"bs": "harness real-time e-markets"
}
},
{
"id": 2,
"name": "Ervin Howell",
"username": "Antonette",
"email": "Shanna@melissa.tv",
"address": {
"street": "Victor Plains",
"suite": "Suite 879",
"city": "Wisokyburgh",
"zipcode": "90566-7771",
"geo": {
"lat": "-43.9509",
"lng": "-34.4618"
}
},
"phone": "010-692-6593 x09125",
"website": "anastasia.net",
"company": {
"name": "Deckow-Crist",
"catchPhrase": "Proactive didactic contingency",
"bs": "synergize scalable supply-chains"
}
},
...
]
(* froms, https://jsonplaceholder.typicode.com/users)
-autoundef
There is an optional flag for d2o
that will allow one to call a non-existing key using its setter form, and via AUTOLOAD
will return undef
, this saves some code and piercing the veil into the HASH itself;
For example, before, it is necessary to pierce the veil of the HASH ref:
my $hash_ref = somecall(..);
d2o $hash_ref;
my @mightexist = qw/foo bar baz/;
foreach my $k (@mightexist) {
say $hash_ref->$k if (exists $hash_ref->{$k}); #...before
}
After, missing methods return undef:
my $hash_ref = somecall(..);
d2o -autoundef, $hash_ref;
my @mightexist = qw/foo bar baz/;
foreach my $k (@mightexist) {
say $hash_ref->$k if ($hash_ref->$k); #............after
}
h2o
's -array
Modifier
As of version 0.20 of Util::H2O, h2o
has now a -arrays
modifier that does something very similar to d2o
, which was released shortly after -arrays
was released in Util::H2O. Had the author of this module known about it, he would not have created d2o
. Nonetheless, d2o
does some things -arrays
doesn't do (and similarly, when o2h -arrays
versus o2d
).
The biggest difference seems to be that h2o
doesn't bless the ARRAY
containers or provide virtual methods. Be advised, however, -arrays
is probably sufficient for the use case d2o
was originally create for; i.e., to more easily objectify complicated data structures obtained from things like JSON returned from web APIs. the virtual methods added to ARRAY
s is not something one would expect form h2o
, which strives to provide a lite or tiny touch. The vmethods do make iterating over the ARRAY
s easier, though.
o2d REF
Does for data structures objectified with d2o
what o2h
does for objects created with h2o
. It only removes the blessing from Util::H2O::
and Util::H2O::More::__a2o
references.
a2o REF
Used internally.
Used internally to give virual methods to ARRAY
ref containers potentially holding HASH
references.
a2o
is not intended to be useful outside of the context of d2o
, but it's exposed in case it is, anyway.
ARRAY
container vmethods
It is still somewhat inconvenient, though idiomatic, to refer to ARRAY
elements directly as in the example above. However, it is still inconsistent with idea of Util::H2O
. So, d2o
leans into its heavy nature by adding some "virtual" methods to ARRAY
containers.
all
Returns a LIST of all items in the ARRAY
container.
my @items = $root->some-barray->all;
get INDEX
, i INDEX
Given an ARRAY
container from d2o
, returns the element at the given index. See push
example below for a practical example.
For shorter code, one may use i
instead of get
, for example, something really pathalogical can be written either:
$data->company->teams->get(0)->members->get(0)->projects->get(0)->tasks->get(1)->status('Completed');
Or,
$data->company->teams->i(0)->members->i(0)->projects->i(0)->tasks->i(1)->status('Completed');
push LIST
Pushes LIST onto ARRAY attached to the vmethod called; also applies the d2o
method to anything pushed.
my @added = $root->some->barray->push({ foo => 1 }, {foo => 2});
my $one = $root->some->barray->get(0)->foo; # returns 1 via "get"
my $two = $root->some->barray->get(1)->foo; # returns 2 via "get"
Items that are push
'd are returned for convenient assignment.
pop
Pops an element from ARRAY
container available after applying d2o
to a structure that has ARRAY
refs at any level.
my $item = $root->some-barray->pop;
unshift LIST
Similar to push
, just operates on the near end of the ARRAY
.
Items that are shift
'd are returned for convenient assignment.
shift
Similar to pop
, just operates on the near end of the ARRAY
.
scalar
Returns the number of items in the ARRAY
container, which is more convenient that doing,
my $count = scalar @{$root->some->barray->all};
Debugging Methods
ddd LIST
Takes a list and applies Data::Dumper's ::Dumper
method to them, in turn. Prints to STDERR
.
Used for debugging data structures, which is a likely thing to need if using this module.
Data::Dumper is require
'd by this method.
dddie LIST
Does the same thing as ddd
, but die
s at the end.
EXTERNAL METHODS
h2o
Because Util::H2O::More
exports h2o
as the basis for its operations, h2o
is also available without needing to qualify its full name space.
DEPENDENCIES
Util::H2O
Requires Util::H2O
because this module is effectively a wrapper around h2o
.
It also uses the state
keyword, which is only available in perls >= 5.10.
While some methods are designed to work with external modules, e.g., opt2h2o
is meant to work with Getopt::Long; at this time there are no dependencies for such methods required by Util::H2O::More
itself.
BUGS
At the time of this release, there are no bugs on the Github issue tracker.
LICENSE AND COPYRIGHT
Perl/perl
ACKNOWLEDGEMENTS
Thank you to HAUKEX for creating Util::H2O and hearing me out on its usefulness for some unintended use cases.
SEE ALSO
This module was featured in the 2023 Perl Advent Calendar on December 22, https://perladvent.org/2023/2023-12-22.html.
AUTHOR
Oodler 577 <oodler@cpan.org>