Completed
Pull Request — master (#6)
by Jacob
03:17
created

Serializer::increaseDepth()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 5
rs 9.4285
cc 1
eloc 3
nc 1
nop 0
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 ($embeddedPropMeta->isOne()) {
153
            return $this->serializeEmbedOne($embedMeta, $value);
1 ignored issue
show
Bug introduced by
It seems like $value defined by parameter $value on line 149 can also be of type object<As3\Modlr\Models\...ctions\EmbedCollection>; however, As3\Modlr\Api\JsonApiOrg...er::serializeEmbedOne() does only seem to accept null|object<As3\Modlr\Models\Embed>, 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...
154
        }
155
        return $this->serializeEmbedMany($embedMeta, $value);
1 ignored issue
show
Bug introduced by
It seems like $value defined by parameter $value on line 149 can also be of type null or object<As3\Modlr\Models\Embed>; however, As3\Modlr\Api\JsonApiOrg...r::serializeEmbedMany() does only seem to accept object<As3\Modlr\Models\...ctions\EmbedCollection>, 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...
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   Embed|null      $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
            $serialized[] = $this->serializeEmbedOne($embedMeta, $embed);
0 ignored issues
show
Documentation introduced by
$embed is of type object<As3\Modlr\Models\AbstractModel>, but the function expects a null|object<As3\Modlr\Models\Embed>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
193
        }
194
        return $serialized;
195
    }
196
197
    /**
198
     * Serializes a relationship value
199
     *
200
     * @param   Model                       $owner
201
     * @param   Model|Model[]|null          $relationship
202
     * @param   RelationshipMetadata        $relMeta
203
     * @param   AdapterInterface            $adapter
204
     * @return  array
205
     */
206
    protected function serializeRelationship(Model $owner, $relationship = null, RelationshipMetadata $relMeta, AdapterInterface $adapter)
207
    {
208
        if ($relMeta->isOne()) {
209
            if (is_array($relationship)) {
210
                throw SerializerException::badRequest('Invalid relationship value.');
211
            }
212
            $serialized = $this->serializeHasOne($owner, $relationship, $adapter);
213
        } elseif (is_array($relationship) || null === $relationship) {
214
            $serialized = $this->serializeHasMany($owner, $relationship, $adapter);
0 ignored issues
show
Bug introduced by
It seems like $relationship defined by parameter $relationship on line 206 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...
215
        } else {
216
            throw SerializerException::badRequest('Invalid relationship value.');
217
        }
218
219
        $ownerMeta = $owner->getMetadata();
220
        $serialized['links'] = [
221
            'self'      => $adapter->buildUrl($ownerMeta, $owner->getId(), $relMeta->getKey()),
222
            'related'   => $adapter->buildUrl($ownerMeta, $owner->getId(), $relMeta->getKey(), true),
223
        ];
224
        return $serialized;
225
    }
226
227
    /**
228
     * Serializes a has-many relationship value
229
     *
230
     * @param   Model                   $owner
231
     * @param   Model[]|null            $models
232
     * @param   AdapterInterface        $adapter
233
     * @return  array
234
     */
235
    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...
236
    {
237
        if (empty($models)) {
238
            return $this->serialize(null, $adapter);
239
        }
240
        return $this->serializeArray($models, $adapter);
241
    }
242
243
    /**
244
     * Serializes a has-one relationship value
245
     *
246
     * @param   Model                   $owner
247
     * @param   Model|null              $model
248
     * @param   AdapterInterface        $adapter
249
     * @return  array
250
     */
251
    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...
252
    {
253
        return $this->serialize($model, $adapter);
254
    }
255
256
    /**
257
     * Encodes the formatted payload array.
258
     *
259
     * @param   array   $payload
260
     * @return  string
261
     */
262
    private function encode(array $payload)
263
    {
264
        return json_encode($payload);
265
    }
266
267
    /**
268
     * Increases the serializer depth.
269
     *
270
     * @return  self
271
     */
272
    protected function increaseDepth()
273
    {
274
        $this->depth++;
275
        return $this;
276
    }
277
278
    /**
279
     * Decreases the serializer depth.
280
     *
281
     * @return  self
282
     */
283
    protected function decreaseDepth()
284
    {
285
        if ($this->depth > 0) {
286
            $this->depth--;
287
        }
288
        return $this;
289
    }
290
}
291