File Coverage

blib/lib/Hash/AsObject.pm
Criterion Covered Total %
statement 56 58 96.5
branch 37 42 88.1
condition 4 9 44.4
subroutine 10 10 100.0
pod 2 2 100.0
total 109 121 90.0


line stmt bran cond sub pod time code
1             package Hash::AsObject;
2              
3 7     7   5850 use strict;
  7         15  
  7         301  
4 7     7   37 use vars qw($VERSION $AUTOLOAD);
  7         14  
  7         1186  
5              
6             $VERSION = '0.13';
7              
8             sub VERSION {
9 7 100   7 1 2203 return $VERSION
10             unless ref($_[0]);
11 5 100       28 scalar @_ > 1 ? $_[0]->{'VERSION'} = $_[1] : $_[0]->{'VERSION'};
12             }
13              
14             sub can {
15             # $obj->can($method)
16             # $cls->can($method)
17 9 100   9 1 919 die "Usage: UNIVERSAL::can(object-ref, method)"
18             unless @_ == 2;
19 8         12 my ($invocant, $method) = @_;
20             # --- Define a stub method in this package (to speed up later invocations)
21 8   66     27 my $cls = ref($invocant) || $invocant;
22 7     7   33 no strict 'refs';
  7         12  
  7         2952  
23             return sub {
24 5     5   1004 my $v;
25 5 100       12 if (scalar @_ > 1) {
26 3         5 $v = $_[0]->{$method} = $_[1];
27 3 50       8 return undef unless defined $v;
28             }
29             else {
30 2         10 $v = $_[0]->{$method};
31             }
32 5 50       13 if (ref($v) eq 'HASH') {
33 0         0 bless $v, $cls;
34             }
35             else {
36 5         18 $v;
37             }
38              
39 8         43 };
40             }
41              
42             sub import {
43             return
44 14 100   14   1198 unless ref($_[0]);
45 5 100       32 scalar @_ > 1 ? $_[0]->{'import'} = $_[1] : $_[0]->{'import'};
46             }
47              
48             sub AUTOLOAD {
49 35     35   19832 my $invocant = shift;
50 35         52 my $key = $AUTOLOAD;
51              
52             # --- Figure out which hash element we're dealing with
53 35 100       77 if (defined $key) {
54 29         299 $key =~ s/.*:://;
55             }
56             else {
57             # --- Someone called $obj->AUTOLOAD -- OK, that's fine, be cool
58             # --- Or they might have called $cls->AUTOLOAD, but we'll catch
59             # that below
60 6         8 $key = 'AUTOLOAD';
61             }
62            
63             # --- We don't need $AUTOLOAD any more, and we need to make sure
64             # it isn't defined in case the next call is $obj->AUTOLOAD
65             # (why the %*@!? doesn't Perl undef this automatically for us
66             # when execution of this sub ends?)
67 35         59 undef $AUTOLOAD;
68            
69             # --- Handle special cases: class method invocations, DESTROY, etc.
70 35 100       100 if (ref($invocant) eq '') {
    100          
71             # --- Class method invocation
72 19 50       58 if ($key eq 'import') {
    100          
73             # --- Ignore $cls->import
74 0         0 return;
75             } elsif ($key eq 'new') {
76             # --- Constructor
77 16 100       52 my $elems =
78             scalar(@_) == 1
79             ? shift # $cls->new({ foo => $bar, ... })
80             : { @_ } # $cls->new( foo => $bar, ... )
81             ;
82 16         76 return bless $elems, $invocant;
83             }
84             else {
85             # --- All other class methods disallowed
86 3         36 die "Can't invoke class method '$key' on a Hash::AsObject object";
87             }
88             } elsif ($key eq 'DESTROY') {
89             # --- This is tricky. There are four distinct cases:
90             # (1) $invocant->DESTROY($val)
91             # (2) $invocant->DESTROY()
92             # (2a) $invocant->{DESTROY} exists and is defined
93             # (2b) $invocant->{DESTROY} exists but is undefined
94             # (2c) $invocant->{DESTROY} doesn't exist
95             # Case 1 will never happen automatically, so we handle it normally
96             # In case 2a, we must return the value of $invocant->{DESTROY} but not
97             # define a method Hash::AsObject::DESTROY
98             # The same is true in case 2b, it's just that the value is undefined
99             # Since we're striving for perfect emulation of hash access, case 2c
100             # must act just like case 2b.
101 2 50 33     14 return $invocant->{'DESTROY'} # Case 2c -- autovivify
102             unless
103             scalar @_ # Case 1
104             or exists $invocant->{'DESTROY'}; # Case 2a or 2b
105             }
106            
107             # --- Handle the most common case (by far)...
108            
109             # --- All calls like $obj->foo(1, 2) must fail spectacularly
110 16 50       45 die "Too many arguments"
111             if scalar(@_) > 1; # We've already shift()ed $invocant off of @_
112            
113             # --- If someone's called $obj->AUTOLOAD
114 16 100       68 if ($key eq 'AUTOLOAD') {
115             # --- Tread carefully -- we can't (re)define &Hash::AsObject::AUTOLOAD
116             # because that would ruin everything
117 5 100       33 return scalar(@_) ? $invocant->{'AUTOLOAD'} = shift : $invocant->{'AUTOLOAD'};
118             }
119             else {
120 11   33     33 my $cls = ref($invocant) || $invocant;
121 7     7   47 no strict 'refs';
  7         13  
  7         1598  
122 11         65 *{ "${cls}::$key" } = sub {
123 36     36   1386 my $v;
124 36 100       83 if (scalar @_ > 1) {
125 12         56 $v = $_[0]->{$key} = $_[1];
126 12 100       50 return undef unless defined $v;
127             }
128             else {
129 24         52 $v = $_[0]->{$key};
130             }
131 33 100       69 if (ref($v) eq 'HASH') {
132 3         13 bless $v, $cls;
133             }
134             else {
135 30         352 $v;
136             }
137              
138 11         47 };
139 11         26 unshift @_, $invocant;
140 11         17 goto &{ "${cls}::$key" };
  11         51  
141             }
142             }
143              
144              
145             1;
146              
147              
148             =head1 NAME
149              
150             Hash::AsObject - treat hashes as objects, with arbitrary accessors/mutators
151              
152             =head1 SYNOPSIS
153              
154             $h = Hash::AsObject->new;
155             $h->foo(123);
156             print $h->foo; # prints 123
157             print $h->{'foo'}; # prints 123
158             $h->{'bar'}{'baz'} = 456;
159             print $h->bar->baz; # prints 456
160              
161             =head1 DESCRIPTION
162              
163             A Hash::AsObject is a blessed hash that provides read-write
164             access to its elements using accessors. (Actually, they're both accessors
165             and mutators.)
166              
167             It's designed to act as much like a plain hash as possible; this means, for
168             example, that you can use methods like C to get or set hash elements
169             with that name. See below for more information.
170              
171             =head1 METHODS
172              
173             The whole point of this module is to provide arbitrary methods. For the most
174             part, these are defined at runtime by a specially written C function.
175              
176             In order to behave properly in all cases, however, a number of special methods
177             and functions must be supported. Some of these are defined while others are
178             simply emulated in AUTOLOAD.
179              
180             =over 4
181              
182             =item B
183              
184             $h = Hash::AsObject->new;
185             $h = Hash::AsObject->new(\%some_hash);
186             $h = Hash::AsObject->new(%some_other_hash);
187              
188             Create a new L.
189              
190             If called as an instance method, this accesses a hash element 'new':
191              
192             $h->{'new'} = 123;
193             $h->new; # 123
194             $h->new(456); # 456
195              
196             =item B
197              
198             This method cannot be used to access a hash element 'isa', because
199             Hash::AsObject doesn't attempt to handle it specially.
200              
201             =item B
202              
203             Similarly, this can't be used to access a hash element 'can'.
204              
205             =item B
206              
207             $h->{'AUTOLOAD'} = 'abc';
208             $h->AUTOLOAD; # 'abc'
209             $h->AUTOLOAD('xyz') # 'xyz'
210              
211             Hash::AsObject::AUTOLOAD recognizes when AUTOLOAD is begin called as an
212             instance method, and treats this as an attempt to get or set the 'AUTOLOAD'
213             hash element.
214              
215             =item B
216              
217             $h->{'DESTROY'} = [];
218             $h->DESTROY; # []
219             $h->DESTROY({}) # {}
220              
221             C is called automatically by the Perl runtime when an object goes out
222             of scope. A Hash::AsObject can't distinguish this from a call to access the
223             element $h->{'DESTROY'}, and so it blithely gets (or sets) the hash's 'DESTROY'
224             element; this isn't a problem, since the Perl interpreter discards any value
225             that DESTROY returns when called automatically.
226              
227             =item B
228              
229             When called as a class method, this returns C<$Hash::AsObject::VERSION>; when
230             called as an instance method, it gets or sets the hash element 'VERSION';
231              
232             =item B
233              
234             Since L doesn't export any symbols, this method
235             has no special significance and you can safely call it as a method to get or
236             set an 'import' element.
237              
238             When called as a class method, nothing happens.
239              
240             =back
241              
242             The methods C and C are special, because they're defined in the
243             C class that all packages automatically inherit from. Unfortunately,
244             this means that you can't use L to access elements
245             'can' and 'isa'.
246              
247             =head1 CAVEATS
248              
249             No distinction is made between non-existent elements and those that are
250             present but undefined. Furthermore, there's no way to delete an
251             element without resorting to C<< delete $h->{'foo'} >>.
252              
253             Storing a hash directly into an element of a Hash::AsObject
254             instance has the effect of blessing that hash into
255             Hash::AsObject.
256              
257             For example, the following code:
258              
259             my $h = Hash::AsObject->new;
260             my $foo = { 'bar' => 1, 'baz' => 2 };
261             print ref($foo), "\n";
262             $h->foo($foo);
263             print ref($foo), "\n";
264              
265             Produces the following output:
266              
267             HASH
268             Hash::AsObject
269              
270             I could fix this, but then code like the following would throw an exception,
271             because C<< $h->foo($foo) >> will return a plain hash reference, not
272             an object:
273              
274             $h->foo($foo)->bar;
275              
276             Well, I can make C<< $h->foo($foo)->bar >> work, but then code like
277             this won't have the desired effect:
278              
279             my $foo = { 'bar' => 123 };
280             $h->foo($foo);
281             $h->foo->bar(456);
282             print $foo->{'bar'}; # prints 123
283             print $h->foo->bar; # prints 456
284              
285             I suppose I could fix I, but that's an awful lot of work for little
286             apparent benefit.
287              
288             Let me know if you have any thoughts on this.
289              
290             =head1 BUGS
291              
292             Autovivification is probably not emulated correctly.
293              
294             The blessing of hashes stored in a Hash::AsObject might be
295             considered a bug. Or a feature; it depends on your point of view.
296              
297             =head1 TO DO
298              
299             =over 4
300              
301             =item *
302              
303             Add the capability to delete elements, perhaps like this:
304              
305             use Hash::AsObject 'deleter' => 'kill';
306             $h = Hash::AsObject->new({'one' => 1, 'two' => 2});
307             kill $h, 'one';
308              
309             That might seem to violate the prohibition against exporting functions
310             from object-oriented packages, but then technically it wouldn't be
311             exporting it B anywhere since the function would be constructed
312             by hand. Alternatively, it could work like this:
313              
314             use Hash::AsObject 'deleter' => 'kill';
315             $h = Hash::AsObject->new({'one' => 1, 'two' => 2});
316             $h->kill('one');
317              
318             But, again, what if the hash contained an element named 'kill'?
319              
320             =item *
321              
322             Define multiple classes in C? For example, there
323             could be one package for read-only access to a hash, one for hashes
324             that throw exceptions when accessors for non-existent keys are called,
325             etc. But this is hard to do fully without (a) altering the underlying
326             hash, or (b) defining methods besides AUTOLOAD. Hmmm...
327              
328             =back
329              
330             =head1 VERSION
331              
332             0.06
333              
334             =head1 AUTHOR
335              
336             Paul Hoffman
337              
338             =head1 CREDITS
339              
340             Andy Wardley for L, which was my
341             inspiration. Writing template code like this:
342              
343             [% foo.bar.baz(qux) %]
344              
345             Made me yearn to write Perl code like this:
346              
347             foo->bar->baz($qux);
348              
349             =head1 COPYRIGHT
350              
351             Copyright 2003-2007 Paul M. Hoffman. All rights reserved.
352              
353             This program is free software; you can redistribute it
354             and modify it under the same terms as Perl itself.
355