File Coverage

lib/PHP/Decode/Array.pm
Criterion Covered Total %
statement 135 150 90.0
branch 58 70 82.8
condition 33 42 78.5
subroutine 20 21 95.2
pod 11 16 68.7
total 257 299 85.9


line stmt bran cond sub pod time code
1             #
2             # PHP arrays - a php array is an ordered map.
3             # http://www.php.net/manual/en/language.types.array.php
4             #
5             package PHP::Decode::Array;
6              
7 7     7   68772 use strict;
  7         19  
  7         197  
8 7     7   34 use warnings;
  7         13  
  7         176  
9 7     7   3289 use Tie::IxHash;
  7         27306  
  7         224  
10 7     7   46 use Exporter qw(import);
  7         15  
  7         10891  
11             our @EXPORT_OK = qw(is_int_index);
12             our %EXPORT_TAGS = (all => \@EXPORT_OK);
13              
14             our $VERSION = '0.15';
15              
16             my $arridx = 1;
17             our $arrpfx = '#arr';
18             our $class_strmap; # client might override $PHP::Decode::Array::class_strmap = \%strmap;
19              
20             sub new_name {
21 339     339 0 940 my $name = "$arrpfx$arridx";
22 339         557 $arridx++;
23 339         1612 return $name;
24             }
25              
26             sub is_int_index {
27 1235     1235 0 2237 my ($k) = @_;
28              
29 1235 100       3671 if ($k =~ /^\-?\d+$/) {
30 1097         4204 return 1;
31             }
32 138         426 return 0;
33             }
34              
35             sub _ordered_map {
36 161     161   337 my ($self) = @_;
37              
38             # preserve the order of inserted keys
39             # https://perldoc.perl.org/perlfaq4#How-can-I-make-my-hash-remember-the-order-I-put-elements-into-it?
40             # https://metacpan.org/pod/Tie::IxHash
41             #
42 161         873 tie my %map, "Tie::IxHash";
43              
44             # convert existing consecutive numeric map to ordered map
45             #
46 161 100       3030 if (exists $self->{map}) {
47 13         27 foreach my $k (sort { $a <=> $b } keys %{$self->{map}}) {
  9         31  
  13         60  
48 22         251 $map{$k} = $self->{map}{$k};
49             }
50             }
51 161         616 return \%map;
52             }
53              
54             sub new {
55 339     339 1 2472 my ($class, %args) = @_;
56              
57             # $self->{map} is created on demand and converted to ordered map
58             # if required. A native perl hashmap is much faster.
59             #
60 339         1114 my $self = bless {
61             %args,
62             name => new_name(),
63             idx => undef,
64             pos => 0,
65             }, $class;
66 339 100       960 $self->{strmap} = $class_strmap unless exists $self->{strmap};
67              
68 339 100       817 if (defined $self->{strmap}) {
69 335         1053 $self->{strmap}{$self->{name}} = $self; # register name
70             }
71 339         997 return $self;
72             }
73              
74             # return number if key contains numeric value.
75             #
76             sub get_index {
77 378     378 0 740 my ($self, $k) = @_;
78 378         607 my $k0 = $k;
79              
80             # float keys are truncated to int,
81             # http://php.net/manual/en/language.types.array.php
82             # (but only int-strings are converted to int-key)
83             #
84 378 100 100     1440 if (defined $self->{strmap} && exists $self->{strmap}{$k}) {
85 305         641 $k = $self->{strmap}{$k};
86             }
87 378 100 100     3277 if (($k0 =~ /^#str\d+$/) && ($k =~ /^\-?(\d|[1-9]\d+)$/)) {
    100 100        
      100        
88 2         5 $k = int($k);
89             } elsif (($k0 !~ /^#str\d+$/) && (ref($k) eq '') && ($k =~ /^\-?(\d|[1-9]\d+|\d+\.\d*|\d*\.\d+)([eE][+-]?\d+)?$/)) {
90 232         521 $k = int($k);
91             } else {
92 144         302 $k = $k0;
93             }
94 378         770 return $k;
95             }
96              
97             sub set {
98 432     432 1 2618 my ($self, $k, $v) = @_;
99              
100             # without key use the increment of the largest previously used int key
101             #
102 432 100       867 if (defined $k) {
103 179         484 $k = $self->get_index($k);
104 179 100       429 if (is_int_index($k)) {
105 93 100 100     394 if (!defined $self->{idx} || ($k >= $self->{idx})) {
106 59         140 $self->{idx} = $k+1;
107             }
108             } else {
109 86         215 $self->{non_numeric} = 1;
110             }
111 179 100 100     583 if (!exists $self->{map} || !exists $self->{ordered}) {
112 130         310 $self->{map} = $self->_ordered_map();
113 130         289 $self->{ordered} = 1;
114             }
115             } else {
116             # use faster unordered map as long as no explicit key is used.
117             #
118 253 100       692 $self->{map} = {} unless exists $self->{map};
119 253 100       601 $self->{idx} = 0 unless defined $self->{idx};
120 253         418 $k = $self->{idx};
121 253         386 $self->{idx} += 1;
122             }
123 432 50 66     1773 if (defined $self->{strmap} && (ref($v) eq ref($self))) {
124 0         0 $self->{map}{$k} = $v->{name};
125             } else {
126 432         1635 $self->{map}{$k} = $v;
127             }
128             #printf ">> setarr: %s{%s} = %s\n", $self->{name}, $k, $v if $opt{v};
129 432         3710 return $self;
130             }
131              
132             sub get {
133 219     219 1 993 my ($self, $k) = @_;
134              
135 219 100       535 if (exists $self->{map}) {
136 197         507 $k = $self->get_index($k);
137              
138 197 100       737 if (exists $self->{map}{$k}) {
139 161         739 return $self->{map}{$k};
140             }
141             }
142 58         196 return;
143             }
144              
145             sub copy {
146 63     63 1 690 my ($self, $keys) = @_;
147              
148             # TODO: #arr$x.$y sub-name here?
149             #
150 63         197 my $c = PHP::Decode::Array->new(strmap => $self->{strmap});
151              
152 63 100       157 if (exists $self->{map}) {
153 51 100 100     170 if (exists $self->{ordered} || defined $keys) {
154 29         80 $c->{map} = $c->_ordered_map();
155 29         81 $c->{ordered} = 1;
156             } else {
157 22         55 $c->{map} = {};
158             }
159 51 100       122 unless (defined $keys) {
160 50         81 $keys = [keys %{$self->{map}}]; # default: all keys
  50         154  
161             }
162 51         642 foreach my $k (@$keys) {
163 87         597 my $v0 = $self->{map}{$k};
164 87         429 my $v = $v0;
165              
166 87 50 66     428 if (defined $v && defined $self->{strmap} && exists $self->{strmap}{$v}) {
      66        
167 82         160 $v = $self->{strmap}{$v};
168             }
169 87 100 66     316 if (defined $v && (ref($v) eq ref($self))) {
170 5         31 my $subarray = $v->copy();
171 5 100       15 if (defined $self->{strmap}) {
172 4         18 $c->{map}{$k} = $subarray->{name};
173             } else {
174 1         5 $c->{map}{$k} = $subarray;
175             }
176             } else {
177 82         247 $c->{map}{$k} = $v0;
178             }
179             }
180 51         499 $c->{idx} = $self->{idx};
181 51         94 $c->{pos} = $self->{pos};
182 51 100       124 $c->{non_mumeric} = 1 if exists $self->{non_numeric};
183             }
184 63         179 return $c;
185             }
186              
187             sub delete {
188 2     2 1 7 my ($self, $k) = @_;
189              
190 2 50       7 if (exists $self->{map}) {
191             # after deletion key order has to be preserved
192             #
193 2 50       8 unless (exists $self->{ordered}) {
194 2         7 $self->{map} = $self->_ordered_map();
195 2         5 $self->{ordered} = 1;
196             }
197 2         14 return delete $self->{map}{$k};
198             }
199 0         0 return;
200             }
201              
202             sub val {
203 1358     1358 1 2354 my ($self, $k) = @_;
204 1358 50       3149 exists $self->{map}{$k} || die "assert: bad key $k passed to array->val()";
205 1358         3965 return $self->{map}{$k}; # for get_keys lookup
206             }
207              
208             sub get_keys {
209 1204     1204 1 2010 my ($self) = @_;
210              
211 1204 100       2616 if (exists $self->{map}) {
212 1111         1649 my @keys;
213 1111 100       2043 if (exists $self->{ordered}) {
214             # insertion order is preserved by Tie::IxHash
215             #
216 246         332 @keys = keys %{$self->{map}};
  246         1161  
217             } else {
218 865         1417 @keys = sort { $a <=> $b } keys %{$self->{map}};
  761         2834  
  865         3824  
219             }
220 1111         7841 return \@keys;
221             }
222 93         230 return [];
223             }
224              
225             sub get_keys_sorted {
226 0     0 0 0 my ($self) = @_;
227 0         0 my @keys;
228              
229             # sort non-hash arrays by index
230             #
231 0 0       0 if (exists $self->{map}) {
232 0 0       0 if (exists $self->{non_numeric}) {
233 0         0 @keys = sort keys %{$self->{map}};
  0         0  
234             } else {
235 0         0 @keys = sort { $a <=> $b } keys %{$self->{map}};
  0         0  
  0         0  
236             }
237 0         0 return \@keys;
238             }
239 0         0 return [];
240             }
241              
242             sub get_pos {
243 11     11 1 19 my ($self) = @_;
244              
245 11         26 return $self->{pos};
246             }
247              
248             sub set_pos {
249 10     10 1 19 my ($self, $pos) = @_;
250              
251 10         17 $self->{pos} = $pos;
252 10         21 return;
253             }
254              
255             sub is_numerical {
256 6     6 0 13 my ($self) = @_;
257              
258 6 50       17 if (exists $self->{non_numeric}) {
259 0         0 return 0;
260             }
261 6         15 return 1;
262             }
263              
264             sub empty {
265 21     21 1 41 my ($self) = @_;
266              
267 21         38 return (keys %{$self->{map}} == 0);
  21         97  
268             }
269              
270             sub to_str {
271 8     8 1 657 my ($self) = @_;
272 8         17 my $keys = $self->get_keys();
273 8         17 my $str = '(';
274              
275 8         15 foreach my $k (@$keys) {
276 16         42 my $v = $self->{map}{$k};
277              
278 16 0 33     107 if (defined $v && defined $self->{strmap} && exists $self->{strmap}{$v}) {
      33        
279 0         0 $v = $self->{strmap}{$v};
280             }
281 16 100       34 $str .= ', ' if ($str ne '(');
282 16 100 66     46 if (defined $v && (ref($v) eq ref($self))) {
283 2         6 $str .= $v->to_str();
284             } else {
285 14 100       25 if (is_int_index($k)) {
286 11         33 $str .= "$k => '$v'";
287             } else {
288 3         11 $str .= "'$k' => '$v'";
289             }
290             }
291             }
292 8         13 $str .= ')';
293 8         35 return $str;
294             }
295              
296             1;
297              
298             __END__