File Coverage

blib/lib/Data/Dump/PHP.pm
Criterion Covered Total %
statement 211 270 78.1
branch 90 146 61.6
condition 40 78 51.2
subroutine 14 14 100.0
pod 2 7 28.5
total 357 515 69.3


line stmt bran cond sub pod time code
1             package Data::Dump::PHP;
2 6     6   29940 use strict;
  6         13  
  6         217  
3 6     6   33 use vars qw(@EXPORT @EXPORT_OK $DEBUG);
  6         11  
  6         398  
4 6     6   6136 use subs qq(dump);
  6         134  
  6         27  
5              
6             our $VERSION = '0.10'; # VERSION
7              
8             require Exporter;
9             *import = \&Exporter::import;
10             @EXPORT = qw(dd_php ddx_php);
11             @EXPORT_OK = qw(dump_php pp_php quote_php);
12              
13             $DEBUG = 0;
14              
15 6     6   10821 use overload ();
  6         6959  
  6         171  
16 6     6   37 use vars qw(%seen %refcnt @dump @fixup %require $TRY_BASE64 $USE_LAMBDA);
  6         11  
  6         31834  
17              
18             $USE_LAMBDA = 0;
19             $TRY_BASE64 = 50 unless defined $TRY_BASE64;
20              
21             my %is_perl_keyword = map { $_ => 1 }
22             qw( __FILE__ __LINE__ __PACKAGE__ __DATA__ __END__ AUTOLOAD BEGIN CORE
23             DESTROY END EQ GE GT INIT LE LT NE abs accept alarm and atan2 bind
24             binmode bless caller chdir chmod chomp chop chown chr chroot close
25             closedir cmp connect continue cos crypt dbmclose dbmopen defined
26             delete die do dump each else elsif endgrent endhostent endnetent
27             endprotoent endpwent endservent eof eq eval exec exists exit exp fcntl
28             fileno flock for foreach fork format formline ge getc getgrent
29             getgrgid getgrnam gethostbyaddr gethostbyname gethostent getlogin
30             getnetbyaddr getnetbyname getnetent getpeername getpgrp getppid
31             getpriority getprotobyname getprotobynumber getprotoent getpwent
32             getpwnam getpwuid getservbyname getservbyport getservent getsockname
33             getsockopt glob gmtime goto grep gt hex if index int ioctl join keys
34             kill last lc lcfirst le length link listen local localtime lock log
35             lstat lt m map mkdir msgctl msgget msgrcv msgsnd my ne next no not oct
36             open opendir or ord pack package pipe pop pos print printf prototype
37             push q qq qr quotemeta qw qx rand read readdir readline readlink
38             readpipe recv redo ref rename require reset return reverse rewinddir
39             rindex rmdir s scalar seek seekdir select semctl semget semop send
40             setgrent sethostent setnetent setpgrp setpriority setprotoent setpwent
41             setservent setsockopt shift shmctl shmget shmread shmwrite shutdown
42             sin sleep socket socketpair sort splice split sprintf sqrt srand stat
43             study sub substr symlink syscall sysopen sysread sysseek system
44             syswrite tell telldir tie tied time times tr truncate uc ucfirst umask
45             undef unless unlink unpack unshift untie until use utime values vec
46             wait waitpid wantarray warn while write x xor y);
47              
48              
49             sub dump
50             {
51 38     38   7723 local %seen;
52 38         65 local %refcnt;
53 38         53 local %require;
54 38         65 local @fixup;
55              
56 38         56 my $name = "a";
57 38         47 my @dump;
58              
59 38         80 for my $v (@_) {
60 49         138 my $val = _dump($v, $name, [], tied($v));
61 49         161 push(@dump, [$name, $val]);
62             } continue {
63 49         122 $name++;
64             }
65              
66 38         57 my $out = "";
67 38 50       101 if (%require) {
68 0         0 die "BUG: should not require() for PHP";
69 0         0 for (sort keys %require) {
70 0         0 $out .= "require $_;\n";
71             }
72             }
73 38 100       123 if (%refcnt) {
74             # output all those with refcounts first
75 2         4 for (@dump) {
76 2         3 my $name = $_->[0];
77 2 50       5 if ($refcnt{$name}) {
78 2         6 $out .= "\$$name = $_->[1];\n";
79 2         4 undef $_->[1];
80             }
81             }
82 2         4 for (@fixup) {
83 2         6 $out .= "$_;\n";
84             }
85             }
86              
87 38         65 my $paren = (@dump != 1);
88 38 50       94 $out .= (@fixup ? "return ":"")."array(" if $paren;
    100          
89 49 50 33     226 $out .= format_list($paren, undef,
    100          
90 38         113 map {defined($dump[$_][1]) ? $dump[$_][1] : (!$paren && $_ == @dump-1 ? "return ":"")."\$".$dump[$_][0]}
91             0..$#dump
92             );
93 38 100       98 $out .= ")" if $paren;
94              
95 38 100 66     200 if (%refcnt || %require) {
96 2         3 $out .= ";\n";
97 2         10 $out =~ s/^/ /gm; # indent
98 2 100       5 if ($USE_LAMBDA) {
99 1         2 $out = "call_user_func(function() { ".$out." })";
100             } else {
101 1         518 $out = "call_user_func(create_function('', ".quote($out)."))";
102             }
103             }
104              
105             #use Data::Dumper; print Dumper(\%refcnt);
106             #use Data::Dumper; print Dumper(\%seen);
107              
108 38 50       90 print STDERR "$out\n" unless defined wantarray;
109 38         449 $out;
110             }
111              
112             *dump_php = \&dump;
113             *pp_php = \&dump;
114              
115             sub dd_php {
116 1     1 1 2517 print dump(@_), "\n";
117             }
118              
119             sub ddx_php {
120 1     1 1 44 my(undef, $file, $line) = caller;
121 1         7 $file =~ s,.*[\\/],,;
122 1         6 my $out = "$file:$line: " . dump(@_) . "\n";
123 1         6 $out =~ s/^/# /gm;
124 1         148 print $out;
125             }
126              
127             sub _dump
128             {
129 84     84   131 my $ref = ref $_[0];
130 84 100       176 my $rval = $ref ? $_[0] : \$_[0];
131 84         95 shift;
132              
133 84         159 my($name, $idx, $dont_remember) = @_;
134              
135 84         89 my($class, $type, $id);
136 84 50       236 if (overload::StrVal($rval) =~ /^(?:([^=]+)=)?([A-Z]+)\(0x([^\)]+)\)$/) {
137 84         777 $class = $1;
138 84         138 $type = $2;
139 84         142 $id = $3;
140             } else {
141 0         0 die "Can't parse " . overload::StrVal($rval);
142             }
143 84 50 33     255 if ($] < 5.008 && $type eq "SCALAR") {
144 0 0       0 $type = "REF" if $ref eq "REF";
145             }
146 84 50       178 warn "\$$name(@$idx) $class $type $id ($ref)" if $DEBUG;
147              
148 84 50       163 unless ($dont_remember) {
149 84 100       209 if (my $s = $seen{$id}) {
150 2         3 my($sname, $sidx) = @$s;
151 2         5 $refcnt{$sname}++;
152 2   33     13 my $sref = fullname($sname, $sidx,
153             ($ref && $type eq "SCALAR"));
154 2 50       6 warn "SEEN: [\$$name(@$idx)] => [\$$sname(@$sidx)] ($ref,$sref)" if $DEBUG;
155 2 50       4 return $sref unless $sname eq $name;
156 2         4 $refcnt{$name}++;
157 2         4 push(@fixup, fullname($name,$idx) . " =& " . $sref);
158 2 50 33     15 die "Can't handle returning references for PHP yet" if @$idx && $idx->[-1] eq '$';
159             #return "do{my \$fix}" if @$idx && $idx->[-1] eq '$';
160 2         8 return "'fix'";
161             }
162 82         346 $seen{$id} = [$name, $idx];
163             }
164              
165 82         95 my $out;
166 82 100 66     368 if ($type eq "SCALAR" || $type eq "REF" || $type eq "REGEXP") {
    50 100        
    100          
    50          
    0          
167 72 100       111 if ($ref) {
168 8 50 33     28 if ($class && $class eq "Regexp") {
169 8         10 my $v = "$rval";
170              
171 8         9 my $mod = "";
172 8 50       34 if ($v =~ /^\(\?\^?([msix-]*):([\x00-\xFF]*)\)\z/) {
173 8         12 $mod = $1;
174 8         13 $v = $2;
175 8         11 $mod =~ s/-.*//;
176             }
177              
178 8         8 my $sep = '/';
179 8         18 my $sep_count = ($v =~ tr/\///);
180 8 100       16 if ($sep_count) {
181             # see if we can find a better one
182 4         6 for ('|', ',', ':', '#') {
183 10         553 my $c = eval "\$v =~ tr/\Q$_\E//";
184             #print "SEP $_ $c $sep_count\n";
185 10 100       34 if ($c < $sep_count) {
186 3         4 $sep = $_;
187 3         3 $sep_count = $c;
188 3 50       7 last if $sep_count == 0;
189             }
190             }
191             }
192 8         67 $v =~ s/\Q$sep\E/\\$sep/g;
193              
194 8         21 $out = quote("$sep$v$sep$mod");
195 8         16 undef($class);
196             }
197             else {
198 0         0 die "Can't handle non-Regexp builtin object (class $class) for PHP yet";
199 0 0       0 delete $seen{$id} if $type eq "SCALAR"; # will be seen again shortly
200 0         0 my $val = _dump($$rval, $name, [@$idx, "\$"]);
201 0 0       0 $out = $class ? "do{\\(my \$o = $val)}" : "\\$val";
202             }
203             } else {
204 64 100 100     427 if (!defined $$rval) {
    100          
205 2         7 $out = "null";
206             }
207             elsif ($$rval =~ /^-?[1-9]\d{0,9}\z/ || $$rval eq "0") {
208 42         61 $out = $$rval;
209             }
210             else {
211 20         66 $out = str($$rval);
212             }
213 64 50 33     185 if ($class && !@$idx) {
214 0         0 die "Can't handle nonref, class, nonidx for PHP yet";
215             # Top is an object, not a reference to one as perl needs
216 0         0 $refcnt{$name}++;
217 0         0 my $obj = fullname($name, $idx);
218 0         0 my $cl = quote($class);
219 0         0 push(@fixup, "bless \\$obj, $cl");
220             }
221             }
222             }
223             elsif ($type eq "GLOB") {
224 0         0 die "Can't handle glob for PHP yet";
225 0 0       0 if ($ref) {
226 0         0 delete $seen{$id};
227 0         0 my $val = _dump($$rval, $name, [@$idx, "*"]);
228 0         0 $out = "\\$val";
229 0 0       0 if ($out =~ /^\\\*Symbol::/) {
230 0         0 $require{Symbol}++;
231 0         0 $out = "Symbol::gensym()";
232             }
233             } else {
234 0         0 my $val = "$$rval";
235 0         0 $out = "$$rval";
236              
237 0         0 for my $k (qw(SCALAR ARRAY HASH)) {
238 0         0 my $gval = *$$rval{$k};
239 0 0       0 next unless defined $gval;
240 0 0 0     0 next if $k eq "SCALAR" && ! defined $$gval; # always there
241 0         0 my $f = scalar @fixup;
242 0         0 push(@fixup, "RESERVED"); # overwritten after _dump() below
243 0         0 $gval = _dump($gval, $name, [@$idx, "*{$k}"]);
244 0         0 $refcnt{$name}++;
245 0         0 my $gname = fullname($name, $idx);
246 0         0 $fixup[$f] = "$gname = $gval"; #XXX indent $gval
247             }
248             }
249             }
250             elsif ($type eq "ARRAY") {
251 1         2 my @vals;
252 1         4 my $tied = tied_str(tied(@$rval));
253 1 50       11 die "Can't handle tied arrayref for PHP yet" if $tied;
254 1         2 my $i = 0;
255 1         4 for my $v (@$rval) {
256 5         39 push(@vals, _dump($v, $name, [@$idx, "[$i]"], $tied));
257 5         12 $i++;
258             }
259 1         4 $out = "array(" . format_list(1, $tied, @vals) . ")";
260             }
261             elsif ($type eq "HASH") {
262 9         17 my(@keys, @vals);
263 9         31 my $tied = tied_str(tied(%$rval));
264 9 50       25 die "Can't handle tied hashref for PHP yet" if $tied;
265              
266             # statistics to determine variation in key lengths
267 9         12 my $kstat_max = 0;
268 9         10 my $kstat_sum = 0;
269 9         9 my $kstat_sum2 = 0;
270              
271 9         33 my @orig_keys = keys %$rval;
272 9         112 my $text_keys = 0;
273 9         19 for (@orig_keys) {
274 13 100       60 $text_keys++, last unless /^[-+]?(?:0|[1-9]\d*)(?:\.\d+)?\z/;
275             }
276              
277 9 100       22 if ($text_keys) {
278 7         28 @orig_keys = sort @orig_keys;
279             }
280             else {
281 2         8 @orig_keys = sort { $a <=> $b } @orig_keys;
  6         13  
282             }
283              
284 9         14 for my $key (@orig_keys) {
285 30         55 my $val = \$rval->{$key};
286 30 100       110 $key = quote($key) if #$is_perl_keyword{$key} ||
287             !(#$key =~ /^[a-zA-Z_]\w{0,19}\z/ ||
288             $key =~ /^-?[1-9]\d{0,8}\z/
289             );
290              
291 30 100       71 $kstat_max = length($key) if length($key) > $kstat_max;
292 30         37 $kstat_sum += length($key);
293 30         44 $kstat_sum2 += length($key)*length($key);
294              
295 30         53 push(@keys, $key);
296 30         160 push(@vals, _dump($$val, $name, [@$idx, "[$key]"], $tied));
297             }
298 9         16 my $nl = "";
299 9         12 my $klen_pad = 0;
300 9         36 my $tmp = "@keys @vals";
301 9 50 66     59 if (length($tmp) > 60 || $tmp =~ /\n/ || $tied) {
      66        
302 3         5 $nl = "\n";
303              
304             # Determine what padding to add
305 3 100       15 if ($kstat_max < 4) {
    50          
306 1         1 $klen_pad = $kstat_max;
307             }
308             elsif (@keys >= 2) {
309 2         4 my $n = @keys;
310 2         6 my $avg = $kstat_sum/$n;
311 2         13 my $stddev = sqrt(($kstat_sum2 - $n * $avg * $avg) / ($n - 1));
312              
313             # I am not actually very happy with this heuristics
314 2 100       11 if ($stddev / $kstat_max < 0.25) {
315 1         2 $klen_pad = $kstat_max;
316             }
317 2 50       7 if ($DEBUG) {
318 0         0 push(@keys, "__S");
319 0         0 push(@vals, sprintf("%.2f (%d/%.1f/%.1f)",
320             $stddev / $kstat_max,
321             $kstat_max, $avg, $stddev));
322             }
323             }
324             }
325 9         16 $out = "array($nl";
326 9 50       26 $out .= " # $tied$nl" if $tied;
327 9         22 while (@keys) {
328 30         89 my $key = shift @keys;
329 30         62 my $val = shift @vals;
330 30         75 my $pad = " " x ($klen_pad + 6);
331 30         38 $val =~ s/\n/\n$pad/gm;
332 30 100       66 $key = " $key" . " " x ($klen_pad - length($key)) if $nl;
333 30         89 $out .= " $key => $val,$nl";
334             }
335 9 100       37 $out =~ s/,$/ / unless $nl;
336 9         586 $out .= ")";
337             }
338             elsif ($type eq "CODE") {
339 0 0       0 if ($USE_LAMBDA) {
340 0         0 $out = "function() {}";
341             } else {
342 0         0 $out = "create_function('', '')";
343             }
344             }
345             else {
346 0         0 warn "Can't handle $type data";
347 0         0 $out = "'#$type#'";
348             }
349              
350 82 50 33     222 if ($class && $ref) {
351 0         0 die "Can't handle object (class $class) for PHP yet";
352 0         0 $out = "bless($out, " . quote($class) . ")";
353             }
354 82         209 return $out;
355             }
356              
357             sub tied_str {
358 10     10 0 15 my $tied = shift;
359 10 50       30 if ($tied) {
360 0 0       0 if (my $tied_ref = ref($tied)) {
361 0         0 $tied = "tied $tied_ref";
362             }
363             else {
364 0         0 $tied = "tied";
365             }
366             }
367 10         57 return $tied;
368             }
369              
370             sub fullname
371             {
372 4     4 0 6 my($name, $idx, $ref) = @_;
373 4         5 substr($name, 0, 0) = "\$";
374              
375 4         6 my @i = @$idx; # need copy in order to not modify @$idx
376 4         7 my @ci = @i;
377 4 0 33     11 if ($ref && @i && $i[0] eq "\$") {
      33        
378 0         0 shift(@i); # remove one deref
379 0         0 $ref = 0;
380             }
381 4   66     19 while (@i && $i[0] eq "\$") {
382 0         0 shift @i;
383 0         0 $name = "\$$name";
384             }
385              
386 4         5 my $last_was_index;
387 4         7 for my $i (@i) {
388 2 50 33     12 if ($i eq "*" || $i eq "\$") {
    50          
389 0         0 $last_was_index = 0;
390 0         0 $name = "$i\{$name}";
391             } elsif ($i =~ s/^\*//) {
392 0         0 $name .= $i;
393 0         0 $last_was_index++;
394             } else {
395             #$name .= "->" unless $last_was_index++;
396 2         4 $name .= $i;
397             }
398             }
399 4 50       8 $name = "\\$name" if $ref;
400 4         10 "*".join("", map {"<$_>"} @ci)."*$name*"; #X#
  2         8  
401 4         17 $name;
402             }
403              
404             sub format_list
405             {
406 39     39 0 67 my $paren = shift;
407 39         46 my $comment = shift;
408 39 100       74 my $indent_lim = $paren ? 0 : 1;
409 39         113 my $tmp = "@_";
410 39 50 33     210 if ($comment || (@_ > $indent_lim && (length($tmp) > 60 || $tmp =~ /\n/))) {
      66        
      33        
411 0         0 my @elem = @_;
412 0         0 for (@elem) { s/^/ /gm; } # indent
  0         0  
413 0 0       0 return "\n" . ($comment ? " # $comment\n" : "") .
414             join(",\n", @elem, "");
415             } else {
416 39         153 return join(", ", @_);
417             }
418             }
419              
420             sub str {
421 20 100   20 0 64 if (length($_[0]) > 30) {
422 10         23 for ($_[0]) {
423             # Check for repeated string
424 10 100       41 if (/^(.)\1\1\1/s) {
425             # seems to be a repating sequence, let's check if it really is
426             # without backtracking
427 4 50       91550 unless (/[^\Q$1\E]/) {
428 4         14 my $base = quote($1);
429 4         8 my $repeat = length;
430 4         21 return "str_repeat($base, $repeat)"
431             }
432             }
433             # Length protection because the RE engine will blow the stack [RT#33520]
434 6 100 66     92 if (length($_) < 16 * 1024 && /^(.{2,5}?)\1*\z/s) {
435 2         9 my $base = quote($1);
436 2         8 my $repeat = length($_)/length($1);
437 2         37 return "str_repeat($base, $repeat)";
438             }
439             }
440             }
441              
442 14         83 local $_ = "e;
443              
444 14 100 100     87 if (length($_) > 40 && !/\\x\{/ && length($_) > (length($_[0]) * 2)) {
      100        
445             # too much binary data, better to represent as a hex/base64 string
446              
447             # Base64 is more compact than hex when string is longer than
448             # 17 bytes (not counting any require statement needed).
449             # But on the other hand, hex is much more readable.
450 1 50 33     9 if ($TRY_BASE64 && length($_[0]) > $TRY_BASE64 &&
      33        
451 1         9 eval { require MIME::Base64 })
452             {
453             #$require{"MIME::Base64"}++;
454 1         13 return "base64_decode(\"" .
455             MIME::Base64::encode($_[0],"") .
456             "\")";
457             }
458 0         0 return "pack(\"H*\",\"" . unpack("H*", $_[0]) . "\")";
459             }
460              
461 13         30 return $_;
462             }
463              
464             my %esc = (
465             "\t" => "\\t",
466             "\n" => "\\n",
467             "\f" => "\\014", # \f only since 5.2.5
468             "\r" => "\\r",
469             "\x0b" => "\\013", # \v only since 5.2.5
470             );
471              
472             # put a string value in double quotes
473             sub quote {
474 63     63 0 367 local($_) = $_[0];
475             # If there are many '"' we might want to use qq() instead
476 63         178 s/([\\\"\$])/\\$1/g;
477 63 100       278 return qq("$_") unless /[^\040-\176]/; # fast exit
478              
479 15         88 s/([\t\n\f\r\x0b])/$esc{$1}/g;
480              
481             # no need for 3 digits in escape for these
482 15         41 s/([\0-\037])(?!\d)/sprintf('\\%o',ord($1))/eg;
  1058         2791  
483              
484 15         56 s/([\0-\037\177-\377])/sprintf('\\x%02X',ord($1))/eg;
  135         345  
485 15         106 s/([^\040-\176])/sprintf('\\x{%X}',ord($1))/eg;
  105         334  
486              
487 15         128 return qq("$_");
488             }
489              
490             *quote_php = \"e;
491              
492             1;
493             # ABSTRACT: Pretty printing of data structures as PHP code
494              
495             __END__