Hash::Wrap - create on-the-fly objects from hashes
version 0.18
use Hash::Wrap; my $result = wrap_hash( { a => 1 } ); print $result->a; # prints print $result->b; # throws # import two constructors, <cloned> and <copied> with different behaviors. use Hash::Wrap { -as => 'cloned', clone => 1}, { -as => 'copied', copy => 1 }; my $cloned = cloned( { a => 1 } ); print $cloned->a; my $copied = copied( { a => 1 } ); print $copied->a; # don't pollute up your namespace my $wrap; use Hash::Wrap { -as => \$wrap}; my $obj = $wrap->( { a => 1 } ); # apply constructors to hashes two levels deep into the hash use Hash::Wrap { -recurse => 2 }; # apply constructors to hashes at any level use Hash::Wrap { -recurse => -1 };
Hash::Wrap creates objects from hashes, providing accessors for hash elements. The objects are hashes, and may be modified using the standard Perl hash operations and the object's accessors will behave accordingly.
Why use this class? Sometimes a hash is created on the fly and it's too much of a hassle to build a class to encapsulate it.
sub foo () { ... ; return { a => 1 }; }
With Hash::Wrap:
Hash::Wrap
use Hash::Wrap; sub foo () { ... ; return wrap_hash( { a => 1 ); } my $obj = foo (); print $obj->a;
Elements can be added or removed to the object and accessors will track them. The object may be made immutable, or may have a restricted set of attributes.
There are many similar modules on CPAN (see "SEE ALSO" for comparisons).
What sets Hash::Wrap apart is that it's possible to customize object construction and accessor behavior:
It's possible to use the passed hash directly, or make shallow or deep copies of it.
Accessors can be customized so that accessing a non-existent element can throw an exception or return the undefined value.
On recent enough versions of Perl, accessors can be lvalues, e.g.
$obj->existing_key = $value;
use'ing Hash::Wrap without options imports a subroutine called wrap_hash which takes a hash, blesses it into a wrapper class and returns the hash:
use
wrap_hash
use Hash::Wrap; my $h = wrap_hash { a => 1 }; print $h->a, "\n"; # prints 1
The wrapper class has no constructor method, so the only way to create an object is via the wrap_hash subroutine. (See "WRAPPER CLASSES" for more about wrapper classes) If wrap_hash is called without arguments, it will create a hash for you.
So rename it:
use Hash::Wrap { -as => "a_much_better_name_for_wrap_hash" }; $obj = a_much_better_name_for_wrap_hash( { a => 1 } );
If the class name matters, but it'll never be instantiated except via the imported constructor subroutine:
use Hash::Wrap { -class => 'My::Class' }; my $h = wrap_hash { a => 1 }; print $h->a, "\n"; # prints 1 $h->isa( 'My::Class' ); # returns true
or, if you want it to reflect the current package, try this:
package Foo; use Hash::Wrap { -class => '-caller', -as => 'wrapit' }; my $h = wrapit { a => 1 }; $h->isa( 'Foo::wrapit' ); # returns true
Again, the wrapper class has no constructor method, so the only way to create an object is via the generated subroutine.
To generate a wrapper class which can be instantiated via its own constructor method:
use Hash::Wrap { -class => 'My::Class', -new => 1 };
The default wrap_hash constructor subroutine is still exported, so
$h = My::Class->new( { a => 1 } );
and
$h = wrap_hash( { a => 1 } );
do the same thing.
To give the constructor method a different name:
use Hash::Wrap { -class => 'My::Class', -new => '_my_new' };
To prevent the constructor subroutine from being imported:
use Hash::Wrap { -as => undef, -class => 'My::Class', -new => 1 };
To create a stand alone wrapper class,
package My::Class; use Hash::Wrap { -base => 1 }; 1;
And later...
use My::Class; $obj = My::Class->new( \%hash );
It's possible to modify the constructor and accessors:
package My::Class; use Hash::Wrap { -base => 1, -new => 'new_from_hash', -undef => 1 }; 1;
Hash::Wrap works at compile time. To modify its behavior pass it options when it is use'd:
use Hash::Wrap { %options1 }, { %options2 }, ... ;
Multiple options hashes may be passed; each hash specifies options for a separate constructor or class.
For example,
use Hash::Wrap { -as => 'cloned', clone => 1}, { -as => 'copied', copy => 1 };
creates two constructors, cloned and copied with different behaviors.
cloned
copied
-as
undef
-return
(This defaults to the string wrap_hash )
If the argument is
a string (but not the string -return)
Import the constructor subroutine with the given name.
undefined
Do not import the constructor. This is usually only used with the "-new" option.
a scalar ref
Do not import the constructor. Store a reference to the constructor into the scalar.
The string -return.
Do not import the constructor. The constructor subroutine(s) will be returned from Hash::Import's import method. This is a fairly esoteric way of doing things:
Hash::Import
import
require Hash::Wrap; ( $copy, $clone ) = Hash::Wrap->import( { -as => '-return', copy => 1 }, { -as => '-return', clone => 1 } );
A list is always returned, even if only one constructor is created.
-copy
If true, the object will store the data in a shallow copy of the hash. By default, the object uses the hash directly.
-clone
Store the data in a deep copy of the hash. if true, "dclone" in Storable is used. If a coderef, it will be called as
$clone = coderef->( $hash )
By default, the object uses the hash directly.
-immutable
The object's attributes and values are locked and may not be altered. Note that this locks the underlying hash.
-lockkeys
If the value is true, the object's attributes are restricted to the existing keys in the hash. If it is an array reference, it specifies which attributes are allowed, in addition to existing attributes. The attribute's values are not locked. Note that this locks the underlying hash.
-undef
Normally an attempt to use an accessor for an non-existent key will result in an exception. This option causes the accessor to return undef instead. It does not create an element in the hash for the key.
-lvalue
If non-zero, the accessors will be lvalue routines, e.g. they can change the underlying hash value by assigning to them:
$obj->attr = 3;
The hash entry must already exist or this will throw an exception.
lvalue subroutines are only available on Perl version 5.16 and later.
If -lvalue = 1 this option will silently be ignored on earlier versions of Perl.
-lvalue = 1
If -lvalue = -1 this option will cause an exception on earlier versions of Perl.
-lvalue = -1
-recurse
Normally only the top level hash is wrapped in a class. This option specifies how many levels deep into the hash hashes should be wrapped. For example, if
%h = ( l => 0, a => { l => 1, b => { l => 2, c => { l => 3 } } } }; use Hash::Wrap { -recurse => 0 }; $h->l # => 0 $h->a->l # => ERROR use Hash::Wrap { -recurse => 1 }; $h->l # => 0 $h->a->l # => 1 $h->a->b->l # => ERROR use Hash::Wrap { -recurse => 2 }; $h->l # => 0 $h->a->l # => 1 $h->a->b->l # => 2 $h->a->b->c->l # => ERROR
For infinite recursion, set -recurse to -1.
-1
-base
If true, the enclosing package is converted into a proxy wrapper class. This should not be used in conjunction with -class. See "A stand alone Wrapper Class".
-class
A class with the given name will be created and new objects will be blessed into the specified class by the constructor subroutine. The new class will not have a constructor method.
If class name is the string -caller, then the class name is set to the fully qualified name of the constructor, e.g.
-caller
package Foo; use Hash::Wrap { -class => '-caller', -as => 'wrap_it' };
results in a class name of Foo::wrap_it.
Foo::wrap_it
If not specified, the class name will be constructed based upon the options. Do not rely upon this name to determine if an object is wrapped by Hash::Wrap.
-new
Add a class constructor method.
If -new is a true boolean value, the method will be called new. Otherwise -new specifies the name of the method.
new
-defined
Add a method which returns true if the passed hash key is defined or does not exist. If -defined is a true boolean value, the method will be called defined. Otherwise it specifies the name of the method. For example,
defined
use Hash::Wrap { -defined => 1 }; $obj = wrap_hash( { a => 1, b => undef } ); $obj->defined( 'a' ); # TRUE $obj->defined( 'b' ); # FALSE $obj->defined( 'c' ); # FALSE
or
use Hash::Wrap { -defined => 'is_defined' }; $obj = wrap_hash( { a => 1 } ); $obj->is_defined( 'a' );
-exists
Add a method which returns true if the passed hash key exists. If -exists is a boolean, the method will be called exists. Otherwise it specifies the name of the method. For example,
exists
use Hash::Wrap { -exists => 1 }; $obj = wrap_hash( { a => 1 } ); $obj->exists( 'a' );
use Hash::Wrap { -exists => 'is_present' }; $obj = wrap_hash( { a => 1 } ); $obj->is_present( 'a' );
-item -predicate => boolean
-predicate
This adds the more traditionally named predicate methods, such as has_foo for attribute foo. Note that this option makes any elements which begin with has_ unavailable via the generated accessors.
has_foo
foo
has_
-methods
Install the passed code references into the class with the specified names. These override any attributes in the hash. For example,
use Hash::Wrap { -methods => { a => sub { 'b' } } }; $obj = wrap_hash( { a => 'a' } ); $obj->a; # returns 'b'
A wrapper class has the following characteristics.
It has the methods DESTROY, AUTOLOAD and can.
DESTROY
AUTOLOAD
can
It will have other methods if the -undef and -exists options are specified. It may have other methods if it is a stand alone class.
It will have a constructor if either of -base or -new is specified.
Wrapper classes have DESTROY, can method, and AUTOLOAD methods, which will mask hash keys with the same names.
Classes which are generated without the -base or -new options do not have a class constructor method, e.g Class->new() will not return a new object. The only way to instantiate them is via the constructor subroutine generated via Hash::Wrap. This allows the underlying hash to have a new attribute which would otherwise be masked by the constructor.
Class->new()
Lvalue accessors are available only on Perl 5.16 and later.
Accessors for deleted elements are not removed. The class's can method will return undef for them, but they are still available in the class's stash.
If a hash key contains characters that aren't legal in method names, there's no way to access that hash entry. One way around this is to use a custom clone subroutine which modifies the keys so they are legal method names. The user can directly insert a non-method-name key into the Hash::Wrap object after it is created, and those still have a key that's not available via a method, but there's no cure for that.
Here's a comparison of this module and others on CPAN.
core dependencies only
object tracks additions and deletions of entries in the hash
optionally applies object paradigm recursively
accessors may be lvalue subroutines
accessing a non-existing element via an accessor throws by default, but can optionally return undef
can use custom package
can copy/clone existing hash. clone may be customized
can add additional methods to the hash object's class
optionally stores the constructor in a scalar
optionally provides per-attribute predicate methods (e.g. has_foo)
optionally provides methods to check an attribute existence or whether its value is defined
As you might expect from a DCONWAY module, this does just about everything you'd like. It has a very heavy set of dependencies.
applies object paradigm recursively
accessing a non-existing element via an accessor creates it
moderate dependency chain (no XS?)
accessing a non-existing element throws
only applies object paradigm to top level hash
can add generic accessor, mutator, and element management methods
accessing a non-existing element via an accessor creates it (not documented, but code implies it)
can() doesn't work
can()
accessing a non-existing element via an accessor returns undef
moderate dependency chain. Requires XS, tied hashes
light dependency chain. Requires XS.
accessing a non-existing element throws, but if an existing element is accessed, then deleted, accessor returns undef rather than throwing
uses source filters
light dependency chain
no documentation
accessing a non-existing element via an accessor returns undef by default, but can optionally throw. Changing behavior is done globally, so all objects are affected.
accessors must be explicitly added.
accessors may have aliases
values may be validated
invoking an accessor may trigger a callback
minimal non-core dependencies (Exporter::Shiny
uses Class::XSAccessor if available
provides separate getter and predicate methods, but only for existing keys in hash.
hash keys are locked.
operates directly on hash.
has a cool name
locks hash by default
optionally recurses into the hash
does not track changes to hash
can destroy class
can add methods
Please report any bugs or feature requests to bug-hash-wrap@rt.cpan.org or through the web interface at: https://rt.cpan.org/Public/Dist/Display.html?Name=Hash-Wrap
Source is available at
https://gitlab.com/djerius/hash-wrap
and may be cloned from
https://gitlab.com/djerius/hash-wrap.git
Diab Jerius <djerius@cpan.org>
This software is Copyright (c) 2017 by Smithsonian Astrophysical Observatory.
This is free software, licensed under:
The GNU General Public License, Version 3, June 2007
To install Hash::Wrap, copy and paste the appropriate command in to your terminal.
cpanm
cpanm Hash::Wrap
CPAN shell
perl -MCPAN -e shell install Hash::Wrap
For more information on module installation, please visit the detailed CPAN module installation guide.