Passed
Push — master ( 672c08...9a8256 )
by Anton
02:40
created

Serializer::serializeIncluded()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 9.2
c 0
b 0
f 0
cc 4
eloc 10
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
                if (is_array($items)) {
104
                    foreach ($items as $item) {
105
                        if ($item instanceof ResourceIdentifierInterface) {
106
                            $relationship[] = ['id' => $item->getId(), 'type' => $item->getType()];
0 ignored issues
show
Coding Style Comprehensibility introduced by
$relationship was never initialized. Although not strictly required by PHP, it is generally a good practice to add $relationship = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
107
                        }
108
                    }
109
                } elseif ($items instanceof ResourceIdentifierInterface) {
110
                    $relationship = ['id' => $items->getId(), 'type' => $items->getType()];
111
                }
112
                if (!empty($relationship)) {
113
                    $data['relationships'][$name] = $relationship;
114
                }
115
            }
116
        }
117
118
        if ($model instanceof Linkable) {
119
            $data['links'] = Link::serialize($model->getLinks());
120
        }
121
122
        return $data;
123
    }
124
125
    /**
126
     * @param ResourceInterface $resource
127
     * @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...
128
     */
129
    protected function serializeResource($resource)
130
    {
131
        if ($this->request->getIsHead()) {
132
            return null;
133
        } else {
134
            $data = ['data' => $this->serializeModel($resource)];
135
136
            $included = $this->serializeIncluded($resource);
137
            if (!empty($included)) {
138
                $data['included'] = $included;
139
            }
140
141
            return $data;
142
        }
143
    }
144
145
    /**
146
     * @param ResourceInterface $resource
147
     * @return array
148
     */
149
    protected function serializeIncluded($resource)
150
    {
151
        $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...
152
        $relationships = $resource->getResourceRelationships();
153
        $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...
154
        foreach ($relationships as $name => $relationship) {
155
            if (!in_array($name, $included)) {
156
                continue;
157
            }
158
            if ($relationship instanceof ResourceInterface) {
159
                $data[] = $this->serializeModel($relationship);
160
            }
161
        }
162
        return $data;
163
    }
164
165
    /**
166
     * Serializes a data provider.
167
     * @param DataProviderInterface $dataProvider
168
     * @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...
169
     */
170
    protected function serializeDataProvider($dataProvider)
171
    {
172
        if ($this->request->getIsHead()) {
173
            return null;
174
        } else {
175
            $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...
176
            $includedModels = [];
177
178
            foreach ($dataProvider->getModels() as $model) {
179
                if ($model instanceof ResourceInterface) {
180
                    $models[] = $this->serializeModel($model);
181
182
                    $included = $this->serializeIncluded($model);
183
                    foreach ($included as $document) {
184
                        $includedModels[] = $document;
185
                    }
186
                }
187
            }
188
189
            $result = ['data' => $models];
190
191
            if (!empty($includedModels)) {
192
                $result['included'] = $includedModels;
193
            }
194
195
            if (($pagination = $dataProvider->getPagination()) !== false) {
196
                return array_merge($result, $this->serializePagination($pagination));
197
            }
198
199
            return $result;
200
        }
201
    }
202
203
    /**
204
     * Serializes a pagination into an array.
205
     * @param Pagination $pagination
206
     * @return array the array representation of the pagination
207
     * @see addPaginationHeaders()
208
     */
209
    protected function serializePagination($pagination)
210
    {
211
        return [
212
            $this->linksEnvelope => Link::serialize($pagination->getLinks(true)),
213
            $this->metaEnvelope => [
214
                'total-count' => $pagination->totalCount,
215
                'page-count' => $pagination->getPageCount(),
216
                'current-page' => $pagination->getPage() + 1,
217
                'per-page' => $pagination->getPageSize(),
218
            ],
219
        ];
220
    }
221
222
    /**
223
     * Serializes the validation errors in a model.
224
     * @param Model $model
225
     * @return array the array representation of the errors
226
     */
227
    protected function serializeModelErrors($model)
228
    {
229
        $this->response->setStatusCode(422, 'Data Validation Failed.');
230
        $result = [];
231
        foreach ($model->getFirstErrors() as $name => $message) {
232
            $result[] = [
233
                'source' => ['pointer' => "/data/attributes/{$name}"],
234
                'detail' => $message,
235
            ];
236
        }
237
238
        return $result;
239
    }
240
241
    /**
242
     * @return array
243
     */
244
    protected function getRequestedFields()
245
    {
246
        $fields = $this->request->get($this->fieldsParam);
247
248
        if (!is_array($fields)) {
249
            $fields = [];
250
        }
251
        foreach ($fields as $key => $field) {
252
            $fields[$key] = preg_split('/\s*,\s*/', $fields, -1, PREG_SPLIT_NO_EMPTY);
253
        }
254
        return $fields;
255
    }
256
257
    protected function getIncluded()
258
    {
259
        $include = $this->request->get($this->expandParam);
260
        return is_string($include) ? preg_split('/\s*,\s*/', $include, -1, PREG_SPLIT_NO_EMPTY) : [];
261
    }
262
}
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...
263