Passed
Pull Request — master (#334)
by Sergei
03:11
created

ArArrayHelper::getValueByPath()   B

Complexity

Conditions 8
Paths 8

Size

Total Lines 26
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 14
c 1
b 0
f 0
dl 0
loc 26
rs 8.4444
cc 8
nc 8
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 PathKey = 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
     * // working with an anonymous function
79
     * $fullName = ArArrayHelper::getValueByPath($user, function ($user, $defaultValue) {
80
     *     return $user->firstName . ' ' . $user->lastName;
81
     * });
82
     * // using dot format to retrieve the property of an {@see ActiveRecordInterface} instance
83
     * $street = ArArrayHelper::getValue($users, 'address.street');
84
     * ```
85
     *
86
     * @param ActiveRecordInterface|array $array Array or an {@see ActiveRecordInterface} instance to extract value from.
87
     * @param string $key Key name of the array element or a property or relation name
88
     * of the {@see ActiveRecordInterface} instance.
89
     * @param mixed|null $default The default value to be returned if the specified `$key` doesn't exist.
90
     *
91
     * @psalm-param Row $array
92
     *
93
     * @return mixed The value of the element if found, default value otherwise
94
     */
95
    public static function getValueByPath(ActiveRecordInterface|array $array, string $key, mixed $default = null): mixed
96
    {
97
        if ($array instanceof ActiveRecordInterface) {
0 ignored issues
show
introduced by
$array is never a sub-type of Yiisoft\ActiveRecord\ActiveRecordInterface.
Loading history...
98
            if ($array->hasAttribute($key)) {
99
                return $array->getAttribute($key);
100
            }
101
102
            if (property_exists($array, $key)) {
103
                return get_object_vars($array)[$key] ?? $default;
104
            }
105
106
            if ($array->isRelationPopulated($key)) {
107
                return $array->relation($key);
108
            }
109
        } elseif (array_key_exists($key, $array)) {
110
            return $array[$key];
111
        }
112
113
        if (!empty($key) && ($pos = strrpos($key, '.')) !== false) {
114
            $array = self::getValueByPath($array, substr($key, 0, $pos), $default);
115
            $key = substr($key, $pos + 1);
116
117
            return self::getValueByPath($array, $key, $default);
118
        }
119
120
        return $default;
121
    }
122
123
    /**
124
     * Returns the value of an array element or {@see ActiveRecordInterface} instance property by the given path.
125
     *
126
     * This method is internally used to populate the data fetched from a database using `$indexBy` parameter.
127
     *
128
     * @param array[] $rows The raw query result from a database.
129
     *
130
     * @psalm-template TRow of Row
131
     * @psalm-param array<TRow> $rows
132
     * @psalm-param PathKey|null $indexBy
133
     * @psalm-return array<TRow>
134
     *
135
     * @return array[]
136
     */
137
    public static function populate(array $rows, Closure|string|null $indexBy = null): array
138
    {
139
        if ($indexBy === null) {
140
            return $rows;
141
        }
142
143
        if ($indexBy instanceof Closure) {
144
            return array_combine(array_map($indexBy, $rows), $rows);
145
        }
146
147
        $result = [];
148
149
        foreach ($rows as $row) {
150
            /** @psalm-suppress MixedArrayOffset */
151
            $result[self::getValueByPath($row, $indexBy)] = $row;
152
        }
153
154
        return $result;
155
    }
156
}
157