Passed
Pull Request — v3 (#729)
by
unknown
32:47
created

Arr   A

Complexity

Total Complexity 12

Size/Duplication

Total Lines 136
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 12
eloc 36
c 1
b 0
f 0
dl 0
loc 136
rs 10

4 Methods

Rating   Name   Duplication   Size   Complexity  
A set() 0 23 3
A hashes() 0 12 2
A handleArrayIteratorCopyOrReference() 0 27 5
A recursively() 0 31 2
1
<?php
2
/**
3
 * This file is part of GameQ.
4
 *
5
 * GameQ is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU Lesser General Public License as published by
7
 * the Free Software Foundation; either version 3 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * GameQ is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU Lesser General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU Lesser General Public License
16
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
 */
18
19
namespace GameQ\Helpers;
20
21
use Closure;
22
use RecursiveArrayIterator;
23
use RecursiveIteratorIterator;
24
25
/**
26
 * This helper contains functions to work with arrays.
27
 *
28
 * @package GameQ\Helpers
29
 */
30
class Arr
31
{
32
    /**
33
     * This helper does process each element of the provided array recursively.
34
     * It does so allowing for modifications to the provided array and without
35
     * using actual recursive calls.
36
     *
37
     * @param array $data
38
     * @param Closure $callback
39
     *
40
     * @return array
41
     */
42
    public static function recursively(array $data, Closure $callback)
43
    {
44
        /* Initialize the RecursiveArrayIterator for the provided data */
45
        $arrayIterator = new RecursiveArrayIterator($data);
46
47
        /* Configure the Iterator for the RecursiveIterator */
48
        $recursiveIterator = new RecursiveIteratorIterator($arrayIterator);
49
50
        /* Traverse the provided data */
51
        foreach ($recursiveIterator as $key => $value) {
52
            /* Get the current sub iterator with Type hinting */
53
            /** @var RecursiveArrayIterator */
54
            $subIterator = $recursiveIterator->getSubIterator();
55
56
            /* Wrap the implementation to handle PHP < 8.1 behaviour */
57
            static::handleArrayIteratorCopyOrReference(
58
                $data,
59
                $recursiveIterator, 
60
                $subIterator, 
61
                /* Update the current value */
62
                fn () => $subIterator->offsetSet(
0 ignored issues
show
Bug introduced by
The method offsetSet() does not exist on RecursiveIterator. It seems like you code against a sub-type of RecursiveIterator such as Phar or RecursiveCachingIterator or SimpleXMLElement or RecursiveArrayIterator or SimpleXMLIterator or Phar. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

62
                fn () => $subIterator->/** @scrutinizer ignore-call */ offsetSet(
Loading history...
63
                    /* Keep the original key */
64
                    $key,
65
                    /* Execute the callback and use the return / modified value */
66
                    $callback($value, $key, $subIterator) ?? $value
67
                )
68
            );
69
        }
70
71
        /* Return the processed data */
72
        return $data;
73
    }
74
75
    /**
76
     * This function is responsible for handling behaivour specific to PHP versions before 8.1.
77
     *
78
     * @param array &$data 
79
     * @param RecursiveIteratorIterator $recursiveIterator 
80
     * @param RecursiveArrayIterator $iterator 
81
     * @param Closure $callback 
82
     * @return void 
83
     */
84
    protected static function handleArrayIteratorCopyOrReference(array &$data, RecursiveIteratorIterator $recursiveIterator, RecursiveArrayIterator $iterator, Closure $callback)
85
    {
86
        /* ArrayIterator before PHP 8.1 does use a copy instead of reference */
87
        if (PHP_VERSION_ID < 80100) {
88
            /* Hash the current state of the iterator */
89
            $hashes = static::hashes((array) $iterator);
90
91
            /* Continue with the provided callback */
92
            $callback();
93
94
            /* Determine if the current iterator has been modified */
95
            if (! empty($diff = array_diff_assoc(static::hashes((array) $iterator), $hashes))) {
96
                /* Determine path to the current iterator */
97
                $path = [];
98
                for ($depth = 0; $depth < $recursiveIterator->getDepth(); $depth++) {
99
                    $path[] = $recursiveIterator->getSubIterator($depth)->key();
100
                }
101
102
                /* Process all modified values */
103
                foreach (array_keys($diff) as $modified) {
104
                    /* Write the modified value to the original array */
105
                    $data = static::set($data, [...$path, $modified], $iterator->offsetGet($modified));
106
                }
107
            }
108
        } else {
109
            /* There is no need to write back any changes when ArrayIterator does use a reference */
110
            $callback();
111
        }
112
    }
113
114
    /**
115
     * This helper is intended to hash the provided array's values
116
     * and return it back as key => hash.
117
     *
118
     * @param array $array 
119
     * @return array<string|int, string> 
120
     */
121
    public static function hashes(array $array)
122
    {
123
        $hashes = [];
124
125
        /* Process the provided array */
126
        foreach ($array as $key => $value) {
127
            /* Serialze and hash each value individually */
128
            $hashes[$key] = md5(serialize($value));
129
        }
130
131
        /* Return array containing the hashes */
132
        return $hashes;
133
    }
134
135
    /**
136
     * This helper is intended to set a value inside the provided array.
137
     *
138
     * @param array &$array
139
     * @param array $path
140
     * @param mixed $value
141
     * @return array
142
     */
143
    public static function set(array &$array, array $path, $value)
144
    {
145
        $current = &$array;
146
147
        /* Process the path until the last element */
148
        foreach ($path as $i => $element) {
149
            /* Remove the element from the path */
150
            unset($path[$i]);
151
152
            /* Create missing key */
153
            if (! isset($current[$element])) {
154
                $current[$element] = [];
155
            }
156
157
            /* Set current to a reference of next */
158
            $current = &$current[$element];
159
        }
160
161
        /* Finally set the value using the last key */
162
        $current = $value;
163
164
        /* Return the current, modified array (level) */
165
        return $array;
166
    }
167
}
168