JsonApiTransformer   A
last analyzed

Complexity

Total Complexity 24

Size/Duplication

Total Lines 201
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Importance

Changes 12
Bugs 1 Features 2
Metric Value
wmc 24
c 12
b 1
f 2
lcom 1
cbo 9
dl 0
loc 201
rs 10

10 Methods

Rating   Name   Duplication   Size   Complexity  
A serialize() 0 12 3
A serializeObject() 0 7 1
A preSerialization() 0 12 2
A serialization() 0 19 1
C setResponseLinks() 0 28 7
A setResponseMeta() 0 6 2
A setResponseVersion() 0 4 1
A postSerialization() 0 7 1
A buildValidPropertyAlias() 0 8 2
B serializedArray() 0 25 4
1
<?php
2
3
namespace NilPortugues\Api\JsonApi;
4
5
use NilPortugues\Api\JsonApi\Helpers\DataAttributesHelper;
6
use NilPortugues\Api\JsonApi\Helpers\DataIncludedHelper;
7
use NilPortugues\Api\JsonApi\Helpers\DataLinksHelper;
8
use NilPortugues\Api\JsonApi\Helpers\PropertyHelper;
9
use NilPortugues\Api\Transformer\Helpers\RecursiveDeleteHelper;
10
use NilPortugues\Api\Transformer\Helpers\RecursiveFilterHelper;
11
use NilPortugues\Api\Transformer\Helpers\RecursiveRenamerHelper;
12
use NilPortugues\Api\Transformer\Transformer;
13
use NilPortugues\Serializer\Serializer;
14
15
/**
16
 * This Transformer follows the http://JsonApi.org specification.
17
 *
18
 * @link http://JsonApi.org/format/#document-structure
19
 */
20
class JsonApiTransformer extends Transformer
21
{
22
    const TITLE = 'title';
23
    const RELATIONSHIPS_KEY = 'relationships';
24
    const LINKS_KEY = 'links';
25
    const TYPE_KEY = 'type';
26
    const DATA_KEY = 'data';
27
    const JSON_API_KEY = 'jsonapi';
28
    const META_KEY = 'meta';
29
    const INCLUDED_KEY = 'included';
30
    const VERSION_KEY = 'version';
31
    const ATTRIBUTES_KEY = 'attributes';
32
    const ID_KEY = 'id';
33
    const ID_SEPARATOR = '.';
34
    const RELATED_LINK = 'related';
35
36
    /**
37
     * @param array $value
38
     *
39
     * @throws \NilPortugues\Api\Transformer\TransformerException
40
     *
41
     * @return string
42
     */
43
    public function serialize($value)
44
    {
45
        $this->noMappingGuard();
46
47
        if (\is_array($value) && !empty($value[Serializer::MAP_TYPE])) {
48
            $data = $this->serializedArray($value);
49
        } else {
50
            $data = $this->serializeObject($value);
51
        }
52
53
        return \json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
54
    }
55
56
    /**
57
     * @param array $value
58
     *
59
     * @return array
60
     */
61
    protected function serializeObject(array $value)
62
    {
63
        $value = $this->preSerialization($value);
64
        $data = $this->serialization($value);
65
66
        return $this->postSerialization($data);
67
    }
68
69
    /**
70
     * @param array $value
71
     *
72
     * @return array
73
     */
74
    protected function preSerialization(array $value)
75
    {
76
        /** @var \NilPortugues\Api\Mapping\Mapping $mapping */
77
        foreach ($this->mappings as $class => $mapping) {
78
            RecursiveFilterHelper::deletePropertiesNotInFilter($this->mappings, $value, $class);
79
            RecursiveDeleteHelper::deleteProperties($this->mappings, $value, $class);
80
            $this->buildValidPropertyAlias($mapping);
81
            RecursiveRenamerHelper::renameKeyValue($this->mappings, $value, $class);
82
        }
83
84
        return $value;
85
    }
86
87
    /**
88
     * @param array $value
89
     *
90
     * @return array
91
     */
92
    protected function serialization(array &$value)
93
    {
94
        $data = [
95
            self::DATA_KEY => \array_merge(
96
                PropertyHelper::setResponseDataTypeAndId($this->mappings, $value),
97
                DataAttributesHelper::setResponseDataAttributes($this->mappings, $value),
98
                DataLinksHelper::setResponseDataLinks($this->mappings, $value),
99
                DataLinksHelper::setResponseDataRelationship($this->mappings, $value, $value)
100
            ),
101
        ];
102
103
        DataIncludedHelper::setResponseDataIncluded($this->mappings, $value, $data);
104
105
        $this->setResponseLinks($value, $data);
106
        $this->setResponseMeta($data);
107
        $this->setResponseVersion($data);
108
109
        return $data;
110
    }
111
112
    /**
113
     * @param array $value
114
     * @param array $data
115
     */
116
    protected function setResponseLinks(array $value, array &$data)
117
    {
118
        $data[self::LINKS_KEY] = \array_filter(
119
            \array_merge(
120
                $this->addHrefToLinks($this->buildLinks()),
121
                (!empty($data[self::LINKS_KEY])) ? $data[self::LINKS_KEY] : []
122
            )
123
        );
124
125
        if (!empty($value[Serializer::CLASS_IDENTIFIER_KEY])) {
126
            $type = $value[Serializer::CLASS_IDENTIFIER_KEY];
127
128
            if (\is_scalar($type)) {
129
                $urls = $this->mappings[$type]->getUrls();
130
131
                $data[self::LINKS_KEY] = \array_filter(
132
                    \array_merge(
133
                        (empty($data[self::LINKS_KEY])) ? [] : $data[self::LINKS_KEY],
134
                        (!empty($urls)) ? $this->addHrefToLinks($this->getResponseAdditionalLinks($value, $type)) : []
135
                    )
136
                );
137
            }
138
        }
139
140
        if (empty($data[self::LINKS_KEY])) {
141
            unset($data[self::LINKS_KEY]);
142
        }
143
    }
144
145
    /**
146
     * @param array $response
147
     */
148
    protected function setResponseMeta(array &$response)
149
    {
150
        if (!empty($this->meta)) {
151
            $response[self::META_KEY] = $this->meta;
152
        }
153
    }
154
155
    /**
156
     * @param array $response
157
     */
158
    protected function setResponseVersion(array &$response)
159
    {
160
        $response[self::JSON_API_KEY][self::VERSION_KEY] = '1.0';
161
    }
162
163
    /**
164
     * @param array $data
165
     *
166
     * @return array
167
     */
168
    protected function postSerialization(array $data)
169
    {
170
        $this->formatScalarValues($data);
171
        RecursiveDeleteHelper::deleteKeys($data, [Serializer::CLASS_IDENTIFIER_KEY]);
172
173
        return $data;
174
    }
175
176
    /**
177
     * @param \NilPortugues\Api\Mapping\Mapping $mapping
178
     *
179
     * @link http://jsonapi.org/format/#document-member-names-allowed-characters
180
     */
181
    protected function buildValidPropertyAlias($mapping)
182
    {
183
        $aliases = $mapping->getAliasedProperties();
184
        foreach ($aliases as &$alias) {
185
            $alias = DataAttributesHelper::transformToValidMemberName($alias);
186
        }
187
        $mapping->setPropertyNameAliases($aliases);
188
    }
189
190
    /**
191
     * @param array $value
192
     *
193
     * @return array
194
     */
195
    protected function serializedArray(array $value)
196
    {
197
        unset($value[Serializer::MAP_TYPE]);
198
199
        $dataValues = [];
200
        $includedValues = [];
201
202
        foreach ($value[Serializer::SCALAR_VALUE] as $v) {
203
            $v = $this->serializeObject($v);
204
            $dataValues[] = $v[self::DATA_KEY];
205
            if (!empty($v[self::INCLUDED_KEY])) {
206
                $includedValues = \array_merge($includedValues, $v[self::INCLUDED_KEY]);
207
            }
208
        }
209
        $includedValues = \array_unique($includedValues, SORT_REGULAR);
210
211
        $data[self::DATA_KEY] = $dataValues;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$data was never initialized. Although not strictly required by PHP, it is generally a good practice to add $data = 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...
212
        $data[self::INCLUDED_KEY] = \array_values($includedValues);
213
        $data = array_filter($data);
214
215
        $this->setResponseLinks($value, $data);
216
        $this->setResponseVersion($data);
217
218
        return (empty($data['data'])) ? array_merge(['data' => []], $data) : $data;
219
    }
220
}
221