Passed
Push — master ( aae7cc...acea2e )
by Anton
29s
created

Serializer::prepareMemberNames()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
/**
3
 * @author Anton Tuyakhov <[email protected]>
4
 */
5
6
namespace tuyakhov\jsonapi;
7
8
use yii\base\Component;
9
use yii\base\InvalidValueException;
10
use yii\base\Model;
11
use yii\data\DataProviderInterface;
12
use yii\data\Pagination;
13
use yii\web\Link;
14
use yii\web\Linkable;
15
use yii\web\Request;
16
use yii\web\Response;
17
18
class Serializer extends Component
19
{
20
    /**
21
     * @var string the name of the query parameter containing the information about which fields should be returned
22
     * for a [[Model]] object. If the parameter is not provided or empty, the default set of fields as defined
23
     * by [[Model::fields()]] will be returned.
24
     */
25
    public $fieldsParam = 'fields';
26
    /**
27
     * @var string the name of the query parameter containing the information about which fields should be returned
28
     * in addition to those listed in [[fieldsParam]] for a resource object.
29
     */
30
    public $expandParam = 'include';
31
    /**
32
     * @var string the name of the envelope (e.g. `_links`) for returning the links objects.
33
     * It takes effect only, if `collectionEnvelope` is set.
34
     * @since 2.0.4
35
     */
36
    public $linksEnvelope = 'links';
37
    /**
38
     * @var string the name of the envelope (e.g. `_meta`) for returning the pagination object.
39
     * It takes effect only, if `collectionEnvelope` is set.
40
     * @since 2.0.4
41
     */
42
    public $metaEnvelope = 'meta';
43
    /**
44
     * @var Request the current request. If not set, the `request` application component will be used.
45
     */
46
    public $request;
47
    /**
48
     * @var Response the response to be sent. If not set, the `response` application component will be used.
49
     */
50
    public $response;
51
    /**
52
     * @var bool whether to automatically pluralize the `type` of resource.
53
     */
54
    public $pluralize = true;
55
56
    /**
57
     * Prepares the member name that should be returned.
58
     * If not set, all member names will be converted to recommended format.
59
     * For example, both 'firstName' and 'first_name' will be converted to 'first-name'.
60
     * @var callable
61
     */
62
    public $prepareMemberName = ['tuyakhov\jsonapi\Inflector', 'var2member'];
63
64
    /**
65
     * Converts a member name to an attribute name.
66
     * @var callable
67
     */
68
    public $formatMemberName = ['tuyakhov\jsonapi\Inflector', 'member2var'];
69
70
71
    /**
72
     * @inheritdoc
73
     */
74
    public function init()
75
    {
76
        if ($this->request === null) {
77
            $this->request = \Yii::$app->getRequest();
78
        }
79
        if ($this->response === null) {
80
            $this->response = \Yii::$app->getResponse();
81
        }
82
    }
83
84
    /**
85
     * Serializes the given data into a format that can be easily turned into other formats.
86
     * This method mainly converts the objects of recognized types into array representation.
87
     * It will not do conversion for unknown object types or non-object data.
88
     * @param mixed $data the data to be serialized.
89
     * @return mixed the converted data.
90
     */
91
    public function serialize($data)
92
    {
93
        if ($data instanceof Model && $data->hasErrors()) {
94
            return $this->serializeModelErrors($data);
95
        } elseif ($data instanceof ResourceInterface) {
96
            return $this->serializeResource($data);
97
        } elseif ($data instanceof DataProviderInterface) {
98
            return $this->serializeDataProvider($data);
99
        } else {
100
            return $data;
101
        }
102
    }
103
104
    /**
105
     * @param ResourceInterface $model
106
     * @return array
107
     */
108
    protected function serializeModel(ResourceInterface $model)
109
    {
110
        $fields = $this->getRequestedFields();
111
        $type = $this->pluralize ? Inflector::pluralize($model->getType()) : $model->getType();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
112
        $fields = isset($fields[$type]) ? $fields[$type] : [];
113
114
        $attributes = $model->getResourceAttributes($fields);
115
        $attributes = array_combine($this->prepareMemberNames(array_keys($attributes)), array_values($attributes));
116
117
        $data = array_merge($this->serializeIdentifier($model), [
118
            'attributes' => $attributes,
119
        ]);
120
121
        $included = $this->getIncluded();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
122
        $relationships = $model->getResourceRelationships();
123
        if (!empty($relationships)) {
124
            foreach ($relationships as $name => $items) {
125
                $relationship = [];
126
                if (is_array($items)) {
127
                    foreach ($items as $item) {
128
                        if ($item instanceof ResourceIdentifierInterface) {
129
                            $relationship[] = $this->serializeIdentifier($item);
130
                        }
131
                    }
132
                } elseif ($items instanceof ResourceIdentifierInterface) {
133
                    $relationship = $this->serializeIdentifier($items);
134
                }
135
                if (!empty($relationship)) {
136
                    $memberName = $this->prepareMemberNames([$name]);
137
                    $memberName = reset($memberName);
138
                    if (in_array($name, $included)) {
139
                        $data['relationships'][$memberName]['data'] = $relationship;
140
                    }
141
                    if ($model instanceof LinksInterface) {
142
                        $links = $model->getRelationshipLinks($memberName);
143
                        if (!empty($links)) {
144
                            $data['relationships'][$memberName]['links'] = Link::serialize($links);
145
                        }
146
                    }
147
                }
148
            }
149
        }
150
151
        if ($model instanceof Linkable) {
152
            $data['links'] = Link::serialize($model->getLinks());
153
        }
154
155
        return $data;
156
    }
157
158
    /**
159
     * @param ResourceInterface $resource
160
     * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be null|array<string,array>?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
161
     */
162
    protected function serializeResource(ResourceInterface $resource)
163
    {
164
        if ($this->request->getIsHead()) {
165
            return null;
166
        } else {
167
            $data = ['data' => $this->serializeModel($resource)];
168
169
            $included = $this->serializeIncluded($resource);
170
            if (!empty($included)) {
171
                $data['included'] = $included;
172
            }
173
174
            return $data;
175
        }
176
    }
177
178
    /**
179
     * Serialize resource identifier object and make type juggling
180
     * @link http://jsonapi.org/format/#document-resource-object-identification
181
     * @param ResourceIdentifierInterface $identifier
182
     * @return array
183
     */
184
    protected function serializeIdentifier(ResourceIdentifierInterface $identifier)
185
    {
186
        $result = [];
187
        foreach (['id', 'type'] as $key) {
188
            $getter = 'get' . ucfirst($key);
189
            $value = $identifier->$getter();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
190
            if ($value === null || is_array($value) || (is_object($value) && !method_exists($value, '__toString'))) {
191
                throw new InvalidValueException("The value {$key} of resource object " . get_class($identifier) . ' MUST be a string.');
192
            }
193
            if ($key === 'type' && $this->pluralize) {
194
                $value = Inflector::pluralize($value);
195
            }
196
            $result[$key] = (string) $value;
197
        }
198
        return $result;
199
    }
200
201
    /**
202
     * @param ResourceInterface $resource
203
     * @return array
204
     */
205
    protected function serializeIncluded($resource)
206
    {
207
        $included = $this->getIncluded();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
208
        $relationships = $resource->getResourceRelationships();
209
        $data = [];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 10 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
210
        foreach ($relationships as $name => $relationship) {
211
            if (!in_array($name, $included)) {
212
                continue;
213
            }
214
            if (!is_array($relationship)) {
215
                $relationship = [$relationship];
216
            }
217
            foreach ($relationship as $model) {
218
                if ($model instanceof ResourceInterface) {
219
                    $data[] = $this->serializeModel($model);
220
                }
221
            }
222
        }
223
        return $data;
224
    }
225
226
    /**
227
     * Serializes a data provider.
228
     * @param DataProviderInterface $dataProvider
229
     * @return array the array representation of the data provider.
0 ignored issues
show
Documentation introduced by
Should the return type not be null|array<string,array>?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
230
     */
231
    protected function serializeDataProvider($dataProvider)
232
    {
233
        if ($this->request->getIsHead()) {
234
            return null;
235
        } else {
236
            $models = [];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 9 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
237
            $includedModels = [];
238
239
            foreach ($dataProvider->getModels() as $model) {
240
                if ($model instanceof ResourceInterface) {
241
                    $models[] = $this->serializeModel($model);
242
243
                    $included = $this->serializeIncluded($model);
244
                    foreach ($included as $document) {
245
                        $includedModels[] = $document;
246
                    }
247
                }
248
            }
249
250
            $result = ['data' => $models];
251
252
            if (!empty($includedModels)) {
253
                $result['included'] = $includedModels;
254
            }
255
256
            if (($pagination = $dataProvider->getPagination()) !== false) {
257
                return array_merge($result, $this->serializePagination($pagination));
258
            }
259
260
            return $result;
261
        }
262
    }
263
264
    /**
265
     * Serializes a pagination into an array.
266
     * @param Pagination $pagination
267
     * @return array the array representation of the pagination
268
     * @see addPaginationHeaders()
269
     */
270
    protected function serializePagination($pagination)
271
    {
272
        return [
273
            $this->linksEnvelope => Link::serialize($pagination->getLinks(true)),
274
            $this->metaEnvelope => [
275
                'total-count' => $pagination->totalCount,
276
                'page-count' => $pagination->getPageCount(),
277
                'current-page' => $pagination->getPage() + 1,
278
                'per-page' => $pagination->getPageSize(),
279
            ],
280
        ];
281
    }
282
283
    /**
284
     * Serializes the validation errors in a model.
285
     * @param Model $model
286
     * @return array the array representation of the errors
287
     */
288
    protected function serializeModelErrors($model)
289
    {
290
        $this->response->setStatusCode(422, 'Data Validation Failed.');
291
        $result = [];
292
        foreach ($model->getFirstErrors() as $name => $message) {
293
            $result[] = [
294
                'source' => ['pointer' => "/data/attributes/{$name}"],
295
                'detail' => $message,
296
            ];
297
        }
298
299
        return $result;
300
    }
301
302
    /**
303
     * @return array
304
     */
305
    protected function getRequestedFields()
306
    {
307
        $fields = $this->request->get($this->fieldsParam);
308
309
        if (!is_array($fields)) {
310
            $fields = [];
311
        }
312
        foreach ($fields as $key => $field) {
313
            $fields[$key] = array_map($this->formatMemberName, preg_split('/\s*,\s*/', $field, -1, PREG_SPLIT_NO_EMPTY));
314
        }
315
        return $fields;
316
    }
317
318
    protected function getIncluded()
319
    {
320
        $include = $this->request->get($this->expandParam);
321
        return is_string($include) ? array_map($this->formatMemberName, preg_split('/\s*,\s*/', $include, -1, PREG_SPLIT_NO_EMPTY)) : [];
322
    }
323
324
325
    /**
326
     * Format member names according to recommendations for JSON API implementations
327
     * @link http://jsonapi.org/format/#document-member-names
328
     * @param array $memberNames
329
     * @return array
330
     */
331
    protected function prepareMemberNames(array $memberNames = [])
332
    {
333
        return array_map($this->prepareMemberName, $memberNames);
334
    }
335
}
0 ignored issues
show
Coding Style introduced by
As per coding style, files should not end with a newline character.

This check marks files that end in a newline character, i.e. an empy line.

Loading history...
336