Passed
Push — master ( c3477c...614fe6 )
by Sergei
03:35
created

ArArrayHelper::getValueByPath()   B

Complexity

Conditions 9
Paths 9

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 15
c 1
b 0
f 0
dl 0
loc 27
rs 8.0555
cc 9
nc 9
nop 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\ActiveRecord;
6
7
use Closure;
8
9
use function array_combine;
10
use function array_key_exists;
11
use function array_map;
12
use function get_object_vars;
13
use function property_exists;
14
use function strrpos;
15
use function substr;
16
17
/**
18
 * Array manipulation methods for ActiveRecord.
19
 *
20
 * @psalm-type Row = ActiveRecordInterface|array
21
 * @psalm-type IndexKey = string|Closure(Row, mixed=):mixed
22
 */
23
final class ArArrayHelper
24
{
25
    /**
26
     * Returns the values of a specified column in an array.
27
     *
28
     * The input array should be multidimensional or an array of {@see ActiveRecordInterface} instances.
29
     *
30
     * For example,
31
     *
32
     * ```php
33
     * $array = [
34
     *     ['id' => '123', 'data' => 'abc'],
35
     *     ['id' => '345', 'data' => 'def'],
36
     * ];
37
     * $result = ArArrayHelper::getColumn($array, 'id');
38
     * // the result is: ['123', '345']
39
     * ```
40
     *
41
     * @param array $array Array to extract values from.
42
     * @param string $name The column name.
43
     *
44
     * @psalm-param Row[] $array
45
     *
46
     * @return array The list of column values.
47
     */
48
    public static function getColumn(array $array, string $name): array
49
    {
50
        return array_map(
51
            static fn (ActiveRecordInterface|array $element): mixed => self::getValueByPath($element, $name),
52
            $array
53
        );
54
    }
55
56
    /**
57
     * Retrieves a value from the array by the given key or from the {@see ActiveRecordInterface} instance
58
     * by the given property or relation name.
59
     *
60
     * If the key doesn't exist, the default value will be returned instead.
61
     *
62
     * The key may be specified in a dot format to retrieve the value of a sub-array or a property or relation of the
63
     * {@see ActiveRecordInterface} instance.
64
     *
65
     * In particular, if the key is `x.y.z`, then the returned value would be `$array['x']['y']['z']` or
66
     * `$array->x->y->z` (if `$array` is an {@see ActiveRecordInterface} instance).
67
     *
68
     * Note that if the array already has an element `x.y.z`, then its value will be returned instead of going through
69
     * the sub-arrays.
70
     *
71
     * Below are some usage examples.
72
     *
73
     * ```php
74
     * // working with an array
75
     * $username = ArArrayHelper::getValueByPath($array, 'username');
76
     * // working with an {@see ActiveRecordInterface} instance
77
     * $username = ArArrayHelper::getValueByPath($user, 'username');
78
     * // using dot format to retrieve the property of an {@see ActiveRecordInterface} instance
79
     * $street = ArArrayHelper::getValue($users, 'address.street');
80
     * ```
81
     *
82
     * @param ActiveRecordInterface|array $array Array or an {@see ActiveRecordInterface} instance to extract value from.
83
     * @param string $key Key name of the array element or a property or relation name
84
     * of the {@see ActiveRecordInterface} instance.
85
     * @param mixed|null $default The default value to be returned if the specified `$key` doesn't exist.
86
     *
87
     * @psalm-param Row $array
88
     *
89
     * @return mixed The value of the element if found, default value otherwise
90
     */
91
    public static function getValueByPath(ActiveRecordInterface|array $array, string $key, mixed $default = null): mixed
92
    {
93
        if ($array instanceof ActiveRecordInterface) {
0 ignored issues
show
introduced by
$array is never a sub-type of Yiisoft\ActiveRecord\ActiveRecordInterface.
Loading history...
94
            if ($array->hasAttribute($key)) {
95
                return $array->getAttribute($key);
96
            }
97
98
            if (property_exists($array, $key)) {
99
                $values = get_object_vars($array);
100
                return array_key_exists($key, $values) ? $values[$key] : $default;
101
            }
102
103
            if ($array->isRelationPopulated($key)) {
104
                return $array->relation($key);
105
            }
106
        } elseif (array_key_exists($key, $array)) {
107
            return $array[$key];
108
        }
109
110
        if (!empty($key) && ($pos = strrpos($key, '.')) !== false) {
111
            $array = self::getValueByPath($array, substr($key, 0, $pos), $default);
112
            $key = substr($key, $pos + 1);
113
114
            return self::getValueByPath($array, $key, $default);
115
        }
116
117
        return $default;
118
    }
119
120
    /**
121
     * Populates an array of rows with the specified column value as keys.
122
     *
123
     * The input array should be multidimensional or an array of {@see ActiveRecordInterface} instances.
124
     *
125
     * For example,
126
     *
127
     * ```php
128
     * $rows = [
129
     *     ['id' => '123', 'data' => 'abc'],
130
     *     ['id' => '345', 'data' => 'def'],
131
     * ];
132
     * $result = ArArrayHelper::populate($rows, 'id');
133
     * // the result is: ['123' => ['id' => '123', 'data' => 'abc'], '345' => ['id' => '345', 'data' => 'def']]
134
     * ```
135
     *
136
     * @param array[] $rows Array to populate.
137
     * @param Closure|string|null $indexBy The column name or anonymous function that specifies the index by which to
138
     * populate the array of rows.
139
     *
140
     * @psalm-template TRow of Row
141
     * @psalm-param array<TRow> $rows
142
     * @psalm-param IndexKey|null $indexBy
143
     * @psalm-return array<TRow>
144
     *
145
     * @return array[]
146
     */
147
    public static function populate(array $rows, Closure|string|null $indexBy = null): array
148
    {
149
        if ($indexBy === null) {
150
            return $rows;
151
        }
152
153
        if ($indexBy instanceof Closure) {
154
            return array_combine(array_map($indexBy, $rows), $rows);
155
        }
156
157
        $result = [];
158
159
        foreach ($rows as $row) {
160
            /** @psalm-suppress MixedArrayOffset */
161
            $result[self::getValueByPath($row, $indexBy)] = $row;
162
        }
163
164
        return $result;
165
    }
166
}
167