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   70688 use strict;
  7         23  
  7         205  
8 7     7   35 use warnings;
  7         14  
  7         172  
9 7     7   3763 use Tie::IxHash;
  7         28862  
  7         227  
10 7     7   47 use Exporter qw(import);
  7         13  
  7         12864  
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 812 my $name = "$arrpfx$arridx";
22 339         539 $arridx++;
23 339         1579 return $name;
24             }
25              
26             sub is_int_index {
27 1235     1235 0 2053 my ($k) = @_;
28              
29 1235 100       3775 if ($k =~ /^\-?\d+$/) {
30 1097         4081 return 1;
31             }
32 138         369 return 0;
33             }
34              
35             sub _ordered_map {
36 161     161   267 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         744 tie my %map, "Tie::IxHash";
43              
44             # convert existing consecutive numeric map to ordered map
45             #
46 161 100       2694 if (exists $self->{map}) {
47 13         36 foreach my $k (sort { $a <=> $b } keys %{$self->{map}}) {
  10         31  
  13         69  
48 22         238 $map{$k} = $self->{map}{$k};
49             }
50             }
51 161         601 return \%map;
52             }
53              
54             sub new {
55 339     339 1 2324 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         1026 my $self = bless {
61             %args,
62             name => new_name(),
63             idx => undef,
64             pos => 0,
65             }, $class;
66 339 100       929 $self->{strmap} = $class_strmap unless exists $self->{strmap};
67              
68 339 100       787 if (defined $self->{strmap}) {
69 335         1045 $self->{strmap}{$self->{name}} = $self; # register name
70             }
71 339         917 return $self;
72             }
73              
74             # return number if key contains numeric value.
75             #
76             sub get_index {
77 378     378 0 658 my ($self, $k) = @_;
78 378         544 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     1359 if (defined $self->{strmap} && exists $self->{strmap}{$k}) {
85 305         629 $k = $self->{strmap}{$k};
86             }
87 378 100 100     3185 if (($k0 =~ /^#str\d+$/) && ($k =~ /^\-?(\d|[1-9]\d+)$/)) {
    100 100        
      100        
88 2         13 $k = int($k);
89             } elsif (($k0 !~ /^#str\d+$/) && (ref($k) eq '') && ($k =~ /^\-?(\d|[1-9]\d+|\d+\.\d*|\d*\.\d+)([eE][+-]?\d+)?$/)) {
90 232         468 $k = int($k);
91             } else {
92 144         265 $k = $k0;
93             }
94 378         761 return $k;
95             }
96              
97             sub set {
98 432     432 1 2607 my ($self, $k, $v) = @_;
99              
100             # without key use the increment of the largest previously used int key
101             #
102 432 100       860 if (defined $k) {
103 179         417 $k = $self->get_index($k);
104 179 100       397 if (is_int_index($k)) {
105 93 100 100     373 if (!defined $self->{idx} || ($k >= $self->{idx})) {
106 59         127 $self->{idx} = $k+1;
107             }
108             } else {
109 86         182 $self->{non_numeric} = 1;
110             }
111 179 100 100     581 if (!exists $self->{map} || !exists $self->{ordered}) {
112 130         255 $self->{map} = $self->_ordered_map();
113 130         271 $self->{ordered} = 1;
114             }
115             } else {
116             # use faster unordered map as long as no explicit key is used.
117             #
118 253 100       686 $self->{map} = {} unless exists $self->{map};
119 253 100       633 $self->{idx} = 0 unless defined $self->{idx};
120 253         380 $k = $self->{idx};
121 253         396 $self->{idx} += 1;
122             }
123 432 50 66     1533 if (defined $self->{strmap} && (ref($v) eq ref($self))) {
124 0         0 $self->{map}{$k} = $v->{name};
125             } else {
126 432         1555 $self->{map}{$k} = $v;
127             }
128             #printf ">> setarr: %s{%s} = %s\n", $self->{name}, $k, $v if $opt{v};
129 432         3532 return $self;
130             }
131              
132             sub get {
133 219     219 1 992 my ($self, $k) = @_;
134              
135 219 100       499 if (exists $self->{map}) {
136 197         481 $k = $self->get_index($k);
137              
138 197 100       670 if (exists $self->{map}{$k}) {
139 161         738 return $self->{map}{$k};
140             }
141             }
142 58         177 return;
143             }
144              
145             sub copy {
146 63     63 1 670 my ($self, $keys) = @_;
147              
148             # TODO: #arr$x.$y sub-name here?
149             #
150 63         186 my $c = PHP::Decode::Array->new(strmap => $self->{strmap});
151              
152 63 100       193 if (exists $self->{map}) {
153 51 100 100     172 if (exists $self->{ordered} || defined $keys) {
154 29         85 $c->{map} = $c->_ordered_map();
155 29         84 $c->{ordered} = 1;
156             } else {
157 22         81 $c->{map} = {};
158             }
159 51 100       122 unless (defined $keys) {
160 50         76 $keys = [keys %{$self->{map}}]; # default: all keys
  50         157  
161             }
162 51         636 foreach my $k (@$keys) {
163 87         614 my $v0 = $self->{map}{$k};
164 87         432 my $v = $v0;
165              
166 87 50 66     431 if (defined $v && defined $self->{strmap} && exists $self->{strmap}{$v}) {
      66        
167 82         158 $v = $self->{strmap}{$v};
168             }
169 87 100 66     313 if (defined $v && (ref($v) eq ref($self))) {
170 5         38 my $subarray = $v->copy();
171 5 100       42 if (defined $self->{strmap}) {
172 4         17 $c->{map}{$k} = $subarray->{name};
173             } else {
174 1         5 $c->{map}{$k} = $subarray;
175             }
176             } else {
177 82         254 $c->{map}{$k} = $v0;
178             }
179             }
180 51         534 $c->{idx} = $self->{idx};
181 51         124 $c->{pos} = $self->{pos};
182 51 100       134 $c->{non_mumeric} = 1 if exists $self->{non_numeric};
183             }
184 63         169 return $c;
185             }
186              
187             sub delete {
188 2     2 1 6 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       6 unless (exists $self->{ordered}) {
194 2         6 $self->{map} = $self->_ordered_map();
195 2         5 $self->{ordered} = 1;
196             }
197 2         10 return delete $self->{map}{$k};
198             }
199 0         0 return;
200             }
201              
202             sub val {
203 1358     1358 1 2334 my ($self, $k) = @_;
204 1358 50       2914 exists $self->{map}{$k} || die "assert: bad key $k passed to array->val()";
205 1358         3858 return $self->{map}{$k}; # for get_keys lookup
206             }
207              
208             sub get_keys {
209 1204     1204 1 1967 my ($self) = @_;
210              
211 1204 100       2590 if (exists $self->{map}) {
212 1111         1523 my @keys;
213 1111 100       2146 if (exists $self->{ordered}) {
214             # insertion order is preserved by Tie::IxHash
215             #
216 246         316 @keys = keys %{$self->{map}};
  246         1039  
217             } else {
218 865         1147 @keys = sort { $a <=> $b } keys %{$self->{map}};
  752         2887  
  865         3806  
219             }
220 1111         7713 return \@keys;
221             }
222 93         232 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         26 return $self->{pos};
246             }
247              
248             sub set_pos {
249 10     10 1 24 my ($self, $pos) = @_;
250              
251 10         18 $self->{pos} = $pos;
252 10         18 return;
253             }
254              
255             sub is_numerical {
256 6     6 0 12 my ($self) = @_;
257              
258 6 50       19 if (exists $self->{non_numeric}) {
259 0         0 return 0;
260             }
261 6         12 return 1;
262             }
263              
264             sub empty {
265 21     21 1 41 my ($self) = @_;
266              
267 21         33 return (keys %{$self->{map}} == 0);
  21         88  
268             }
269              
270             sub to_str {
271 8     8 1 629 my ($self) = @_;
272 8         16 my $keys = $self->get_keys();
273 8         14 my $str = '(';
274              
275 8         14 foreach my $k (@$keys) {
276 16         41 my $v = $self->{map}{$k};
277              
278 16 0 33     108 if (defined $v && defined $self->{strmap} && exists $self->{strmap}{$v}) {
      33        
279 0         0 $v = $self->{strmap}{$v};
280             }
281 16 100       31 $str .= ', ' if ($str ne '(');
282 16 100 66     44 if (defined $v && (ref($v) eq ref($self))) {
283 2         16 $str .= $v->to_str();
284             } else {
285 14 100       23 if (is_int_index($k)) {
286 11         32 $str .= "$k => '$v'";
287             } else {
288 3         9 $str .= "'$k' => '$v'";
289             }
290             }
291             }
292 8         11 $str .= ')';
293 8         22 return $str;
294             }
295              
296             1;
297              
298             __END__