Passed
Pull Request — master (#19434)
by Fedonyuk
13:15 queued 05:05
created

ArrayableTrait::toArray()   B

Complexity

Conditions 11
Paths 44

Size

Total Lines 36
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 11.0699

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 11
eloc 23
c 2
b 1
f 0
nc 44
nop 3
dl 0
loc 36
ccs 22
cts 24
cp 0.9167
crap 11.0699
rs 7.3166

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @link https://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license https://www.yiiframework.com/license/
6
 */
7
8
namespace yii\base;
9
10
use JsonSerializable;
11
use Yii;
12
use yii\helpers\ArrayHelper;
13
use yii\web\Link;
14
use yii\web\Linkable;
15
16
/**
17
 * ArrayableTrait provides a common implementation of the [[Arrayable]] interface.
18
 *
19
 * ArrayableTrait implements [[toArray()]] by respecting the field definitions as declared
20
 * in [[fields()]] and [[extraFields()]].
21
 *
22
 * @author Qiang Xue <[email protected]>
23
 * @since 2.0
24
 */
25
trait ArrayableTrait
26
{
27
    /**
28
     * Returns the list of fields that should be returned by default by [[toArray()]] when no specific fields are specified.
29
     *
30
     * A field is a named element in the returned array by [[toArray()]].
31
     *
32
     * This method should return an array of field names or field definitions.
33
     * If the former, the field name will be treated as an object property name whose value will be used
34
     * as the field value. If the latter, the array key should be the field name while the array value should be
35
     * the corresponding field definition which can be either an object property name or a PHP callable
36
     * returning the corresponding field value. The signature of the callable should be:
37
     *
38
     * ```php
39
     * function ($model, $field) {
40
     *     // return field value
41
     * }
42
     * ```
43
     *
44
     * For example, the following code declares four fields:
45
     *
46
     * - `email`: the field name is the same as the property name `email`;
47
     * - `firstName` and `lastName`: the field names are `firstName` and `lastName`, and their
48
     *   values are obtained from the `first_name` and `last_name` properties;
49
     * - `fullName`: the field name is `fullName`. Its value is obtained by concatenating `first_name`
50
     *   and `last_name`.
51
     *
52
     * ```php
53
     * return [
54
     *     'email',
55
     *     'firstName' => 'first_name',
56
     *     'lastName' => 'last_name',
57
     *     'fullName' => function () {
58
     *         return $this->first_name . ' ' . $this->last_name;
59
     *     },
60
     * ];
61
     * ```
62
     *
63
     * In this method, you may also want to return different lists of fields based on some context
64
     * information. For example, depending on the privilege of the current application user,
65
     * you may return different sets of visible fields or filter out some fields.
66
     *
67
     * The default implementation of this method returns the public object member variables indexed by themselves.
68
     *
69
     * @return array the list of field names or field definitions.
70
     * @see toArray()
71
     */
72 1
    public function fields()
73
    {
74 1
        $fields = Yii::getObjectVars($this);
75 1
        if ($fields !== []) {
76 1
            $fields = array_keys($fields);
77 1
            $fields = array_combine($fields, $fields);
78
        }
79
80 1
        return $fields;
81
    }
82
83
    /**
84
     * Returns the list of fields that can be expanded further and returned by [[toArray()]].
85
     *
86
     * This method is similar to [[fields()]] except that the list of fields returned
87
     * by this method are not returned by default by [[toArray()]]. Only when field names
88
     * to be expanded are explicitly specified when calling [[toArray()]], will their values
89
     * be exported.
90
     *
91
     * The default implementation returns an empty array.
92
     *
93
     * You may override this method to return a list of expandable fields based on some context information
94
     * (e.g. the current application user).
95
     *
96
     * @return array the list of expandable field names or field definitions. Please refer
97
     * to [[fields()]] on the format of the return value.
98
     * @see toArray()
99
     * @see fields()
100
     */
101
    public function extraFields()
102
    {
103
        return [];
104
    }
105
106
    /**
107
     * Converts the model into an array.
108
     *
109
     * This method will first identify which fields to be included in the resulting array by calling [[resolveFields()]].
110
     * It will then turn the model into an array with these fields. If `$recursive` is true,
111
     * any embedded objects will also be converted into arrays.
112
     * When embedded objects are [[Arrayable]], their respective nested fields will be extracted and passed to [[toArray()]].
113
     *
114
     * If the model implements the [[Linkable]] interface, the resulting array will also have a `_link` element
115
     * which refers to a list of links as specified by the interface.
116
     *
117
     * @param array $fields the fields being requested.
118
     * If empty or if it contains '*', all fields as specified by [[fields()]] will be returned.
119
     * Fields can be nested, separated with dots (.). e.g.: item.field.sub-field
120
     * `$recursive` must be true for nested fields to be extracted. If `$recursive` is false, only the root fields will be extracted.
121
     * @param array $expand the additional fields being requested for exporting. Only fields declared in [[extraFields()]]
122
     * will be considered.
123
     * Expand can also be nested, separated with dots (.). e.g.: item.expand1.expand2
124
     * `$recursive` must be true for nested expands to be extracted. If `$recursive` is false, only the root expands will be extracted.
125
     * @param bool $recursive whether to recursively return array representation of embedded objects.
126
     * @return array the array representation of the object
127
     */
128 14
    public function toArray(array $fields = [], array $expand = [], $recursive = true)
129
    {
130 14
        $data = [];
131 14
        foreach ($this->resolveFields($fields, $expand) as $field => $definition) {
132 14
            $attribute = is_string($definition) ? $this->$definition : $definition($this, $field);
133
134 14
            if ($recursive) {
135 14
                $nestedFields = $this->extractFieldsFor($fields, $field);
136 14
                $nestedExpand = $this->extractFieldsFor($expand, $field);
137 14
                if ($attribute instanceof Arrayable) {
138 3
                    $attribute = $attribute->toArray($nestedFields, $nestedExpand);
139 14
                } elseif ($attribute instanceof JsonSerializable) {
140 1
                    $attribute = $attribute->jsonSerialize();
141 14
                } elseif (is_array($attribute)) {
142 2
                    $attribute = array_map(
143 2
                        static function ($item) use ($nestedFields, $nestedExpand) {
144 2
                            if ($item instanceof Arrayable) {
145 2
                                return $item->toArray($nestedFields, $nestedExpand);
146
                            }
147 1
                            if ($item instanceof JsonSerializable) {
148 1
                                return $item->jsonSerialize();
149
                            }
150
                            return $item;
151 2
                        },
152
                        $attribute
153
                    );
154
                }
155
            }
156 14
            $data[$field] = $attribute;
157
        }
158
159 14
        if ($this instanceof Linkable) {
160
            $data['_links'] = Link::serialize($this->getLinks());
161
        }
162
163 14
        return $recursive ? ArrayHelper::toArray($data) : $data;
164
    }
165
166
    /**
167
     * Extracts the root field names from nested fields.
168
     * Nested fields are separated with dots (`.`). e.g: "item.id"
169
     * The previous example would extract "item".
170
     *
171
     * @param array $fields The fields requested for extraction
172
     * @return array root fields extracted from the given nested fields
173
     * @since 2.0.14
174
     */
175 14
    protected function extractRootFields(array $fields)
176
    {
177 14
        $result = [];
178
179 14
        if (!in_array('*', $fields, true)) {
180 14
            foreach ($fields as $field) {
181 3
                list($rootField) = explode('.', $field, 2);
182 3
                if ($rootField === '*') {
183
                    $result = [];
184
                    break;
185
                }
186 3
                $result[] = $rootField;
187
            }
188
        }
189
190 14
        if ($result !== []) {
191 3
            $result = array_unique($result);
192
        }
193
194 14
        return $result;
195
    }
196
197
    /**
198
     * Extract nested fields from a fields collection for a given root field
199
     * Nested fields are separated with dots (`.`). e.g: "item.id"
200
     * The previous example would extract "id".
201
     *
202
     * @param array $fields The fields requested for extraction
203
     * @param string $rootField The root field for which we want to extract the nested fields
204
     * @return array nested fields extracted for the given field
205
     * @since 2.0.14
206
     */
207 14
    protected function extractFieldsFor(array $fields, $rootField)
208
    {
209 14
        $result = [];
210
211 14
        $rootField .= '.';
212 14
        $rootFieldLength = strlen($rootField);
213 14
        foreach ($fields as $field) {
214 3
            if (0 === strpos($field, $rootField)) {
215 2
                $result[] = substr($field, $rootFieldLength);
216
            }
217
        }
218
219 14
        if ($result !== []) {
220 2
            $result = array_unique($result);
221
        }
222
223 14
        return $result;
224
    }
225
226
    /**
227
     * Determines which fields can be returned by [[toArray()]].
228
     * This method will first extract the root fields from the given fields.
229
     * Then it will check the requested root fields against those declared in [[fields()]] and [[extraFields()]]
230
     * to determine which fields can be returned.
231
     * @param array $fields the fields being requested for exporting
232
     * @param array $expand the additional fields being requested for exporting
233
     * @return array the list of fields to be exported. The array keys are the field names, and the array values
234
     * are the corresponding object property names or PHP callables returning the field values.
235
     */
236 14
    protected function resolveFields(array $fields, array $expand)
237
    {
238 14
        $fields = $this->extractRootFields($fields);
239 14
        $expand = $this->extractRootFields($expand);
240 14
        $result = [];
241
242 14
        foreach ($this->fields() as $field => $definition) {
243 14
            if (is_int($field)) {
244 7
                $field = $definition;
245
            }
246 14
            if (empty($fields) || in_array($field, $fields, true)) {
247 14
                $result[$field] = $definition;
248
            }
249
        }
250
251 14
        if (empty($expand)) {
252 13
            return $result;
253
        }
254
255 3
        foreach ($this->extraFields() as $field => $definition) {
256 3
            if (is_int($field)) {
257 3
                $field = $definition;
258
            }
259 3
            if (in_array($field, $expand, true)) {
260 3
                $result[$field] = $definition;
261
            }
262
        }
263
264 3
        return $result;
265
    }
266
}
267