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         21  
  7         193  
8 7     7   34 use warnings;
  7         13  
  7         164  
9 7     7   3301 use Tie::IxHash;
  7         28204  
  7         229  
10 7     7   48 use Exporter qw(import);
  7         13  
  7         10907  
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 884 my $name = "$arrpfx$arridx";
22 339         556 $arridx++;
23 339         1666 return $name;
24             }
25              
26             sub is_int_index {
27 1235     1235 0 2146 my ($k) = @_;
28              
29 1235 100       3652 if ($k =~ /^\-?\d+$/) {
30 1097         4034 return 1;
31             }
32 138         414 return 0;
33             }
34              
35             sub _ordered_map {
36 161     161   274 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         762 tie my %map, "Tie::IxHash";
43              
44             # convert existing consecutive numeric map to ordered map
45             #
46 161 100       2961 if (exists $self->{map}) {
47 13         21 foreach my $k (sort { $a <=> $b } keys %{$self->{map}}) {
  10         36  
  13         63  
48 22         270 $map{$k} = $self->{map}{$k};
49             }
50             }
51 161         658 return \%map;
52             }
53              
54             sub new {
55 339     339 1 2452 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         1059 my $self = bless {
61             %args,
62             name => new_name(),
63             idx => undef,
64             pos => 0,
65             }, $class;
66 339 100       1031 $self->{strmap} = $class_strmap unless exists $self->{strmap};
67              
68 339 100       707 if (defined $self->{strmap}) {
69 335         1023 $self->{strmap}{$self->{name}} = $self; # register name
70             }
71 339         960 return $self;
72             }
73              
74             # return number if key contains numeric value.
75             #
76             sub get_index {
77 378     378 0 692 my ($self, $k) = @_;
78 378         628 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     1457 if (defined $self->{strmap} && exists $self->{strmap}{$k}) {
85 305         628 $k = $self->{strmap}{$k};
86             }
87 378 100 100     3210 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         487 $k = int($k);
91             } else {
92 144         252 $k = $k0;
93             }
94 378         793 return $k;
95             }
96              
97             sub set {
98 432     432 1 2508 my ($self, $k, $v) = @_;
99              
100             # without key use the increment of the largest previously used int key
101             #
102 432 100       904 if (defined $k) {
103 179         393 $k = $self->get_index($k);
104 179 100       467 if (is_int_index($k)) {
105 93 100 100     373 if (!defined $self->{idx} || ($k >= $self->{idx})) {
106 59         126 $self->{idx} = $k+1;
107             }
108             } else {
109 86         179 $self->{non_numeric} = 1;
110             }
111 179 100 100     601 if (!exists $self->{map} || !exists $self->{ordered}) {
112 130         302 $self->{map} = $self->_ordered_map();
113 130         257 $self->{ordered} = 1;
114             }
115             } else {
116             # use faster unordered map as long as no explicit key is used.
117             #
118 253 100       770 $self->{map} = {} unless exists $self->{map};
119 253 100       643 $self->{idx} = 0 unless defined $self->{idx};
120 253         387 $k = $self->{idx};
121 253         399 $self->{idx} += 1;
122             }
123 432 50 66     1977 if (defined $self->{strmap} && (ref($v) eq ref($self))) {
124 0         0 $self->{map}{$k} = $v->{name};
125             } else {
126 432         2053 $self->{map}{$k} = $v;
127             }
128             #printf ">> setarr: %s{%s} = %s\n", $self->{name}, $k, $v if $opt{v};
129 432         3619 return $self;
130             }
131              
132             sub get {
133 219     219 1 964 my ($self, $k) = @_;
134              
135 219 100       498 if (exists $self->{map}) {
136 197         472 $k = $self->get_index($k);
137              
138 197 100       737 if (exists $self->{map}{$k}) {
139 161         794 return $self->{map}{$k};
140             }
141             }
142 58         236 return;
143             }
144              
145             sub copy {
146 63     63 1 650 my ($self, $keys) = @_;
147              
148             # TODO: #arr$x.$y sub-name here?
149             #
150 63         194 my $c = PHP::Decode::Array->new(strmap => $self->{strmap});
151              
152 63 100       271 if (exists $self->{map}) {
153 51 100 100     208 if (exists $self->{ordered} || defined $keys) {
154 29         92 $c->{map} = $c->_ordered_map();
155 29         109 $c->{ordered} = 1;
156             } else {
157 22         53 $c->{map} = {};
158             }
159 51 100       140 unless (defined $keys) {
160 50         71 $keys = [keys %{$self->{map}}]; # default: all keys
  50         178  
161             }
162 51         667 foreach my $k (@$keys) {
163 87         576 my $v0 = $self->{map}{$k};
164 87         440 my $v = $v0;
165              
166 87 50 66     427 if (defined $v && defined $self->{strmap} && exists $self->{strmap}{$v}) {
      66        
167 82         139 $v = $self->{strmap}{$v};
168             }
169 87 100 66     302 if (defined $v && (ref($v) eq ref($self))) {
170 5         22 my $subarray = $v->copy();
171 5 100       38 if (defined $self->{strmap}) {
172 4         18 $c->{map}{$k} = $subarray->{name};
173             } else {
174 1         4 $c->{map}{$k} = $subarray;
175             }
176             } else {
177 82         257 $c->{map}{$k} = $v0;
178             }
179             }
180 51         527 $c->{idx} = $self->{idx};
181 51         109 $c->{pos} = $self->{pos};
182 51 100       1585 $c->{non_mumeric} = 1 if exists $self->{non_numeric};
183             }
184 63         181 return $c;
185             }
186              
187             sub delete {
188 2     2 1 14 my ($self, $k) = @_;
189              
190 2 50       8 if (exists $self->{map}) {
191             # after deletion key order has to be preserved
192             #
193 2 50       7 unless (exists $self->{ordered}) {
194 2         6 $self->{map} = $self->_ordered_map();
195 2         7 $self->{ordered} = 1;
196             }
197 2         12 return delete $self->{map}{$k};
198             }
199 0         0 return;
200             }
201              
202             sub val {
203 1358     1358 1 2392 my ($self, $k) = @_;
204 1358 50       3046 exists $self->{map}{$k} || die "assert: bad key $k passed to array->val()";
205 1358         4083 return $self->{map}{$k}; # for get_keys lookup
206             }
207              
208             sub get_keys {
209 1204     1204 1 2185 my ($self) = @_;
210              
211 1204 100       2628 if (exists $self->{map}) {
212 1111         1572 my @keys;
213 1111 100       2170 if (exists $self->{ordered}) {
214             # insertion order is preserved by Tie::IxHash
215             #
216 246         351 @keys = keys %{$self->{map}};
  246         1137  
217             } else {
218 865         1221 @keys = sort { $a <=> $b } keys %{$self->{map}};
  722         2746  
  865         3568  
219             }
220 1111         7780 return \@keys;
221             }
222 93         248 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 20 my ($self) = @_;
244              
245 11         25 return $self->{pos};
246             }
247              
248             sub set_pos {
249 10     10 1 22 my ($self, $pos) = @_;
250              
251 10         15 $self->{pos} = $pos;
252 10         20 return;
253             }
254              
255             sub is_numerical {
256 6     6 0 14 my ($self) = @_;
257              
258 6 50       19 if (exists $self->{non_numeric}) {
259 0         0 return 0;
260             }
261 6         16 return 1;
262             }
263              
264             sub empty {
265 21     21 1 59 my ($self) = @_;
266              
267 21         33 return (keys %{$self->{map}} == 0);
  21         112  
268             }
269              
270             sub to_str {
271 8     8 1 639 my ($self) = @_;
272 8         20 my $keys = $self->get_keys();
273 8         13 my $str = '(';
274              
275 8         13 foreach my $k (@$keys) {
276 16         44 my $v = $self->{map}{$k};
277              
278 16 0 33     117 if (defined $v && defined $self->{strmap} && exists $self->{strmap}{$v}) {
      33        
279 0         0 $v = $self->{strmap}{$v};
280             }
281 16 100       36 $str .= ', ' if ($str ne '(');
282 16 100 66     51 if (defined $v && (ref($v) eq ref($self))) {
283 2         7 $str .= $v->to_str();
284             } else {
285 14 100       38 if (is_int_index($k)) {
286 11         29 $str .= "$k => '$v'";
287             } else {
288 3         10 $str .= "'$k' => '$v'";
289             }
290             }
291             }
292 8         15 $str .= ')';
293 8         21 return $str;
294             }
295              
296             1;
297              
298             __END__