Passed
Pull Request — master (#334)
by Sergei
02:46
created

ArArrayHelper::getColumn()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 3
c 2
b 0
f 0
dl 0
loc 5
rs 10
cc 1
nc 1
nop 2
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
                return get_object_vars($array)[$key] ?? $default;
100
            }
101
102
            if ($array->isRelationPopulated($key)) {
103
                return $array->relation($key);
104
            }
105
        } elseif (array_key_exists($key, $array)) {
106
            return $array[$key];
107
        }
108
109
        if (!empty($key) && ($pos = strrpos($key, '.')) !== false) {
110
            $array = self::getValueByPath($array, substr($key, 0, $pos), $default);
111
            $key = substr($key, $pos + 1);
112
113
            return self::getValueByPath($array, $key, $default);
114
        }
115
116
        return $default;
117
    }
118
119
    /**
120
     * Returns the value of an array element or {@see ActiveRecordInterface} instance property by the given path.
121
     *
122
     * This method is internally used to populate the data fetched from a database using `$indexBy` parameter.
123
     *
124
     * @param array[] $rows The raw query result from a database.
125
     *
126
     * @psalm-template TRow of Row
127
     * @psalm-param array<TRow> $rows
128
     * @psalm-param IndexKey|null $indexBy
129
     * @psalm-return array<TRow>
130
     *
131
     * @return array[]
132
     */
133
    public static function populate(array $rows, Closure|string|null $indexBy = null): array
134
    {
135
        if ($indexBy === null) {
136
            return $rows;
137
        }
138
139
        if ($indexBy instanceof Closure) {
140
            return array_combine(array_map($indexBy, $rows), $rows);
141
        }
142
143
        $result = [];
144
145
        foreach ($rows as $row) {
146
            /** @psalm-suppress MixedArrayOffset */
147
            $result[self::getValueByPath($row, $indexBy)] = $row;
148
        }
149
150
        return $result;
151
    }
152
}
153