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