Completed
Push — master ( 55b06d...9f2a87 )
by Alexander
35:57
created

framework/base/ArrayableTrait.php (1 issue)

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 Yii;
11
use yii\helpers\ArrayHelper;
12
use yii\web\Link;
13
use yii\web\Linkable;
14
15
/**
16
 * ArrayableTrait provides a common implementation of the [[Arrayable]] interface.
17
 *
18
 * ArrayableTrait implements [[toArray()]] by respecting the field definitions as declared
19
 * in [[fields()]] and [[extraFields()]].
20
 *
21
 * @author Qiang Xue <[email protected]>
22
 * @since 2.0
23
 */
24
trait ArrayableTrait
25
{
26
    /**
27
     * Returns the list of fields that should be returned by default by [[toArray()]] when no specific fields are specified.
28
     *
29
     * A field is a named element in the returned array by [[toArray()]].
30
     *
31
     * This method should return an array of field names or field definitions.
32
     * If the former, the field name will be treated as an object property name whose value will be used
33
     * as the field value. If the latter, the array key should be the field name while the array value should be
34
     * the corresponding field definition which can be either an object property name or a PHP callable
35
     * returning the corresponding field value. The signature of the callable should be:
36
     *
37
     * ```php
38
     * function ($model, $field) {
39
     *     // return field value
40
     * }
41
     * ```
42
     *
43
     * For example, the following code declares four fields:
44
     *
45
     * - `email`: the field name is the same as the property name `email`;
46
     * - `firstName` and `lastName`: the field names are `firstName` and `lastName`, and their
47
     *   values are obtained from the `first_name` and `last_name` properties;
48
     * - `fullName`: the field name is `fullName`. Its value is obtained by concatenating `first_name`
49
     *   and `last_name`.
50
     *
51
     * ```php
52
     * return [
53
     *     'email',
54
     *     'firstName' => 'first_name',
55
     *     'lastName' => 'last_name',
56
     *     'fullName' => function () {
57
     *         return $this->first_name . ' ' . $this->last_name;
58
     *     },
59
     * ];
60
     * ```
61
     *
62
     * In this method, you may also want to return different lists of fields based on some context
63
     * information. For example, depending on the privilege of the current application user,
64
     * you may return different sets of visible fields or filter out some fields.
65
     *
66
     * The default implementation of this method returns the public object member variables indexed by themselves.
67
     *
68
     * @return array the list of field names or field definitions.
69
     * @see toArray()
70
     */
71 1
    public function fields()
72
    {
73 1
        $fields = array_keys(Yii::getObjectVars($this));
74 1
        return array_combine($fields, $fields);
0 ignored issues
show
Bug Best Practice introduced by
The expression return array_combine($fields, $fields) could also return false which is incompatible with the documented return type array. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
75
    }
76
77
    /**
78
     * Returns the list of fields that can be expanded further and returned by [[toArray()]].
79
     *
80
     * This method is similar to [[fields()]] except that the list of fields returned
81
     * by this method are not returned by default by [[toArray()]]. Only when field names
82
     * to be expanded are explicitly specified when calling [[toArray()]], will their values
83
     * be exported.
84
     *
85
     * The default implementation returns an empty array.
86
     *
87
     * You may override this method to return a list of expandable fields based on some context information
88
     * (e.g. the current application user).
89
     *
90
     * @return array the list of expandable field names or field definitions. Please refer
91
     * to [[fields()]] on the format of the return value.
92
     * @see toArray()
93
     * @see fields()
94
     */
95
    public function extraFields()
96
    {
97
        return [];
98
    }
99
100
    /**
101
     * Converts the model into an array.
102
     *
103
     * This method will first identify which fields to be included in the resulting array by calling [[resolveFields()]].
104
     * It will then turn the model into an array with these fields. If `$recursive` is true,
105
     * any embedded objects will also be converted into arrays.
106
     * When embeded objects are [[Arrayable]], their respective nested fields will be extracted and passed to [[toArray()]].
107
     *
108
     * If the model implements the [[Linkable]] interface, the resulting array will also have a `_link` element
109
     * which refers to a list of links as specified by the interface.
110
     *
111
     * @param array $fields the fields being requested.
112
     * If empty or if it contains '*', all fields as specified by [[fields()]] will be returned.
113
     * Fields can be nested, separated with dots (.). e.g.: item.field.sub-field
114
     * `$recursive` must be true for nested fields to be extracted. If `$recursive` is false, only the root fields will be extracted.
115
     * @param array $expand the additional fields being requested for exporting. Only fields declared in [[extraFields()]]
116
     * will be considered.
117
     * Expand can also be nested, separated with dots (.). e.g.: item.expand1.expand2
118
     * `$recursive` must be true for nested expands to be extracted. If `$recursive` is false, only the root expands will be extracted.
119
     * @param bool $recursive whether to recursively return array representation of embedded objects.
120
     * @return array the array representation of the object
121
     */
122 8
    public function toArray(array $fields = [], array $expand = [], $recursive = true)
123
    {
124 8
        $data = [];
125 8
        foreach ($this->resolveFields($fields, $expand) as $field => $definition) {
126 8
            $attribute = is_string($definition) ? $this->$definition : $definition($this, $field);
127
128 8
            if ($recursive) {
129 8
                $nestedFields = $this->extractFieldsFor($fields, $field);
130 8
                $nestedExpand = $this->extractFieldsFor($expand, $field);
131 8
                if ($attribute instanceof Arrayable) {
132 2
                    $attribute = $attribute->toArray($nestedFields, $nestedExpand);
133 8
                } elseif (is_array($attribute)) {
134 1
                    $attribute = array_map(
135 1
                        function ($item) use ($nestedFields, $nestedExpand) {
136 1
                            if ($item instanceof Arrayable) {
137 1
                                return $item->toArray($nestedFields, $nestedExpand);
138
                            }
139
                            return $item;
140 1
                        },
141 1
                        $attribute
142
                    );
143
                }
144
            }
145 8
            $data[$field] = $attribute;
146
        }
147
148 8
        if ($this instanceof Linkable) {
149
            $data['_links'] = Link::serialize($this->getLinks());
150
        }
151
152 8
        return $recursive ? ArrayHelper::toArray($data) : $data;
153
    }
154
155
    /**
156
     * Extracts the root field names from nested fields.
157
     * Nested fields are separated with dots (.). e.g: "item.id"
158
     * The previous example would extract "item".
159
     *
160
     * @param array $fields The fields requested for extraction
161
     * @return array root fields extracted from the given nested fields
162
     * @since 2.0.14
163
     */
164 8
    protected function extractRootFields(array $fields)
165
    {
166 8
        $result = [];
167
168 8
        foreach ($fields as $field) {
169 3
            $result[] = current(explode('.', $field, 2));
170
        }
171
172 8
        if (in_array('*', $result, true)) {
173 1
            $result = [];
174
        }
175
176 8
        return array_unique($result);
177
    }
178
179
    /**
180
     * Extract nested fields from a fields collection for a given root field
181
     * Nested fields are separated with dots (.). e.g: "item.id"
182
     * The previous example would extract "id".
183
     *
184
     * @param array $fields The fields requested for extraction
185
     * @param string $rootField The root field for which we want to extract the nested fields
186
     * @return array nested fields extracted for the given field
187
     * @since 2.0.14
188
     */
189 8
    protected function extractFieldsFor(array $fields, $rootField)
190
    {
191 8
        $result = [];
192
193 8
        foreach ($fields as $field) {
194 3
            if (0 === strpos($field, "{$rootField}.")) {
195 3
                $result[] = preg_replace('/^' . preg_quote($rootField, '/') . '\./i', '', $field);
196
            }
197
        }
198
199 8
        return array_unique($result);
200
    }
201
202
    /**
203
     * Determines which fields can be returned by [[toArray()]].
204
     * This method will first extract the root fields from the given fields.
205
     * Then it will check the requested root fields against those declared in [[fields()]] and [[extraFields()]]
206
     * to determine which fields can be returned.
207
     * @param array $fields the fields being requested for exporting
208
     * @param array $expand the additional fields being requested for exporting
209
     * @return array the list of fields to be exported. The array keys are the field names, and the array values
210
     * are the corresponding object property names or PHP callables returning the field values.
211
     */
212 8
    protected function resolveFields(array $fields, array $expand)
213
    {
214 8
        $fields = $this->extractRootFields($fields);
215 8
        $expand = $this->extractRootFields($expand);
216 8
        $result = [];
217
218 8
        foreach ($this->fields() as $field => $definition) {
219 8
            if (is_int($field)) {
220 5
                $field = $definition;
221
            }
222 8
            if (empty($fields) || in_array($field, $fields, true)) {
223 8
                $result[$field] = $definition;
224
            }
225
        }
226
227 8
        if (empty($expand)) {
228 7
            return $result;
229
        }
230
231 3
        foreach ($this->extraFields() as $field => $definition) {
232 3
            if (is_int($field)) {
233 3
                $field = $definition;
234
            }
235 3
            if (in_array($field, $expand, true)) {
236 3
                $result[$field] = $definition;
237
            }
238
        }
239
240 3
        return $result;
241
    }
242
}
243