File Coverage

blib/lib/Class/Storage.pm
Criterion Covered Total %
statement 24 94 25.5
branch 0 38 0.0
condition 0 16 0.0
subroutine 8 17 47.0
pod 2 2 100.0
total 34 167 20.3


line stmt bran cond sub pod time code
1             package Class::Storage;
2              
3             # See perldoc at bottom of this file
4              
5 2     2   30929 use 5.006;
  2         7  
  2         90  
6 2     2   12 use strict;
  2         15  
  2         112  
7 2     2   10 use warnings FATAL => 'all';
  2         15  
  2         104  
8              
9 2     2   9 use Scalar::Util qw(blessed reftype);
  2         2  
  2         187  
10              
11 2     2   13 use base qw(Exporter);
  2         3  
  2         195  
12             our @EXPORT_OK = qw(packObjects unpackObjects);
13              
14 2     2   8 use constant DEFAULT_TO_PACKED_METHOD_NAME => "TO_PACKED";
  2         2  
  2         130  
15 2     2   7 use constant DEFAULT_FROM_PACKED_METHOD_NAME => "FROM_PACKED";
  2         2  
  2         77  
16              
17             # MooseX::Storage uses __CLASS__ and so it is perhaps a good idea not to choose
18             # *exactly* the same magic string - then it isn't magic any more! :-)
19 2     2   8 use constant DEFAULT_MAGIC_STRING => "__class__";
  2         2  
  2         1629  
20              
21             sub packObjects {
22 0     0 1   my ($data, %options) = @_;
23              
24 0           _setDefaultOptions(\%options);
25              
26 0           my $val = _packObjects($data, \%options);
27 0   0       return $val // $data;
28             }
29              
30             sub _packObjects {
31 0     0     my ($data, $options) = @_;
32              
33 0           my $toPackedMethodName = $options->{toPackedMethodName};
34              
35 0 0 0       if (blessed $data && $data->can($toPackedMethodName)) {
36 0           my $packed = $data->$toPackedMethodName();
37 0           bless $packed, ref($data);
38 0           $data = $packed;
39             }
40              
41 0 0         if (reftype $data) {
42 0 0         if (reftype $data eq 'HASH') {
    0          
43 0           return _packObjectsHash($data, $options);
44             } elsif (reftype $data eq 'ARRAY') {
45 0           return _packObjectsArray($data, $options);
46             }
47             }
48              
49 0           return undef;
50             }
51              
52             sub _packObjectsHash {
53 0     0     my ($hash, $options) = @_;
54             # use Dbug; dbugDump(['hash', $hash]);
55 0           foreach my $key (keys %$hash) {
56 0           my $val = $hash->{$key};
57 0           my $newVal = _packObjects($val, $options);
58 0 0         if ($newVal) {
59 0           $hash->{$key} = $newVal;
60             }
61             }
62 0 0         if (blessed $hash) {
63 0 0         $hash = {
64             ( $options->{magicString} ?
65             ( $options->{magicString} => ref($hash) ) : ()),
66             %$hash
67             };
68             }
69 0           return $hash;
70             }
71              
72             sub _packObjectsArray {
73 0     0     my ($array, $options) = @_;
74             # use Dbug; dbugDump(['array', $array]);
75 0           foreach my $index (0..$#$array) {
76 0           my $val = $array->[$index];
77 0           my $newVal = _packObjects($val, $options);
78 0 0         if ($newVal) {
79 0           $array->[$index] = $newVal;
80             }
81             }
82 0 0         if (blessed $array) {
83 0 0         $array = [
84             ( $options->{magicString} ?
85             ( $options->{magicString} => ref($array) ) : ()),
86             @$array
87             ];
88             }
89 0           return $array;
90             }
91              
92             sub unpackObjects {
93 0     0 1   my ($data, %options) = @_;
94 0           _setDefaultOptions(\%options);
95 0           my $val = _unpackObjects($data, \%options);
96 0   0       return $val // $data;
97             }
98              
99             sub _unpackObjects {
100 0     0     my ($data, $options) = @_;
101 0 0         if (reftype $data eq 'HASH') {
    0          
102 0           return _unpackObjectsHash($data, $options);
103             } elsif (reftype $data eq 'ARRAY') {
104 0           return _unpackObjectsArray($data, $options);
105             }
106 0           return undef;
107             }
108              
109             sub _unpackObjectsHash {
110 0     0     my ($hash, $options) = @_;
111 0           my $class = delete $hash->{$options->{magicString}};
112 0 0         if ($class) {
113 0           my $fromPackedMethodName = $options->{fromPackedMethodName};
114 0 0         if ($class->can($fromPackedMethodName)) {
115 0           return $class->$fromPackedMethodName($hash);
116             }
117 0           bless $hash, $class;
118             }
119 0           foreach my $key (keys %$hash) {
120 0           my $newVal = _unpackObjects($hash->{$key}, $options);
121 0 0         $hash->{$key} = $newVal
122             if defined $newVal;
123             }
124 0           return undef;
125             }
126              
127             sub _unpackObjectsArray {
128 0     0     my ($array, $options) = @_;
129 0 0 0       if (scalar @$array >= 2 && $array->[0] eq $options->{magicString}) {
130 0           shift @$array;
131 0           my $class = shift @$array;
132 0           my $fromPackedMethodName = $options->{fromPackedMethodName};
133 0 0         if ($class->can($fromPackedMethodName)) {
134 0           return $class->$fromPackedMethodName($array);
135             }
136 0           bless $array, $class;
137             }
138 0           foreach my $i (0..$#$array) {
139 0           my $newVal = _unpackObjects($array->[$i], $options);
140 0 0         $array->[$i] = $newVal
141             if defined $newVal;
142             }
143 0           return undef;
144             }
145              
146             sub _setDefaultOptions {
147 0     0     my ($options) = @_;
148 0   0       $options->{toPackedMethodName} //= DEFAULT_TO_PACKED_METHOD_NAME;
149 0   0       $options->{fromPackedMethodName} //= DEFAULT_FROM_PACKED_METHOD_NAME;
150 0 0         if (! exists $options->{magicString}) {
151 0           $options->{magicString} = DEFAULT_MAGIC_STRING;
152             }
153             }
154              
155             =head1 NAME
156              
157             Class::Storage - pack objects by removing blessing so they can be unpacked back
158             into objects again later.
159              
160             Handles blessed HASHes and ARRAYs
161              
162             =head1 VERSION
163              
164             Version 0.01
165              
166             =cut
167              
168             our $VERSION = '0.01';
169              
170             =head1 SYNOPSIS
171              
172             This module came into existence out of the need to be able to send I
173             over JSON. JSON does not allow any blessed references to be sent by default and
174             if sent, provides no generic way to resurrect these objects again after
175             decoding. This can now all be done like this:
176              
177             use JSON;
178             use Class::Storage qw(packObjects unpackObjects);
179              
180             my $object = bless { a => 1 }, 'MyModule';
181             my $packed = packObjects( $object );
182              
183             # $packed is now { __class__ => 'MyModule', a => 1 }
184              
185             print $writeHandle encode_json($packed), "\n";
186              
187             # And on the other "side":
188              
189             my $jsonString = <$readHandle>;
190             my $packed = decode_json($jsonString);
191             my $unpackedObject = unpackObjects($packed);
192              
193             # $unpacked is now bless { a => 1 }, 'MyModule'
194             # Which is_deeply the same as $object that we started with
195              
196             However, there is no JSON-specific functionality in this module whatsoever,
197             only a way to cleanly "unbless" - remove the bless-ing - in a way that reliably
198             can be re-introduced later.
199              
200             =head1 DESCRIPTION
201              
202             =head2 Using a magic string
203              
204             As you can see from the L, we use a magic string ("__class__" by default) to store the class information for HASHes and ARRAYs.
205              
206             So C turns:
207              
208             bless { key => "value" }, "ModuleA";
209             bless [ "val1", "val2" ], "ModuleB";
210              
211             into:
212              
213             { __class__ => 'ModuleA', key => "value" }
214             [ "__class__", 'ModuleB', "val1", "val2" ]
215              
216             C converts any hashes with the magic string as a key and any
217             arrays with the magic string as the first element back to blessed references
218              
219             This "magic string" can be given as an option (see L), but if you
220             cannot live with a magic string, you can also provide
221             C<< magicString => undef >>. But then you won't be able to re-bless that data.
222             If this is your itch, you may actually want L instead.
223              
224             =head2 Returns packed/unpacked data + modifies input argument
225              
226             The valid data is returned. However, for speed, we also modify and re-use data
227             from the input value. So don't rely on being able to reuse the C<$data> input
228             for C and C after they've been called and don't modify them
229             either.
230              
231             If you don't want your input modified:
232              
233             use Storable qw(dclone);
234             my $pristineData = somesub();
235             my $unblessed = ubless(dclone($pristineData));
236              
237             =head2 Inspiration
238              
239             Class::Storage is inspired by L but this is a generic
240             implementation that works on all plain perl classes that are implemented as
241             blessed references to HASHes and ARRAYs (B hashes and arrays).
242              
243             NOTE: L uses C<__CLASS__> as its magic string and we use
244             C<__class__> to make sure they're not the same.
245              
246             =head2 C and C
247              
248             If you want to control how internal state gets represeted when packed, then
249             provide a C instance method. It will be called like:
250              
251             my $packed = $object->TO_PACKED();
252              
253             This packed data will be used by C instead of the guts of
254             C<$object>.
255              
256             Similarly, when during C, if a module has a C
257             static method it will be called like this:
258              
259             my $object = $module->FROM_PACKED($packed);
260              
261             As you can see, C and C go together as pairs.
262              
263             You can also modify the names of these methods with the C
264             and C options. See L.
265              
266             =head1 NOTE ABOUT KINDS OF BLESSED OBJECTS
267              
268             L says:
269              
270             "... it's possible to bless any type of data structure or referent, including
271             scalars, globs, and subroutines. You may see this sort of thing when looking at
272             code in the wild."
273              
274             In particular I've seen several XS modules create instances where the internal
275             state is not visible to Perl, and hence cannot be handled properly by this
276             module. Here is an example with JSON:
277              
278             use Data::Dumper;
279             use JSON;
280             print Dumper(JSON->new()->pretty(1));
281             # prints
282             # $VAR1 = bless( do{\(my $o = '')}, 'JSON' );
283              
284             Clearly a L object has internal state and other data. This is an example
285             of a blessed reference, but not a blessed HASH or ARRAY that Class::Storage can
286             handle. If you try C-ing such a JSON instance, Class::Storage will
287             just leave the JSON object altogether untouched.
288              
289             =head1 EXPORT
290              
291             our @EXPORT_OK = qw(packObjects unpackObjects);
292              
293             =head1 SUBROUTINES/METHODS
294              
295             Both C and C share the same C<%options>. See L
296             below.
297              
298             =head2 packObjects
299              
300             my $packed = packObjects($blessed, %options);
301              
302             =head2 unpackObjects
303              
304             my $unpacked = unpackObjects($unbessed, %options);
305              
306             =head1 OPTIONS
307              
308             These options are common to C and C:
309              
310             =over 4
311              
312             =item * C
313              
314             This option lets you change the name of the C method to something
315             else. Hint: C could be a good idea here!
316              
317             =item * C
318              
319             This option lets you change the name of the C method to something
320             else. Hint: C could be a good idea here, even though L
321             doesn't have such a method.
322              
323             Which is actually the entire Raison d'Etre of this module!
324              
325             =item * C
326              
327             Change the magic string used to store the class name to something else than
328             C<__class__>.
329              
330             If this is false, don't store class information at all, in which case
331             C becomes analogous to L.
332              
333             =back
334              
335             =encoding UTF-8
336              
337             =head1 AUTHOR
338              
339             Peter Valdemar Mørch, C<< >>
340              
341             =head1 BUGS
342              
343             Please report any bugs or feature requests to C, or through
344             the web interface at L. I will be notified, and then you'll
345             automatically be notified of progress on your bug as I make changes.
346              
347             =head1 SUPPORT
348              
349             You can find documentation for this module with the perldoc command.
350              
351             perldoc Class::Storage
352              
353             You can also look for information at:
354              
355             =over 4
356              
357             =item * Repository and Bug Tracker on Github
358              
359             L
360              
361             =item * AnnoCPAN: Annotated CPAN documentation
362              
363             L
364              
365             =item * CPAN Ratings
366              
367             L
368              
369             =item * Search CPAN
370              
371             L
372              
373             =back
374              
375             =head1 ACKNOWLEDGEMENTS
376              
377             This has been inspired by many sources, but checkout:
378              
379             =over 4
380              
381             =item * How to convert Perl objects into JSON and vice versa - Stack Overflow
382              
383             L
384              
385             =item * How do I turn Moose objects into JSON for use in Catalyst?
386              
387             L
388              
389             =item * MooseX-Storage
390              
391             L
392              
393             =item * Brian D Foy's quick hack
394              
395             Where he defines a TO_JSON in UNIVERSAL so it applies to all objects. It makes
396             a deep copy, unblesses it, and returns the data structure.
397              
398             L
399              
400             =back
401              
402             =head1 LICENSE AND COPYRIGHT
403              
404             Copyright 2015 Peter Valdemar Mørch.
405              
406             This program is free software; you can redistribute it and/or modify it
407             under the terms of the the Artistic License (2.0). You may obtain a
408             copy of the full license at:
409              
410             L
411              
412             Any use, modification, and distribution of the Standard or Modified
413             Versions is governed by this Artistic License. By using, modifying or
414             distributing the Package, you accept this license. Do not use, modify,
415             or distribute the Package, if you do not accept this license.
416              
417             If your Modified Version has been derived from a Modified Version made
418             by someone other than you, you are nevertheless required to ensure that
419             your Modified Version complies with the requirements of this license.
420              
421             This license does not grant you the right to use any trademark, service
422             mark, tradename, or logo of the Copyright Holder.
423              
424             This license includes the non-exclusive, worldwide, free-of-charge
425             patent license to make, have made, use, offer to sell, sell, import and
426             otherwise transfer the Package with respect to any patent claims
427             licensable by the Copyright Holder that are necessarily infringed by the
428             Package. If you institute patent litigation (including a cross-claim or
429             counterclaim) against any party alleging that the Package constitutes
430             direct or contributory patent infringement, then this Artistic License
431             to you shall terminate on the date that such litigation is filed.
432              
433             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
434             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
435             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
436             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
437             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
438             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
439             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
440             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
441              
442             =cut
443              
444             1; # End of Class::Storage