Passed
Pull Request — master (#9)
by
unknown
02:28
created

Serializer   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 283
Duplicated Lines 6.36 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 0
Metric Value
wmc 47
lcom 1
cbo 11
dl 18
loc 283
rs 8.439
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A init() 0 9 3
B serialize() 0 12 5
C serializeModel() 8 47 12
A serializeResource() 0 15 3
B serializeIncluded() 0 20 6
C serializeDataProvider() 0 32 7
A serializePagination() 0 12 1
A serializeModelErrors() 0 13 2
A getRequestedFields() 0 12 3
A getIncluded() 0 5 2
A checkIdentification() 10 14 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Serializer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Serializer, and based on these observations, apply Extract Interface, too.

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
53
    /**
54
     * @inheritdoc
55
     */
56
    public function init()
57
    {
58
        if ($this->request === null) {
59
            $this->request = \Yii::$app->getRequest();
60
        }
61
        if ($this->response === null) {
62
            $this->response = \Yii::$app->getResponse();
63
        }
64
    }
65
66
    /**
67
     * Serializes the given data into a format that can be easily turned into other formats.
68
     * This method mainly converts the objects of recognized types into array representation.
69
     * It will not do conversion for unknown object types or non-object data.
70
     * @param mixed $data the data to be serialized.
71
     * @return mixed the converted data.
72
     */
73
    public function serialize($data)
74
    {
75
        if ($data instanceof Model && $data->hasErrors()) {
76
            return $this->serializeModelErrors($data);
77
        } elseif ($data instanceof ResourceInterface) {
78
            return $this->serializeResource($data);
79
        } elseif ($data instanceof DataProviderInterface) {
80
            return $this->serializeDataProvider($data);
81
        } else {
82
            return $data;
83
        }
84
    }
85
86
    /**
87
     * @param ResourceInterface $model
88
     * @return array
89
     */
90
    protected function serializeModel(ResourceInterface $model)
91
    {
92
        $this->checkIdentification($model);
93
94
        $fields = $this->getRequestedFields();
95
96
        $attributes = isset($fields[$model->getType()]) ? $fields[$model->getType()] : [];
97
        $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...
98
            'id' => $model->getId(),
99
            'type' => $model->getType(),
100
            'attributes' => $model->getResourceAttributes($attributes),
101
        ];
102
103
        $relationships = $model->getResourceRelationships();
104
        if (!empty($relationships)) {
105
            foreach ($relationships as $name => $items) {
106
                $relationship = [];
107
                if (is_array($items)) {
108
                    foreach ($items as $item) {
109 View Code Duplication
                        if ($item instanceof ResourceIdentifierInterface) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
110
                            $this->checkIdentification($item);
0 ignored issues
show
Compatibility introduced by
$item of type object<tuyakhov\jsonapi\...rceIdentifierInterface> is not a sub-type of object<tuyakhov\jsonapi\ResourceInterface>. It seems like you assume a child interface of the interface tuyakhov\jsonapi\ResourceIdentifierInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
111
                            $relationship[] = ['id' => $item->getId(), 'type' => $item->getType()];
112
                        }
113
                    }
114 View Code Duplication
                } elseif ($items instanceof ResourceIdentifierInterface) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
115
                    $this->checkIdentification($items);
0 ignored issues
show
Compatibility introduced by
$items of type object<tuyakhov\jsonapi\...rceIdentifierInterface> is not a sub-type of object<tuyakhov\jsonapi\ResourceInterface>. It seems like you assume a child interface of the interface tuyakhov\jsonapi\ResourceIdentifierInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

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

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
289
            throw new InvalidValueException(
290
                'The values id of resource object ' . get_class ($resource) . ' MUST be a string.'
291
            );
292
        }
293
294 View Code Duplication
        if (!is_string($resource->getType())) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
295
            throw new InvalidValueException(
296
                'The values type of resource object ' . get_class ($resource) . ' MUST be a string.'
297
            );
298
        }
299
    }
300
}
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...
301