Issues (2882)

src/ORM/ArrayLib.php (4 issues)

1
<?php
2
3
namespace SilverStripe\ORM;
4
5
use Generator;
6
7
/**
8
 * Library of static methods for manipulating arrays.
9
 */
10
class ArrayLib
11
{
12
13
    /**
14
     * Inverses the first and second level keys of an associative
15
     * array, keying the result by the second level, and combines
16
     * all first level entries within them.
17
     *
18
     * Before:
19
     * <example>
20
     * array(
21
     *    'row1' => array(
22
     *        'col1' =>'val1',
23
     *        'col2' => 'val2'
24
     *    ),
25
     *    'row2' => array(
26
     *        'col1' => 'val3',
27
     *        'col2' => 'val4'
28
     *    )
29
     * )
30
     * </example>
31
     *
32
     * After:
33
     * <example>
34
     * array(
35
     *    'col1' => array(
36
     *        'row1' => 'val1',
37
     *        'row2' => 'val3',
38
     *    ),
39
     *    'col2' => array(
40
     *        'row1' => 'val2',
41
     *        'row2' => 'val4',
42
     *    ),
43
     * )
44
     * </example>
45
     *
46
     * @param array $arr
47
     * @return array
48
     */
49
    public static function invert($arr)
50
    {
51
        if (!$arr) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $arr of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
52
            return [];
53
        }
54
55
        $result = array();
56
57
        foreach ($arr as $columnName => $column) {
58
            foreach ($column as $rowName => $cell) {
59
                $result[$rowName][$columnName] = $cell;
60
            }
61
        }
62
63
        return $result;
64
    }
65
66
    /**
67
     * Return an array where the keys are all equal to the values.
68
     *
69
     * @param $arr array
70
     * @return array
71
     */
72
    public static function valuekey($arr)
73
    {
74
        return array_combine($arr, $arr);
75
    }
76
77
    /**
78
     * Flattens a multi-dimensional array to a one level array without preserving the keys
79
     *
80
     * @param array $array
81
     * @return array
82
     */
83
    public static function array_values_recursive($array)
84
    {
85
        return self::flatten($array, false);
86
    }
87
88
    /**
89
     * Filter an array by keys (useful for only allowing certain form-input to
90
     * be saved).
91
     *
92
     * @param $arr array
93
     * @param $keys array
94
     *
95
     * @return array
96
     */
97
    public static function filter_keys($arr, $keys)
98
    {
99
        foreach ($arr as $key => $v) {
100
            if (!in_array($key, $keys)) {
101
                unset($arr[$key]);
102
            }
103
        }
104
105
        return $arr;
106
    }
107
108
    /**
109
     * Determines if an array is associative by checking for existing keys via
110
     * array_key_exists().
111
     *
112
     * @see http://nz.php.net/manual/en/function.is-array.php#121692
113
     *
114
     * @param array $array
115
     *
116
     * @return boolean
117
     */
118
    public static function is_associative($array)
119
    {
120
        $isAssociative = !empty($array)
121
            && is_array($array)
122
            && ($array !== array_values($array));
123
124
        return $isAssociative;
125
    }
126
127
    /**
128
     * Recursively searches an array $haystack for the value(s) $needle.
129
     *
130
     * Assumes that all values in $needle (if $needle is an array) are at
131
     * the SAME level, not spread across multiple dimensions of the $haystack.
132
     *
133
     * @param mixed $needle
134
     * @param array $haystack
135
     * @param boolean $strict
136
     *
137
     * @return boolean
138
     */
139
    public static function in_array_recursive($needle, $haystack, $strict = false)
140
    {
141
        if (!is_array($haystack)) {
0 ignored issues
show
The condition is_array($haystack) is always true.
Loading history...
142
            return false;
143
        }
144
145
        if (in_array($needle, $haystack, $strict)) {
146
            return true;
147
        } else {
148
            foreach ($haystack as $obj) {
149
                if (self::in_array_recursive($needle, $obj, $strict)) {
150
                    return true;
151
                }
152
            }
153
        }
154
155
        return false;
156
    }
157
158
    /**
159
     * Similar to array_map, but recurses when arrays are encountered.
160
     *
161
     * Actually only one array argument is supported.
162
     *
163
     * @param $f callback to apply
164
     * @param $array array
165
     * @return array
166
     */
167
    public static function array_map_recursive($f, $array)
168
    {
169
        $applyOrRecurse = function ($v) use ($f) {
170
            return is_array($v) ? ArrayLib::array_map_recursive($f, $v) : call_user_func($f, $v);
171
        };
172
173
        return array_map($applyOrRecurse, $array);
174
    }
175
176
    /**
177
     * Recursively merges two or more arrays.
178
     *
179
     * Behaves similar to array_merge_recursive(), however it only merges
180
     * values when both are arrays rather than creating a new array with
181
     * both values, as the PHP version does. The same behaviour also occurs
182
     * with numeric keys, to match that of what PHP does to generate $_REQUEST.
183
     *
184
     * @param array $array
185
     *
186
     * @return array
187
     */
188
    public static function array_merge_recursive($array)
189
    {
190
        $arrays = func_get_args();
191
        $merged = array();
192
193
        if (count($arrays) == 1) {
194
            return $array;
195
        }
196
197
        while ($arrays) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $arrays of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
198
            $array = array_shift($arrays);
199
200
            if (!is_array($array)) {
201
                trigger_error(
202
                    'SilverStripe\ORM\ArrayLib::array_merge_recursive() encountered a non array argument',
203
                    E_USER_WARNING
204
                );
205
                return [];
206
            }
207
208
            if (!$array) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $array of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
209
                continue;
210
            }
211
212
            foreach ($array as $key => $value) {
213
                if (is_array($value) && array_key_exists($key, $merged) && is_array($merged[$key])) {
214
                    $merged[$key] = ArrayLib::array_merge_recursive($merged[$key], $value);
215
                } else {
216
                    $merged[$key] = $value;
217
                }
218
            }
219
        }
220
221
        return $merged;
222
    }
223
224
    /**
225
     * Takes an multi dimension array and returns the flattened version.
226
     *
227
     * @param array $array
228
     * @param boolean $preserveKeys
229
     * @param array $out
230
     *
231
     * @return array
232
     */
233
    public static function flatten($array, $preserveKeys = true, &$out = array())
234
    {
235
        array_walk_recursive(
236
            $array,
237
            function ($value, $key) use (&$out, $preserveKeys) {
238
                if (!is_scalar($value)) {
239
                    // Do nothing
240
                } elseif ($preserveKeys) {
241
                    $out[$key] = $value;
242
                } else {
243
                    $out[] = $value;
244
                }
245
            }
246
        );
247
248
        return $out;
249
    }
250
251
    /**
252
     * Iterate list, but allowing for modifications to the underlying list.
253
     * Items in $list will only be iterated exactly once for each key, and supports
254
     * items being removed or deleted.
255
     * List must be associative.
256
     *
257
     * @param array $list
258
     * @return Generator
259
     */
260
    public static function iterateVolatile(array &$list)
261
    {
262
        // Keyed by already-iterated items
263
        $iterated = [];
264
        // Get all items not yet iterated
265
        while ($items = array_diff_key($list, $iterated)) {
266
            // Yield all results
267
            foreach ($items as $key => $value) {
268
                // Skip items removed by a prior step
269
                if (array_key_exists($key, $list)) {
270
                    // Ensure we yield from the source list
271
                    $iterated[$key] = true;
272
                    yield $key => $list[$key];
273
                }
274
            }
275
        }
276
    }
277
}
278