NAME

Hash::Wrap - create on-the-fly objects from hashes

VERSION

version 1.09

SYNOPSIS

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 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 };

DESCRIPTION

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:

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:

USAGE

Simple Usage

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 Hash::Wrap;

my $h = wrap_hash { a => 1 };
print $h->a, "\n";             # prints 1

[API change @ v1.0] The passed hash must be a plain hash (i.e. not an object or blessed hash). To pass an object, you must specify a custom clone subroutine returning a plain hashref via the "-clone" option.

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.

Advanced Usage

wrap_hash is an awful name for the constructor subroutine

So rename it:

use Hash::Wrap { -as => "a_much_better_name_for_wrap_hash" };

$obj = a_much_better_name_for_wrap_hash( { a => 1 } );

The Wrapper Class name matters

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 => '-target', -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.

The Wrapper Class needs its own class constructor method

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 };

A stand alone Wrapper Class

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;

Recursive wrapping

Hash::Wrap can automatically wrap nested hashes using the "-recurse" option.

Using the original hash

The "-recurse" option allows mapping nested hashes onto chained methods, e.g.

use Hash::Wrap { -recurse => -1, -as => 'recwrap' };

my %hash = ( a => { b => { c => 'd' } } );

my $wrap = recwrap(\%hash);

$wrap->a->b->c eq 'd'; # true

Along the way, %hash, $hash{a}, $hash{b}, $hash{c} are all blessed into wrapping classes.

Copying the original hash

If "-copy" is also specified, then the relationship between the nested hashes in the original hash and those hashes retrieved by wrapper methods depends upon what level in the structure has been wrapped. For example,

use Hash::Wrap { -recurse => -1, -copy => 1, -as => 'copyrecwrap' };
use Scalar::Util 'refaddr';

my %hash = ( a => { b => { c => 'd' } } );

my $wrap = copyrecwrap(\%hash);

refaddr( $wrap ) != refaddr( \%hash );

Because the $wrap->a method hasn't been called, then the $hash{a} structure has yet to be wrapped, so, using $wrap as a hash,

refaddr( $wrap->{a} ) == refaddr( $hash{a} );

However,

# invoking $wrap->a wraps a copy of $hash{a} because of the -copy
# attribute
refaddr( $wrap->a ) != refaddr( $hash{a} );

# so $wrap->{a} is no longer the same as $hash{a}:
refaddr( $wrap->{a} ) != refaddr( $hash{a} );
refaddr( $wrap->{a} ) == refaddr( $wrap->a );

Importing into an alternative package

Normally the constructor is installed into the package importing Hash::Wrap. The -into option can change that:

package This::Package;
use Hash::Wrap { -into => 'Other::Package' };

will install Other::Package::wrap_hash.

OPTIONS

Hash::Wrap works at import 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.

Constructor

Accessors

Class

Extra Class Methods

WRAPPER CLASSES

A wrapper class has the following characteristics.

Wrapper Class Limitations

LIMITATIONS

Lvalue accessors

Lvalue accessors are available only on Perl 5.16 and later.

Accessors for deleted hash elements

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.

Wrapping immutable structures

Locked (e.g. immutable) hashes cannot be blessed into a class. This will cause Hash::Wrap to fail if it is asked to work directly (without cloning or copying) on a locked hash or recursive wrapping is specified and the hash contains nested locked hashes.

To create an immutable Hash::Wrap object from an immutable hash, use the "-copy" and "-immutable" attributes. The "-copy" attribute performs a shallow copy of the hash which is then locked by "-immutable". The default "-clone" option will not work, as it will clone the immutability of the input hash.

Adding the "-recurse" option will properly create an immutable wrapped object when used on locked hashes. It does not suffer the issue described in "Eventual immutability in nested structures" in "Bugs".

Cloning with recursion

Cloning by default uses "dclone" in Storable, which performs a deep clone of the passed hash. In recursive mode, the clone operation is performed at every wrapping of a nested hash, causing some data to be repeatedly cloned. This does not create a memory leak, but it is inefficient. Consider using "-copy" instead of "-clone" with "-recurse".

BUGS

Eventual immutability in nested structures

Immutability is added to mutable nested structures as they are traversed via method calls. This means that the hash underlying the wrapper object is not fully immutable until all nested hashes have been visited via methods.

For example,

use Hash::Wrap { -immutable => 1, -recurse => -1, -as 'immutable' };

my $wrap = immutable( { a => { b => 2 } } );
$wrap->{a}    = 11; # expected fail: IMMUTABLE
$wrap->{a}{b} = 22; # unexpected success: NOT IMMUTABLE
$wrap->a;
$wrap->{a}{b} = 33; # expected fail: IMMUTABLE; $wrap->{a} is now locked

EXAMPLES

Existing keys are not compatible with method names

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.

SEE ALSO

Here's a comparison of this module and others on CPAN.

SUPPORT

Bugs

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

Source is available at

https://codeberg.org/djerius/p5-Hash-Wrap

and may be cloned from

https://codeberg.org/djerius/p5-Hash-Wrap.git

AUTHOR

Diab Jerius djerius@cpan.org

COPYRIGHT AND LICENSE

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