Completed
Pull Request — master (#6)
by Jacob
02:56
created

Serializer::serializeError()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 8
rs 9.4285
cc 1
eloc 4
nc 1
nop 3
1
<?php
2
3
namespace As3\Modlr\Api\JsonApiOrg;
4
5
use As3\Modlr\Api\AdapterInterface;
6
use As3\Modlr\Api\SerializerException;
7
use As3\Modlr\Api\SerializerInterface;
8
use As3\Modlr\Metadata\AttributeMetadata;
9
use As3\Modlr\Metadata\EmbeddedPropMetadata;
10
use As3\Modlr\Metadata\EmbedMetadata;
11
use As3\Modlr\Metadata\RelationshipMetadata;
12
use As3\Modlr\Models\Collections\Collection;
13
use As3\Modlr\Models\Collections\EmbedCollection;
14
use As3\Modlr\Models\Embed;
15
use As3\Modlr\Models\Model;
16
17
/**
18
 * Serializes Models into the JSON API spec.
19
 * www.jsonapi.org
20
 *
21
 * @author Jacob Bare <[email protected]>
22
 */
23
final class Serializer implements SerializerInterface
24
{
25
    /**
26
     * Denotes the current object depth of the serializer.
27
     *
28
     * @var int
29
     */
30
    private $depth = 0;
31
32
    /**
33
     * {@inheritDoc}
34
     */
35
    public function serialize(Model $model = null, AdapterInterface $adapter)
36
    {
37
        $serialized['data'] = null;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$serialized was never initialized. Although not strictly required by PHP, it is generally a good practice to add $serialized = 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...
38
        if (null !== $model) {
39
            $serialized['data'] = $this->serializeModel($model, $adapter);
40
        }
41
        return (0 === $this->depth) ? $this->encode($serialized) : $serialized;
42
    }
43
44
    /**
45
     * {@inheritDoc}
46
     */
47
    public function serializeCollection(Collection $collection, AdapterInterface $adapter)
48
    {
49
        return $this->serializeArray($collection->allWithoutLoad(), $adapter);
50
    }
51
52
    /**
53
     * {@inheritDoc}
54
     */
55
    public function serializeArray(array $models, AdapterInterface $adapter)
56
    {
57
        $serialized['data'] = [];
0 ignored issues
show
Coding Style Comprehensibility introduced by
$serialized was never initialized. Although not strictly required by PHP, it is generally a good practice to add $serialized = 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...
58
        foreach ($models as $model) {
59
            $serialized['data'][] = $this->serializeModel($model, $adapter);
60
        }
61
        return (0 === $this->depth) ? $this->encode($serialized) : $serialized;
62
    }
63
64
    /**
65
     * {@inheritDoc}
66
     */
67
    public function serializeError($title, $message, $httpCode)
68
    {
69
        return $this->encode([
70
            'errors'    => [
71
                ['status' => (String) $httpCode, 'title' => $title, 'detail' => $message],
72
            ],
73
        ]);
74
    }
75
76
    /**
77
     * Serializes the "interior" of a model.
78
     * This is the serialization that takes place outside of a "data" container.
79
     * Can be used for root model and relationship model serialization.
80
     *
81
     * @param   Model               $model
82
     * @param   AdapterInterface    $adapter
83
     * @return  array
84
     */
85
    protected function serializeModel(Model $model, AdapterInterface $adapter)
86
    {
87
        $metadata = $model->getMetadata();
88
        $serialized = [
89
            'type'  => $model->getType(),
90
            'id'    => $model->getId(),
91
        ];
92
        if ($this->depth > 0) {
93
            // $this->includeResource($resource);
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
94
            return $serialized;
95
        }
96
97
        foreach ($metadata->getAttributes() as $key => $attrMeta) {
98
            $value = $model->get($key);
99
            $serialized['attributes'][$key] = $this->serializeAttribute($value, $attrMeta);
100
        }
101
102
        foreach ($metadata->getEmbeds() as $key => $embeddedPropMeta) {
103
            $value = $model->get($key);
104
            $serialized['attributes'][$key] = $this->serializeEmbed($value, $embeddedPropMeta);
105
        }
106
107
        $serialized['links'] = ['self' => $adapter->buildUrl($metadata, $model->getId())];
108
109
        $model->enableCollectionAutoInit(false);
110
        $this->increaseDepth();
111
        foreach ($metadata->getRelationships() as $key => $relMeta) {
112
            $relationship = $model->get($key);
113
            $serialized['relationships'][$key] = $this->serializeRelationship($model, $relationship, $relMeta, $adapter);
114
        }
115
        $this->decreaseDepth();
116
        $model->enableCollectionAutoInit(true);
117
        return $serialized;
118
    }
119
120
    /**
121
     * Serializes an attribute value.
122
     *
123
     * @param   mixed               $value
124
     * @param   AttributeMetadata   $attrMeta
125
     * @return  mixed
126
     */
127
    protected function serializeAttribute($value, AttributeMetadata $attrMeta)
128
    {
129
        if ('date' === $attrMeta->dataType && $value instanceof \DateTime) {
130
            $milliseconds = sprintf('%03d', round($value->format('u') / 1000, 0));
131
            return gmdate(sprintf('Y-m-d\TH:i:s.%s\Z', $milliseconds), $value->getTimestamp());
132
        }
133
        if ('array' === $attrMeta->dataType && empty($value)) {
134
            return [];
135
        }
136
        if ('object' === $attrMeta->dataType) {
137
            return (array) $value;
138
        }
139
        return $value;
140
    }
141
142
    /**
143
     * Serializes an embed value.
144
     *
145
     * @param   Embed|EmbedCollection|null  $value
146
     * @param   EmbeddedPropMetadata        $embeddedPropMeta
147
     * @return  array|null
148
     */
149
    protected function serializeEmbed($value, EmbeddedPropMetadata $embeddedPropMeta)
150
    {
151
        $embedMeta = $embeddedPropMeta->embedMeta;
152
        if (true === $embeddedPropMeta->isOne()) {
153
            return $this->serializeEmbedOne($embedMeta, $value);
154
        }
155
        return $this->serializeEmbedMany($embedMeta, $value);
156
    }
157
158
    /**
159
     * Serializes an embed one value.
160
     *
161
     * @param   EmbedMetadata   $embedMeta
162
     * @param   Embed|null      $embed
163
     * @return  array|null
164
     */
165
    protected function serializeEmbedOne(EmbedMetadata $embedMeta, Embed $embed = null)
166
    {
167
        if (null === $embed) {
168
            return;
169
        }
170
        $serialized = [];
171
        foreach ($embedMeta->getAttributes() as $key => $attrMeta) {
172
            $serialized[$key] = $this->serializeAttribute($embed->get($key), $attrMeta);
173
        }
174
        foreach ($embedMeta->getEmbeds() as $key => $embeddedPropMeta) {
175
            $serialized[$key] = $this->serializeEmbed($embed->get($key), $embeddedPropMeta);
176
        }
177
178
        return empty($serialized) ? null : $serialized;
179
    }
180
181
    /**
182
     * Serializes an embed many value.
183
     *
184
     * @param   EmbedMetadata   $embedMeta
185
     * @param   EmbedCollection $embed
0 ignored issues
show
Bug introduced by
There is no parameter named $embed. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
186
     * @return  array
187
     */
188
    protected function serializeEmbedMany(EmbedMetadata $embedMeta, EmbedCollection $collection)
189
    {
190
        $serialized = [];
191
        foreach ($collection as $embed) {
192
            if (!$embed instanceof Embed) {
193
                continue;
194
            }
195
            $serialized[] = $this->serializeEmbedOne($embedMeta, $embed);
196
        }
197
        return $serialized;
198
    }
199
200
    /**
201
     * Serializes a relationship value
202
     *
203
     * @param   Model                       $owner
204
     * @param   Model|Model[]|null          $relationship
205
     * @param   RelationshipMetadata        $relMeta
206
     * @param   AdapterInterface            $adapter
207
     * @return  array
208
     */
209
    protected function serializeRelationship(Model $owner, $relationship = null, RelationshipMetadata $relMeta, AdapterInterface $adapter)
210
    {
211
        if ($relMeta->isOne()) {
212
            if (is_array($relationship)) {
213
                throw SerializerException::badRequest('Invalid relationship value.');
214
            }
215
            $serialized = $this->serializeHasOne($owner, $relationship, $adapter);
216
        } elseif (is_array($relationship) || null === $relationship) {
217
            $serialized = $this->serializeHasMany($owner, $relationship, $adapter);
0 ignored issues
show
Bug introduced by
It seems like $relationship defined by parameter $relationship on line 209 can also be of type array; however, As3\Modlr\Api\JsonApiOrg...zer::serializeHasMany() does only seem to accept null|array<integer,objec...s3\Modlr\Models\Model>>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
218
        } else {
219
            throw SerializerException::badRequest('Invalid relationship value.');
220
        }
221
222
        $ownerMeta = $owner->getMetadata();
223
        $serialized['links'] = [
224
            'self'      => $adapter->buildUrl($ownerMeta, $owner->getId(), $relMeta->getKey()),
225
            'related'   => $adapter->buildUrl($ownerMeta, $owner->getId(), $relMeta->getKey(), true),
226
        ];
227
        return $serialized;
228
    }
229
230
    /**
231
     * Serializes a has-many relationship value
232
     *
233
     * @param   Model                   $owner
234
     * @param   Model[]|null            $models
235
     * @param   AdapterInterface        $adapter
236
     * @return  array
237
     */
238
    protected function serializeHasMany(Model $owner, array $models = null, AdapterInterface $adapter)
0 ignored issues
show
Unused Code introduced by
The parameter $owner is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
239
    {
240
        if (empty($models)) {
241
            return $this->serialize(null, $adapter);
242
        }
243
        return $this->serializeArray($models, $adapter);
244
    }
245
246
    /**
247
     * Serializes a has-one relationship value
248
     *
249
     * @param   Model                   $owner
250
     * @param   Model|null              $model
251
     * @param   AdapterInterface        $adapter
252
     * @return  array
253
     */
254
    protected function serializeHasOne(Model $owner, Model $model = null, AdapterInterface $adapter)
0 ignored issues
show
Unused Code introduced by
The parameter $owner is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
255
    {
256
        return $this->serialize($model, $adapter);
257
    }
258
259
    /**
260
     * Encodes the formatted payload array.
261
     *
262
     * @param   array   $payload
263
     * @return  string
264
     */
265
    private function encode(array $payload)
266
    {
267
        return json_encode($payload);
268
    }
269
270
    /**
271
     * Increases the serializer depth.
272
     *
273
     * @return  self
274
     */
275
    protected function increaseDepth()
276
    {
277
        $this->depth++;
278
        return $this;
279
    }
280
281
    /**
282
     * Decreases the serializer depth.
283
     *
284
     * @return  self
285
     */
286
    protected function decreaseDepth()
287
    {
288
        if ($this->depth > 0) {
289
            $this->depth--;
290
        }
291
        return $this;
292
    }
293
}
294