Passed
Push — master ( c6c1d6...f962e2 )
by Anton
02:32
created

Serializer   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 277
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 0
Metric Value
wmc 50
lcom 1
cbo 10
dl 0
loc 277
rs 8.6206
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() 0 41 12
A serializeResource() 0 15 3
B serializeIdentifier() 0 13 6
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

How to fix   Complexity   

Complex Class

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