Passed
Push — master ( 0f31aa...43dc9a )
by Anton
02:22
created

Serializer::serializeIncluded()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 8.8571
c 0
b 0
f 0
cc 6
eloc 13
nc 4
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\Model;
10
use yii\data\DataProviderInterface;
11
use yii\data\Pagination;
12
use yii\web\Link;
13
use yii\web\Linkable;
14
use yii\web\Request;
15
use yii\web\Response;
16
17
class Serializer extends Component
18
{
19
    /**
20
     * @var string the name of the query parameter containing the information about which fields should be returned
21
     * for a [[Model]] object. If the parameter is not provided or empty, the default set of fields as defined
22
     * by [[Model::fields()]] will be returned.
23
     */
24
    public $fieldsParam = 'fields';
25
    /**
26
     * @var string the name of the query parameter containing the information about which fields should be returned
27
     * in addition to those listed in [[fieldsParam]] for a resource object.
28
     */
29
    public $expandParam = 'include';
30
    /**
31
     * @var string the name of the envelope (e.g. `_links`) for returning the links objects.
32
     * It takes effect only, if `collectionEnvelope` is set.
33
     * @since 2.0.4
34
     */
35
    public $linksEnvelope = 'links';
36
    /**
37
     * @var string the name of the envelope (e.g. `_meta`) for returning the pagination object.
38
     * It takes effect only, if `collectionEnvelope` is set.
39
     * @since 2.0.4
40
     */
41
    public $metaEnvelope = 'meta';
42
    /**
43
     * @var Request the current request. If not set, the `request` application component will be used.
44
     */
45
    public $request;
46
    /**
47
     * @var Response the response to be sent. If not set, the `response` application component will be used.
48
     */
49
    public $response;
50
51
52
    /**
53
     * @inheritdoc
54
     */
55
    public function init()
56
    {
57
        if ($this->request === null) {
58
            $this->request = \Yii::$app->getRequest();
59
        }
60
        if ($this->response === null) {
61
            $this->response = \Yii::$app->getResponse();
62
        }
63
    }
64
65
    /**
66
     * Serializes the given data into a format that can be easily turned into other formats.
67
     * This method mainly converts the objects of recognized types into array representation.
68
     * It will not do conversion for unknown object types or non-object data.
69
     * @param mixed $data the data to be serialized.
70
     * @return mixed the converted data.
71
     */
72
    public function serialize($data)
73
    {
74
        if ($data instanceof Model && $data->hasErrors()) {
75
            return $this->serializeModelErrors($data);
76
        } elseif ($data instanceof ResourceInterface) {
77
            return $this->serializeResource($data);
78
        } elseif ($data instanceof DataProviderInterface) {
79
            return $this->serializeDataProvider($data);
80
        } else {
81
            return $data;
82
        }
83
    }
84
85
    /**
86
     * @param ResourceInterface $model
87
     * @return array
88
     */
89
    protected function serializeModel($model)
90
    {
91
        $fields = $this->getRequestedFields();
92
93
        $attributes = isset($fields[$model->getType()]) ? $fields[$model->getType()] : [];
94
        $data = [
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 7 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...
95
            'id' => $model->getId(),
96
            'type' => $model->getType(),
97
            'attributes' => $model->getResourceAttributes($attributes),
98
        ];
99
100
        $relationships = $model->getResourceRelationships();
101
        if (!empty($relationships)) {
102
            foreach ($relationships as $name => $items) {
103
                $relationship = [];
104
                if (is_array($items)) {
105
                    foreach ($items as $item) {
106
                        if ($item instanceof ResourceIdentifierInterface) {
107
                            $relationship[] = ['id' => $item->getId(), 'type' => $item->getType()];
108
                        }
109
                    }
110
                } elseif ($items instanceof ResourceIdentifierInterface) {
111
                    $relationship = ['id' => $items->getId(), 'type' => $items->getType()];
112
                }
113
                if (!empty($relationship)) {
114
                    $data['relationships'][$name]['data'] = $relationship;
115
                }
116
            }
117
        }
118
119
        if ($model instanceof Linkable) {
120
            $data['links'] = Link::serialize($model->getLinks());
121
        }
122
123
        return $data;
124
    }
125
126
    /**
127
     * @param ResourceInterface $resource
128
     * @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...
129
     */
130
    protected function serializeResource($resource)
131
    {
132
        if ($this->request->getIsHead()) {
133
            return null;
134
        } else {
135
            $data = ['data' => $this->serializeModel($resource)];
136
137
            $included = $this->serializeIncluded($resource);
138
            if (!empty($included)) {
139
                $data['included'] = $included;
140
            }
141
142
            return $data;
143
        }
144
    }
145
146
    /**
147
     * @param ResourceInterface $resource
148
     * @return array
149
     */
150
    protected function serializeIncluded($resource)
151
    {
152
        $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...
153
        $relationships = $resource->getResourceRelationships();
154
        $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...
155
        foreach ($relationships as $name => $relationship) {
156
            if (!in_array($name, $included)) {
157
                continue;
158
            }
159
            if (!is_array($relationship)) {
160
                $relationship = [$relationship];
161
            }
162
            foreach ($relationship as $model) {
163
                if ($model instanceof ResourceInterface) {
164
                    $data[] = $this->serializeModel($model);
165
                }
166
            }
167
        }
168
        return $data;
169
    }
170
171
    /**
172
     * Serializes a data provider.
173
     * @param DataProviderInterface $dataProvider
174
     * @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...
175
     */
176
    protected function serializeDataProvider($dataProvider)
177
    {
178
        if ($this->request->getIsHead()) {
179
            return null;
180
        } else {
181
            $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...
182
            $includedModels = [];
183
184
            foreach ($dataProvider->getModels() as $model) {
185
                if ($model instanceof ResourceInterface) {
186
                    $models[] = $this->serializeModel($model);
187
188
                    $included = $this->serializeIncluded($model);
189
                    foreach ($included as $document) {
190
                        $includedModels[] = $document;
191
                    }
192
                }
193
            }
194
195
            $result = ['data' => $models];
196
197
            if (!empty($includedModels)) {
198
                $result['included'] = $includedModels;
199
            }
200
201
            if (($pagination = $dataProvider->getPagination()) !== false) {
202
                return array_merge($result, $this->serializePagination($pagination));
203
            }
204
205
            return $result;
206
        }
207
    }
208
209
    /**
210
     * Serializes a pagination into an array.
211
     * @param Pagination $pagination
212
     * @return array the array representation of the pagination
213
     * @see addPaginationHeaders()
214
     */
215
    protected function serializePagination($pagination)
216
    {
217
        return [
218
            $this->linksEnvelope => Link::serialize($pagination->getLinks(true)),
219
            $this->metaEnvelope => [
220
                'total-count' => $pagination->totalCount,
221
                'page-count' => $pagination->getPageCount(),
222
                'current-page' => $pagination->getPage() + 1,
223
                'per-page' => $pagination->getPageSize(),
224
            ],
225
        ];
226
    }
227
228
    /**
229
     * Serializes the validation errors in a model.
230
     * @param Model $model
231
     * @return array the array representation of the errors
232
     */
233
    protected function serializeModelErrors($model)
234
    {
235
        $this->response->setStatusCode(422, 'Data Validation Failed.');
236
        $result = [];
237
        foreach ($model->getFirstErrors() as $name => $message) {
238
            $result[] = [
239
                'source' => ['pointer' => "/data/attributes/{$name}"],
240
                'detail' => $message,
241
            ];
242
        }
243
244
        return $result;
245
    }
246
247
    /**
248
     * @return array
249
     */
250
    protected function getRequestedFields()
251
    {
252
        $fields = $this->request->get($this->fieldsParam);
253
254
        if (!is_array($fields)) {
255
            $fields = [];
256
        }
257
        foreach ($fields as $key => $field) {
258
            $fields[$key] = preg_split('/\s*,\s*/', $fields, -1, PREG_SPLIT_NO_EMPTY);
259
        }
260
        return $fields;
261
    }
262
263
    protected function getIncluded()
264
    {
265
        $include = $this->request->get($this->expandParam);
266
        return is_string($include) ? preg_split('/\s*,\s*/', $include, -1, PREG_SPLIT_NO_EMPTY) : [];
267
    }
268
}
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...
269