File Coverage

blib/lib/PHP/Serialization.pm
Criterion Covered Total %
statement 188 225 83.5
branch 69 106 65.0
condition 21 36 58.3
subroutine 20 21 95.2
pod 4 5 80.0
total 302 393 76.8


line stmt bran cond sub pod time code
1             package PHP::Serialization;
2 10     10   236487 use strict;
  10         20  
  10         353  
3 10     10   49 use warnings;
  10         17  
  10         249  
4 10     10   64 use Exporter ();
  10         30  
  10         210  
5 10     10   50 use Scalar::Util qw/blessed/;
  10         16  
  10         1617  
6 10     10   55 use Carp qw(croak confess carp);
  10         25  
  10         652  
7 10     10   12329 use bytes;
  10         94  
  10         55  
8              
9 10     10   300 use vars qw/$VERSION @ISA @EXPORT_OK/;
  10         16  
  10         35033  
10              
11             $VERSION = '0.34';
12              
13             @ISA = qw(Exporter);
14             @EXPORT_OK = qw(unserialize serialize);
15              
16             =head1 NAME
17              
18             PHP::Serialization - simple flexible means of converting the output of PHP's serialize() into the equivalent Perl memory structure, and vice versa.
19              
20             =head1 SYNOPSIS
21              
22             use PHP::Serialization qw(serialize unserialize);
23             my $encoded = serialize({ a => 1, b => 2});
24             my $hashref = unserialize($encoded);
25              
26             =cut
27              
28              
29             =head1 DESCRIPTION
30              
31             Provides a simple, quick means of serializing perl memory structures (including object data!) into a format that PHP can deserialize() and access, and vice versa.
32              
33             NOTE: Converts PHP arrays into Perl Arrays when the PHP array used exclusively numeric indexes, and into Perl Hashes then the PHP array did not.
34              
35             =cut
36              
37             sub new {
38 14     14 0 38 my ($class) = shift;
39 14 50       104 my $self = bless {}, blessed($class) ? blessed($class) : $class;
40 14         81 return $self;
41             }
42              
43             =head1 FUNCTIONS
44              
45             Exportable functions..
46              
47             =cut
48              
49             =head2 serialize($var,[optional $asString,[optional $sortHashes]])
50              
51             Serializes the memory structure pointed to by $var, and returns a scalar value of encoded data.
52              
53             If the optional $asString is true, $var will be encoded as string if it is double or float.
54              
55             If the optional $sortHashes is true, all hashes will be sorted before serialization.
56              
57             NOTE: Will recursively encode objects, hashes, arrays, etc.
58              
59             SEE ALSO: ->encode()
60              
61             =cut
62              
63             sub serialize {
64 6     6 1 8120 return __PACKAGE__->new->encode(@_);
65             }
66              
67             =head2 unserialize($encoded,[optional CLASS])
68              
69             Deserializes the encoded data in $encoded, and returns a value (be it a hashref, arrayref, scalar, etc)
70             representing the data structure serialized in $encoded_string.
71              
72             If the optional CLASS is specified, any objects are blessed into CLASS::$serialized_class. Otherwise, O
73             bjects are blessed into PHP::Serialization::Object::$serialized_class. (which has no methods)
74              
75             SEE ALSO: ->decode()
76              
77             =cut
78              
79             sub unserialize {
80 8     8 1 2933 return __PACKAGE__->new->decode(@_);
81             }
82              
83             =head1 METHODS
84              
85             Functionality available if using the object interface..
86              
87             =cut
88              
89             =head2 decode($encoded_string,[optional CLASS])
90              
91             Deserializes the encoded data in $encoded, and returns a value (be it a hashref, arrayref, scalar, etc)
92             representing the data structure serialized in $encoded_string.
93              
94             If the optional CLASS is specified, any objects are blessed into CLASS::$serialized_class. Otherwise,
95             Objects are blessed into PHP::Serialization::Object::$serialized_class. (which has no methods)
96              
97             SEE ALSO: unserialize()
98              
99             =cut
100              
101             my $sorthash;
102              
103             sub decode {
104 8     8 1 21 my ($self, $string, $class, $shash) = @_;
105 8 50       30 $sorthash=$shash if defined($shash);
106              
107 8         17 my $cursor = 0;
108 8         60 $self->{string} = \$string;
109 8         24 $self->{cursor} = \$cursor;
110 8         25 $self->{strlen} = length($string);
111              
112 8 50       28 if ( defined $class ) {
113 0         0 $self->{class} = $class;
114             }
115             else {
116 8         24 $self->{class} = 'PHP::Serialization::Object';
117             }
118              
119             # Ok, start parsing...
120 8         40 my @values = $self->_parse();
121              
122             # Ok, we SHOULD only have one value..
123 5 50       24 if ( $#values == -1 ) {
    50          
124             # Oops, none...
125 0         0 return;
126             }
127             elsif ( $#values == 0 ) {
128             # Ok, return our one value..
129 5         28 return $values[0];
130             }
131             else {
132             # Ok, return a reference to the list.
133 0         0 return \@values;
134             }
135              
136             } # End of decode sub.
137              
138             my %type_table = (
139             O => 'object',
140             s => 'scalar',
141             a => 'array',
142             i => 'integer',
143             d => 'float',
144             b => 'boolean',
145             N => 'undef',
146             );
147              
148             sub _parse_array {
149 17     17   25 my $self = shift;
150 17         26 my $elemcount = shift;
151 17         29 my $cursor = $self->{cursor};
152 17         24 my $string = $self->{string};
153 17         25 my $strlen = $self->{strlen};
154 17 50       43 confess("No cursor") unless $cursor;
155 17 50       33 confess("No string") unless $string;
156 17 50       40 confess("No strlen") unless $strlen;
157              
158 17         24 my @elems = ();
159 17 50 33     58 my @shash_arr = ('some') if (($sorthash) and (ref($sorthash) eq 'HASH'));
160              
161 17         42 $self->_skipchar('{');
162 17         55 foreach my $i (1..$elemcount*2) {
163 81         1019 push(@elems,$self->_parse_elem);
164 77 50 66     317 if (($i % 2) and (@shash_arr)) {
165 0 0       0 $shash_arr[0]= ((($i-1)/2) eq $elems[$#elems])? 'array' : 'hash' unless ($shash_arr[0] eq 'hash');
    0          
166 0         0 push(@shash_arr,$elems[$#elems]);
167             }
168             }
169 13         35 $self->_skipchar('}');
170 13 50       32 push(@elems,\@shash_arr) if (@shash_arr);
171 13         54 return @elems;
172             }
173              
174             sub _parse_elem {
175 89     89   103 my $self = shift;
176 89         120 my $cursor = $self->{cursor};
177 89         104 my $string = $self->{string};
178 89         108 my $strlen = $self->{strlen};
179              
180 89         83 my @elems;
181              
182 89         159 my $type_c = $self->_readchar();
183 89         145 my $type = $type_table{$type_c};
184 89 100       176 if (!defined $type) {
185 1         256 croak("ERROR: Unknown type $type_c.");
186             }
187              
188 88 100 100     361 if ( $type eq 'object' ) {
    100          
    100          
    100          
    100          
    50          
189 1         3 $self->_skipchar(':');
190             # Ok, get our name count...
191 1         3 my $namelen = $self->_readnum();
192 1         3 $self->_skipchar(':');
193              
194             # Ok, get our object name...
195 1         2 $self->_skipchar('"');
196 1         3 my $name = $self->_readstr($namelen);
197 1         8 $self->_skipchar('"');
198              
199             # Ok, our sub elements...
200 1         5 $self->_skipchar(':');
201 1         4 my $elemcount = $self->_readnum();
202 1         6 $self->_skipchar(':');
203              
204 1         7 my %value = $self->_parse_array($elemcount);
205              
206             # TODO: Call wakeup
207             # TODO: Support for objecttypes
208 0         0 return bless(\%value, $self->{class} . '::' . $name);
209             } elsif ( $type eq 'array' ) {
210 16         38 $self->_skipchar(':');
211             # Ok, our sub elements...
212 16         40 my $elemcount = $self->_readnum();
213 16         37 $self->_skipchar(':');
214              
215 16         66 my @values = $self->_parse_array($elemcount);
216             # If every other key is not numeric, map to a hash..
217 13         22 my $subtype = 'array';
218 13         15 my @newlist;
219 13 50       34 my @shash_arr=@{pop(@values)} if (ref($sorthash) eq 'HASH');
  0         0  
220 13         28 foreach ( 0..$#values ) {
221 31 100       156 if ( ($_ % 2) ) {
    100          
222 11         17 push(@newlist, $values[$_]);
223 11         17 next;
224             } elsif (($_ / 2) ne $values[$_]) {
225 9         12 $subtype = 'hash';
226 9         12 last;
227             }
228 11 50       47 if ( $values[$_] !~ /^\d+$/ ) {
229 0         0 $subtype = 'hash';
230 0         0 last;
231             }
232             }
233 13 100       26 if ( $subtype eq 'array' ) {
234             # Ok, remap...
235 4         23 return \@newlist;
236             } else {
237             # Ok, force into hash..
238 9         33 my %hash = @values;
239 9 0 33     32 ${$sorthash}{\%hash}=@shash_arr if ((ref($sorthash) eq 'HASH') and @shash_arr and (shift(@shash_arr) ne 'array'));
  0   33     0  
240 9         32 return \%hash;
241             }
242             }
243             elsif ( $type eq 'scalar' ) {
244 39         72 $self->_skipchar(':');
245             # Ok, get our string size count...
246 39         73 my $strlen = $self->_readnum;
247 39         94 $self->_skipchar(':');
248              
249 38         69 $self->_skipchar('"');
250 38         69 my $string = $self->_readstr($strlen);
251 37         79 $self->_skipchar('"');
252 37         67 $self->_skipchar(';');
253 37         102 return $string;
254             }
255             elsif ( $type eq 'integer' || $type eq 'float' ) {
256 28         541 $self->_skipchar(':');
257             # Ok, read the value..
258 28         51 my $val = $self->_readnum;
259 28 100       64 if ( $type eq 'integer' ) { $val = int($val); }
  27         38  
260 28         56 $self->_skipchar(';');
261 28         73 return $val;
262             }
263             elsif ( $type eq 'boolean' ) {
264 2         8 $self->_skipchar(':');
265             # Ok, read our boolen value..
266 2         4 my $bool = $self->_readchar;
267              
268 2         6 $self->_skipchar;
269 2 50       9 if ($bool eq '0') {
270 2         4 $bool = undef;
271             }
272 2         6 return $bool;
273             }
274             elsif ( $type eq 'undef' ) {
275 2         5 $self->_skipchar(';');
276 2         4 return undef;
277             }
278             else {
279 0         0 confess "Unknown element type '$type' found! (cursor $$cursor)";
280             }
281              
282             }
283              
284              
285             sub _parse {
286 8     8   53 my ($self) = @_;
287 8         21 my $cursor = $self->{cursor};
288 8         16 my $string = $self->{string};
289 8         18 my $strlen = $self->{strlen};
290 8 50       27 confess("No cursor") unless $cursor;
291 8 50       34 confess("No string") unless $string;
292 8 50       27 confess("No strlen") unless $strlen;
293 8         13 my @elems;
294 8         33 push(@elems,$self->_parse_elem);
295              
296             # warn if we have unused chars
297 5 50       17 if ($$cursor != $strlen) {
298 0         0 carp("WARN: Unused characters in string after $$cursor.");
299             }
300 5         14 return @elems;
301              
302             } # End of decode.
303              
304             sub _readstr {
305 662     662   744 my ($self, $length) = @_;
306 662         779 my $string = $self->{string};
307 662         862 my $cursor = $self->{cursor};
308 662 100       1377 if ($$cursor + $length > length($$string)) {
309 1         435 croak("ERROR: Read past end of string. Want $length after $$cursor. (".$$string.")");
310             }
311 661         912 my $str = substr($$string, $$cursor, $length);
312 661         714 $$cursor += $length;
313              
314 661         1646 return $str;
315             }
316              
317             sub _readchar {
318 623     623   866 my ($self) = @_;
319 623         962 return $self->_readstr(1);
320             }
321              
322             sub _readnum {
323             # Reads in a character at a time until we run out of numbers to read...
324 85     85   101 my ($self) = @_;
325 85         107 my $cursor = $self->{cursor};
326              
327 85         84 my $string;
328 85         83 while ( 1 ) {
329 212         403 my $char = $self->_readchar;
330 212 100       673 if ( $char !~ /^[\d\.-]+$/ ) {
331 85         91 $$cursor--;
332 85         794 last;
333             }
334 127         170 $string .= $char;
335             } # End of while.
336              
337 85         1491 return $string;
338             } # End of readnum
339              
340             sub _skipchar {
341 320     320   837 my $self = shift;
342 320         342 my $want = shift;
343 320         490 my $c = $self->_readchar();
344 320 100 100     1350 if (($want)&&($c ne $want)) {
345 1         3 my $cursor = $self->{cursor};
346 1         2 my $str = $self->{string};
347 1         309 croak("ERROR: Wrong char $c, expected $want at position ".$$cursor." (".$$str.")");
348             }
349 319 50 66     1737 print "_skipchar: WRONG char $c ($want)\n" if (($want)&&($c ne $want));
350             # ${$$self{cursor}}++;
351             } # Move our cursor one bytes ahead...
352              
353              
354             =head2 encode($reference,[optional $asString,[optional $sortHashes]])
355              
356             Serializes the memory structure pointed to by $reference, and returns a scalar value of encoded data.
357              
358             If the optional $asString is true, $reference will be encoded as string if it is double or float.
359              
360             If the optional $sortHashes is true, all hashes will be sorted before serialization.
361              
362             NOTE: Will recursively encode objects, hashes, arrays, etc.
363              
364             SEE ALSO: serialize()
365              
366             =cut
367              
368             sub encode {
369 32     32 1 61 my ($self, $val, $iskey, $shash) = @_;
370 32 100       79 $iskey=0 unless defined $iskey;
371 32 50       107 $sorthash=$shash if defined $shash;
372              
373 32 50       142 if ( ! defined $val ) {
    50          
    100          
374 0         0 return $self->_encode('null', $val);
375             }
376             elsif ( blessed $val ) {
377 0         0 return $self->_encode('obj', $val);
378             }
379             elsif ( ! ref($val) ) {
380 24 100 66     385 if ( $val =~ /^-?(?:[0-9]|[1-9]\d{1,10})$/ && abs($val) < 2**31 ) {
    100 100        
381 14         42 return $self->_encode('int', $val);
382             }
383             elsif ( $val =~ /^-?\d+\.\d*$/ && !$iskey) {
384 1         3 return $self->_encode('float', $val);
385             }
386             else {
387 9         43 return $self->_encode('string', $val);
388             }
389             }
390             else {
391 8         22 my $type = ref($val);
392 8 50 66     47 if ($type eq 'HASH' || $type eq 'ARRAY' ) {
393 8 50 33     27 return $self->_sort_hash_encode($val) if (($sorthash) and ($type eq 'HASH'));
394 8         30 return $self->_encode('array', $val);
395             }
396             else {
397 0         0 confess "I can't serialize data of type '$type'!";
398             }
399             }
400             }
401              
402             sub _sort_hash_encode {
403 0     0   0 my ($self, $val) = @_;
404              
405 0         0 my $buffer = '';
406 0 0 0     0 my @hsort = ((ref($sorthash) eq 'HASH') and (ref(${$sorthash}{$val}) eq 'ARRAY')) ? ${$sorthash}{$val} : sort keys %{$val};
  0         0  
  0         0  
407 0         0 $buffer .= sprintf('a:%d:',scalar(@hsort)) . '{';
408 0         0 for (@hsort) {
409 0         0 $buffer .= $self->encode($_,1);
410 0         0 $buffer .= $self->encode($$val{$_});
411             }
412 0         0 $buffer .= '}';
413 0         0 return $buffer;
414             }
415              
416             sub _encode {
417 32     32   66 my ($self, $type, $val) = @_;
418              
419 32         46 my $buffer = '';
420 32 50       132 if ( $type eq 'null' ) {
    100          
    100          
    100          
    50          
    0          
421 0         0 $buffer .= 'N;';
422             }
423             elsif ( $type eq 'int' ) {
424 14         67 $buffer .= sprintf('i:%d;', $val);
425             }
426             elsif ( $type eq 'float' ) {
427 1         6 $buffer .= sprintf('d:%s;', $val);
428             }
429             elsif ( $type eq 'string' ) {
430 9         41 $buffer .= sprintf('s:%d:"%s";', length($val), $val);
431             }
432             elsif ( $type eq 'array' ) {
433 8 100       25 if ( ref($val) eq 'ARRAY' ) {
434 2         5 $buffer .= sprintf('a:%d:',($#{$val}+1)) . '{';
  2         19  
435 5         38 map { # Ewww
436 2         9 $buffer .= $self->encode($_);
437 5         15 $buffer .= $self->encode($$val[$_]);
438 2         7 } 0..$#{$val};
439 2         6 $buffer .= '}';
440             }
441             else {
442 6         10 $buffer .= sprintf('a:%d:',scalar(keys(%{$val}))) . '{';
  6         39  
443 6         12 while ( my ($key, $value) = each(%{$val}) ) {
  14         60  
444 8         35 $buffer .= $self->encode($key,1);
445 8         22 $buffer .= $self->encode($value);
446             }
447 6         10 $buffer .= '}';
448             }
449             }
450             elsif ( $type eq 'obj' ) {
451 0         0 my $class = ref($val);
452 0         0 $class =~ /(\w+)$/;
453 0         0 my $subclass = $1;
454 0         0 $buffer .= sprintf('O:%d:"%s":%d:', length($subclass), $subclass, scalar(keys %{$val})) . '{';
  0         0  
455 0         0 foreach ( %{$val} ) {
  0         0  
456 0         0 $buffer .= $self->encode($_);
457             }
458 0         0 $buffer .= '}';
459             }
460             else {
461 0         0 confess "Unknown encode type!";
462             }
463 32         125 return $buffer;
464              
465             }
466              
467             =head1 TODO
468              
469             Support diffrent object types
470              
471             =head1 AUTHOR INFORMATION
472              
473             Copyright (c) 2003 Jesse Brown . All rights reserved. This program is free software;
474             you can redistribute it and/or modify it under the same terms as Perl itself.
475              
476             Various patches contributed by assorted authors on rt.cpan.org (as detailed in Changes file).
477              
478             Currently maintained by Tomas Doran .
479              
480             Rewritten to solve all known bugs by Bjørn-Olav Strand
481              
482             =cut
483              
484             package PHP::Serialization::Object;
485              
486             1;