This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
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
|
|||
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, $collection->getTotalCount()); |
||
50 | } |
||
51 | |||
52 | /** |
||
53 | * {@inheritDoc} |
||
54 | */ |
||
55 | public function serializeArray(array $models, AdapterInterface $adapter, $totalCount = null) |
||
56 | { |
||
57 | $serialized = []; |
||
58 | if (0 === $this->depth && !empty($totalCount)) { |
||
59 | $serialized = $this->appendPaginationLinks($adapter, $totalCount, $serialized); |
||
60 | } |
||
61 | $serialized['data'] = []; |
||
62 | foreach ($models as $model) { |
||
63 | $serialized['data'][] = $this->serializeModel($model, $adapter); |
||
64 | } |
||
65 | return (0 === $this->depth) ? $this->encode($serialized) : $serialized; |
||
66 | } |
||
67 | |||
68 | /** |
||
69 | * {@inheritDoc} |
||
70 | */ |
||
71 | public function serializeError($title, $message, $httpCode) |
||
72 | { |
||
73 | return $this->encode([ |
||
74 | 'errors' => [ |
||
75 | ['status' => (String) $httpCode, 'title' => $title, 'detail' => $message], |
||
76 | ], |
||
77 | ]); |
||
78 | } |
||
79 | |||
80 | /** |
||
81 | * Appends the pagination links to the serialized data. |
||
82 | * |
||
83 | * @param AdapterInterface $adapter |
||
84 | * @param int $totalCount |
||
85 | * @param array $serialized |
||
86 | * @return array |
||
87 | */ |
||
88 | protected function appendPaginationLinks(AdapterInterface $adapter, $totalCount, array $serialized) |
||
89 | { |
||
90 | $totalCount = (Integer) $totalCount; |
||
91 | $original = $adapter->getRequest(); |
||
92 | $pagination = $original->getPagination(); |
||
93 | $displayed = $pagination['offset'] + $pagination['limit']; |
||
94 | |||
95 | $request = clone $adapter->getRequest(); |
||
96 | if (false === $request->hasPagination()) { |
||
97 | return $serialized; |
||
98 | } |
||
99 | |||
100 | // first |
||
101 | $request->setPagination(0, $pagination['limit']); |
||
102 | $first = $request->getUrl(); |
||
103 | |||
104 | // last |
||
105 | $lastOffset = floor($totalCount / $pagination['limit']); |
||
106 | $request->setPagination($lastOffset * $pagination['limit'], $pagination['limit']); |
||
107 | $last = $request->getUrl(); |
||
108 | |||
109 | // prev |
||
110 | $prev = null; |
||
111 | if ($pagination['offset'] > 0) { |
||
112 | $request->setPagination($pagination['offset'] - $pagination['limit'], $pagination['limit']); |
||
113 | $prev = $request->getUrl(); |
||
114 | } |
||
115 | |||
116 | // next |
||
117 | $next = null; |
||
118 | if ($displayed < $totalCount) { |
||
119 | $request->setPagination($pagination['offset'] + $pagination['limit'], $pagination['limit']); |
||
120 | $next = $request->getUrl(); |
||
121 | } |
||
122 | $serialized['links'] = ['first' => $first, 'last' => $last, 'prev' => $prev, 'next' => $next]; |
||
123 | return $serialized; |
||
124 | } |
||
125 | |||
126 | /** |
||
127 | * Serializes the "interior" of a model. |
||
128 | * This is the serialization that takes place outside of a "data" container. |
||
129 | * Can be used for root model and relationship model serialization. |
||
130 | * |
||
131 | * @param Model $model |
||
132 | * @param AdapterInterface $adapter |
||
133 | * @return array |
||
134 | */ |
||
135 | protected function serializeModel(Model $model, AdapterInterface $adapter) |
||
136 | { |
||
137 | $metadata = $model->getMetadata(); |
||
138 | $serialized = [ |
||
139 | 'type' => $model->getType(), |
||
140 | 'id' => $model->getId(), |
||
141 | ]; |
||
142 | if ($this->depth > 0) { |
||
143 | // $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. ![]() |
|||
144 | return $serialized; |
||
145 | } |
||
146 | |||
147 | View Code Duplication | foreach ($metadata->getAttributes() as $key => $attrMeta) { |
|
0 ignored issues
–
show
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. ![]() |
|||
148 | if (false === $attrMeta->shouldSerialize()) { |
||
149 | continue; |
||
150 | } |
||
151 | $value = $model->get($key); |
||
152 | $serialized['attributes'][$key] = $this->serializeAttribute($value, $attrMeta); |
||
153 | } |
||
154 | |||
155 | View Code Duplication | foreach ($metadata->getEmbeds() as $key => $embeddedPropMeta) { |
|
0 ignored issues
–
show
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. ![]() |
|||
156 | if (false === $attrMeta->shouldSerialize()) { |
||
0 ignored issues
–
show
The variable
$attrMeta seems to be defined by a foreach iteration on line 147 . Are you sure the iterator is never empty, otherwise this variable is not defined?
It seems like you are relying on a variable being defined by an iteration: foreach ($a as $b) {
}
// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.
// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}
// $b is now guaranteed to be defined here.
![]() |
|||
157 | continue; |
||
158 | } |
||
159 | $value = $model->get($key); |
||
160 | $serialized['attributes'][$key] = $this->serializeEmbed($value, $embeddedPropMeta); |
||
161 | } |
||
162 | |||
163 | $serialized['links'] = ['self' => $adapter->buildUrl($metadata, $model->getId())]; |
||
164 | |||
165 | $model->enableCollectionAutoInit(false); |
||
166 | $this->increaseDepth(); |
||
167 | foreach ($metadata->getRelationships() as $key => $relMeta) { |
||
168 | if (false === $relMeta->shouldSerialize()) { |
||
169 | continue; |
||
170 | } |
||
171 | $relationship = $model->get($key); |
||
172 | $serialized['relationships'][$key] = $this->serializeRelationship($model, $relationship, $relMeta, $adapter); |
||
173 | } |
||
174 | $this->decreaseDepth(); |
||
175 | $model->enableCollectionAutoInit(true); |
||
176 | return $serialized; |
||
177 | } |
||
178 | |||
179 | /** |
||
180 | * Serializes an attribute value. |
||
181 | * |
||
182 | * @param mixed $value |
||
183 | * @param AttributeMetadata $attrMeta |
||
184 | * @return mixed |
||
185 | */ |
||
186 | protected function serializeAttribute($value, AttributeMetadata $attrMeta) |
||
187 | { |
||
188 | if ('date' === $attrMeta->dataType && $value instanceof \DateTime) { |
||
189 | $milliseconds = sprintf('%03d', round($value->format('u') / 1000, 0)); |
||
190 | return gmdate(sprintf('Y-m-d\TH:i:s.%s\Z', $milliseconds), $value->getTimestamp()); |
||
191 | } |
||
192 | if ('array' === $attrMeta->dataType && empty($value)) { |
||
193 | return []; |
||
194 | } |
||
195 | if ('object' === $attrMeta->dataType) { |
||
196 | return (array) $value; |
||
197 | } |
||
198 | return $value; |
||
199 | } |
||
200 | |||
201 | /** |
||
202 | * Serializes an embed value. |
||
203 | * |
||
204 | * @param Embed|EmbedCollection|null $value |
||
205 | * @param EmbeddedPropMetadata $embeddedPropMeta |
||
206 | * @return array|null |
||
207 | */ |
||
208 | protected function serializeEmbed($value, EmbeddedPropMetadata $embeddedPropMeta) |
||
209 | { |
||
210 | $embedMeta = $embeddedPropMeta->embedMeta; |
||
211 | if (true === $embeddedPropMeta->isOne()) { |
||
212 | return $this->serializeEmbedOne($embedMeta, $value); |
||
213 | } |
||
214 | return $this->serializeEmbedMany($embedMeta, $value); |
||
215 | } |
||
216 | |||
217 | /** |
||
218 | * Serializes an embed one value. |
||
219 | * |
||
220 | * @param EmbedMetadata $embedMeta |
||
221 | * @param Embed|null $embed |
||
222 | * @return array|null |
||
223 | */ |
||
224 | protected function serializeEmbedOne(EmbedMetadata $embedMeta, Embed $embed = null) |
||
225 | { |
||
226 | if (null === $embed) { |
||
227 | return; |
||
228 | } |
||
229 | $serialized = []; |
||
230 | foreach ($embedMeta->getAttributes() as $key => $attrMeta) { |
||
231 | $serialized[$key] = $this->serializeAttribute($embed->get($key), $attrMeta); |
||
232 | } |
||
233 | foreach ($embedMeta->getEmbeds() as $key => $embeddedPropMeta) { |
||
234 | $serialized[$key] = $this->serializeEmbed($embed->get($key), $embeddedPropMeta); |
||
235 | } |
||
236 | |||
237 | return empty($serialized) ? null : $serialized; |
||
238 | } |
||
239 | |||
240 | /** |
||
241 | * Serializes an embed many value. |
||
242 | * |
||
243 | * @param EmbedMetadata $embedMeta |
||
244 | * @param EmbedCollection $embed |
||
0 ignored issues
–
show
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 /**
* @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. ![]() |
|||
245 | * @return array |
||
246 | */ |
||
247 | protected function serializeEmbedMany(EmbedMetadata $embedMeta, EmbedCollection $collection) |
||
248 | { |
||
249 | $serialized = []; |
||
250 | foreach ($collection as $embed) { |
||
251 | if (!$embed instanceof Embed) { |
||
252 | continue; |
||
253 | } |
||
254 | $serialized[] = $this->serializeEmbedOne($embedMeta, $embed); |
||
255 | } |
||
256 | return $serialized; |
||
257 | } |
||
258 | |||
259 | /** |
||
260 | * Serializes a relationship value |
||
261 | * |
||
262 | * @param Model $owner |
||
263 | * @param Model|Model[]|null $relationship |
||
264 | * @param RelationshipMetadata $relMeta |
||
265 | * @param AdapterInterface $adapter |
||
266 | * @return array |
||
267 | */ |
||
268 | protected function serializeRelationship(Model $owner, $relationship = null, RelationshipMetadata $relMeta, AdapterInterface $adapter) |
||
269 | { |
||
270 | if ($relMeta->isOne()) { |
||
271 | if (is_array($relationship)) { |
||
272 | throw SerializerException::badRequest('Invalid relationship value.'); |
||
273 | } |
||
274 | $serialized = $this->serializeHasOne($owner, $relationship, $adapter); |
||
275 | } elseif (is_array($relationship) || null === $relationship) { |
||
276 | $serialized = $this->serializeHasMany($owner, $relationship, $adapter); |
||
0 ignored issues
–
show
It seems like
$relationship defined by parameter $relationship on line 268 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. ![]() |
|||
277 | } else { |
||
278 | throw SerializerException::badRequest('Invalid relationship value.'); |
||
279 | } |
||
280 | |||
281 | $ownerMeta = $owner->getMetadata(); |
||
282 | $serialized['links'] = [ |
||
283 | 'self' => $adapter->buildUrl($ownerMeta, $owner->getId(), $relMeta->getKey()), |
||
284 | 'related' => $adapter->buildUrl($ownerMeta, $owner->getId(), $relMeta->getKey(), true), |
||
285 | ]; |
||
286 | return $serialized; |
||
287 | } |
||
288 | |||
289 | /** |
||
290 | * Serializes a has-many relationship value |
||
291 | * |
||
292 | * @param Model $owner |
||
293 | * @param Model[]|null $models |
||
294 | * @param AdapterInterface $adapter |
||
295 | * @return array |
||
296 | */ |
||
297 | protected function serializeHasMany(Model $owner, array $models = null, AdapterInterface $adapter) |
||
0 ignored issues
–
show
|
|||
298 | { |
||
299 | if (empty($models)) { |
||
300 | return $this->serialize(null, $adapter); |
||
301 | } |
||
302 | return $this->serializeArray($models, $adapter); |
||
303 | } |
||
304 | |||
305 | /** |
||
306 | * Serializes a has-one relationship value |
||
307 | * |
||
308 | * @param Model $owner |
||
309 | * @param Model|null $model |
||
310 | * @param AdapterInterface $adapter |
||
311 | * @return array |
||
312 | */ |
||
313 | protected function serializeHasOne(Model $owner, Model $model = null, AdapterInterface $adapter) |
||
0 ignored issues
–
show
|
|||
314 | { |
||
315 | return $this->serialize($model, $adapter); |
||
316 | } |
||
317 | |||
318 | /** |
||
319 | * Encodes the formatted payload array. |
||
320 | * |
||
321 | * @param array $payload |
||
322 | * @return string |
||
323 | */ |
||
324 | private function encode(array $payload) |
||
325 | { |
||
326 | return json_encode($payload); |
||
327 | } |
||
328 | |||
329 | /** |
||
330 | * Increases the serializer depth. |
||
331 | * |
||
332 | * @return self |
||
333 | */ |
||
334 | protected function increaseDepth() |
||
335 | { |
||
336 | $this->depth++; |
||
337 | return $this; |
||
338 | } |
||
339 | |||
340 | /** |
||
341 | * Decreases the serializer depth. |
||
342 | * |
||
343 | * @return self |
||
344 | */ |
||
345 | protected function decreaseDepth() |
||
346 | { |
||
347 | if ($this->depth > 0) { |
||
348 | $this->depth--; |
||
349 | } |
||
350 | return $this; |
||
351 | } |
||
352 | } |
||
353 |
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:
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 thebar
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.