Completed
Pull Request — master (#293)
by
unknown
02:49
created

Scope::getHalTransformedData()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 11
ccs 6
cts 6
cp 1
rs 9.4285
cc 2
eloc 7
nc 2
nop 3
crap 2
1
<?php
2
3
/*
4
 * This file is part of the League\Fractal package.
5
 *
6
 * (c) Phil Sturgeon <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 **/
11
12
namespace League\Fractal;
13
14
use InvalidArgumentException;
15
use League\Fractal\Hal\HalTransformer;
16
use League\Fractal\Resource\Collection;
17
use League\Fractal\Resource\Item;
18
use League\Fractal\Resource\NullResource;
19
use League\Fractal\Resource\ResourceInterface;
20
use League\Fractal\Serializer\SerializerAbstract;
21
use League\Fractal\Hal\HalInterface;
22
23
/**
24
 * Scope
25
 *
26
 * The scope class acts as a tracker, relating a specific resource in a specific
27
 * context. For example, the same resource could be attached to multiple scopes.
28
 * There are root scopes, parent scopes and child scopes.
29
 */
30
class Scope
31
{
32
    /**
33
     * @var array
34
     */
35
    protected $availableIncludes = [];
36
37
    /**
38
     * @var string
39
     */
40
    protected $scopeIdentifier;
41
42
    /**
43
     * @var \League\Fractal\Manager
44
     */
45
    protected $manager;
46
47
    /**
48
     * @var ResourceInterface
49
     */
50
    protected $resource;
51
52
    /**
53
     * @var array
54
     */
55
    protected $parentScopes = [];
56
57
    /**
58
     * Create a new scope instance.
59
     *
60
     * @param Manager           $manager
61
     * @param ResourceInterface $resource
62
     * @param string            $scopeIdentifier
63
     *
64
     * @return void
65
     */
66 55
    public function __construct(Manager $manager, ResourceInterface $resource, $scopeIdentifier = null)
67
    {
68 55
        $this->manager = $manager;
69 55
        $this->resource = $resource;
70 55
        $this->scopeIdentifier = $scopeIdentifier;
71 55
    }
72
73
    /**
74
     * Embed a scope as a child of the current scope.
75
     *
76
     * @internal
77
     *
78
     * @param string            $scopeIdentifier
79
     * @param ResourceInterface $resource
80
     *
81
     * @return \League\Fractal\Scope
82
     */
83 28
    public function embedChildScope($scopeIdentifier, $resource)
84
    {
85 28
        return $this->manager->createData($resource, $scopeIdentifier, $this);
86
    }
87
88
    /**
89
     * Get the current identifier.
90
     *
91
     * @return string
92
     */
93 28
    public function getScopeIdentifier()
94
    {
95 28
        return $this->scopeIdentifier;
96
    }
97
98
    /**
99
     * Get the unique identifier for this scope.
100
     *
101
     * @param string $appendIdentifier
102
     *
103
     * @return string
104
     */
105 24
    public function getIdentifier($appendIdentifier = null)
106
    {
107 24
        $identifierParts = array_merge($this->parentScopes, [$this->scopeIdentifier, $appendIdentifier]);
108
109 24
        return implode('.', array_filter($identifierParts));
110
    }
111
112
    /**
113
     * Getter for parentScopes.
114
     *
115
     * @return mixed
116
     */
117 29
    public function getParentScopes()
118
    {
119 29
        return $this->parentScopes;
120
    }
121
122
    /**
123
     * Getter for resource.
124
     *
125
     * @return ResourceInterface
126
     */
127 1
    public function getResource()
128
    {
129 1
        return $this->resource;
130
    }
131
132
    /**
133
     * Getter for manager.
134
     *
135
     * @return \League\Fractal\Manager
136
     */
137 24
    public function getManager()
138
    {
139 24
        return $this->manager;
140
    }
141
142
    /**
143
     * Is Requested.
144
     *
145
     * Check if - in relation to the current scope - this specific segment is allowed.
146
     * That means, if a.b.c is requested and the current scope is a.b, then c is allowed. If the current
147
     * scope is a then c is not allowed, even if it is there and potentially transformable.
148
     *
149
     * @internal
150
     *
151
     * @param string $checkScopeSegment
152
     *
153
     * @return bool Returns the new number of elements in the array.
154
     */
155 32 View Code Duplication
    public function isRequested($checkScopeSegment)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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.

Loading history...
156
    {
157 32
        if ($this->parentScopes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->parentScopes of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
158 14
            $scopeArray = array_slice($this->parentScopes, 1);
159 14
            array_push($scopeArray, $this->scopeIdentifier, $checkScopeSegment);
160 14
        } else {
161 32
            $scopeArray = [$checkScopeSegment];
162
        }
163
164 32
        $scopeString = implode('.', (array) $scopeArray);
165
166 32
        return in_array($scopeString, $this->manager->getRequestedIncludes());
167
    }
168
169
    /**
170
     * Is Excluded.
171
     *
172
     * Check if - in relation to the current scope - this specific segment should
173
     * be excluded. That means, if a.b.c is excluded and the current scope is a.b,
174
     * then c will not be allowed in the transformation whether it appears in
175
     * the list of default or available, requested includes.
176
     *
177
     * @internal
178
     *
179
     * @param string $checkScopeSegment
180
     *
181
     * @return bool
182
     */
183 24 View Code Duplication
    public function isExcluded($checkScopeSegment)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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.

Loading history...
184
    {
185 24
        if ($this->parentScopes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->parentScopes of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
186 3
            $scopeArray = array_slice($this->parentScopes, 1);
187 3
            array_push($scopeArray, $this->scopeIdentifier, $checkScopeSegment);
188 3
        } else {
189 24
            $scopeArray = [$checkScopeSegment];
190
        }
191
192 24
        $scopeString = implode('.', (array) $scopeArray);
193
194 24
        return in_array($scopeString, $this->manager->getRequestedExcludes());
195
    }
196
197
    /**
198
     * Push Parent Scope.
199
     *
200
     * Push a scope identifier into parentScopes
201
     *
202
     * @internal
203
     *
204
     * @param string $identifierSegment
205
     *
206
     * @return int Returns the new number of elements in the array.
207
     */
208 1
    public function pushParentScope($identifierSegment)
209
    {
210 1
        return array_push($this->parentScopes, $identifierSegment);
211
    }
212
213
    /**
214
     * Set parent scopes.
215
     *
216
     * @internal
217
     *
218
     * @param string[] $parentScopes Value to set.
219
     *
220
     * @return $this
221
     */
222 28
    public function setParentScopes($parentScopes)
223
    {
224 28
        $this->parentScopes = $parentScopes;
225
226 28
        return $this;
227
    }
228
229
    /**
230
     * Convert the current data for this scope to an array.
231
     *
232
     * @return array
233
     */
234 44
    public function toArray()
235
    {
236 44
        list($rawData, $rawIncludedData) = $this->executeResourceTransformers();
237
238 43
        $serializer = $this->manager->getSerializer();
239
240 43
        $data = $this->serializeResource($serializer, $rawData);
241
242
        // If the serializer wants the includes to be side-loaded then we'll
243
        // serialize the included data and merge it with the data.
244 42
        if ($serializer->sideloadIncludes()) {
245 25
            $includedData = $serializer->includedData($this->resource, $rawIncludedData);
246
247
            // If the serializer wants to inject additional information
248
            // about the included resources, it can do so now.
249 25
            $data = $serializer->injectData($data, $rawIncludedData);
250
251 25
            if ($this->isRootScope()) {
252
                // If the serializer wants to have a final word about all
253
                // the objects that are sideloaded, it can do so now.
254 25
                $includedData = $serializer->filterIncludes(
255 25
                    $includedData,
256
                    $data
257 25
                );
258 25
            }
259
260 25
            $data = array_merge($data, $includedData);
261 25
        }
262
263 42
        if ($this->resource instanceof Collection) {
264 25
            if ($this->resource->hasCursor()) {
265 1
                $pagination = $serializer->cursor($this->resource->getCursor());
266 25
            } elseif ($this->resource->hasPaginator()) {
267 4
                $pagination = $serializer->paginator($this->resource->getPaginator());
268 4
            }
269
270 25
            if (! empty($pagination)) {
271 5
                $this->resource->setMetaValue(key($pagination), current($pagination));
272 5
            }
273 25
        }
274
275
        // Pull out all of OUR metadata and any custom meta data to merge with the main level data
276 42
        $meta = $serializer->meta($this->resource->getMeta());
277
278 42
        return array_merge($data, $meta);
279
    }
280
281
    /**
282
     * Convert the current data for this scope to JSON.
283
     *
284
     * @return string
285
     */
286 30
    public function toJson()
287
    {
288 30
        return json_encode($this->toArray());
289
    }
290
291
    /**
292
     * Execute the resources transformer and return the data and included data.
293
     *
294
     * @internal
295
     *
296
     * @return array
297
     */
298 44
    protected function executeResourceTransformers()
299
    {
300 44
        $transformer = $this->resource->getTransformer();
301 44
        $data = $this->resource->getData();
302
303 44
        $transformedData = $includedData = [];
304
305 44
        if ($this->resource instanceof Item) {
306 29
            list($transformedData, $includedData[]) = $this->fireTransformer($transformer, $data);
307 44
        } elseif ($this->resource instanceof Collection) {
308 25
            foreach ($data as $value) {
309 23
                list($transformedData[], $includedData[]) = $this->fireTransformer($transformer, $value);
310 25
            }
311 30
        } elseif ($this->resource instanceof NullResource) {
312 4
            $transformedData = null;
313 4
            $includedData = [];
314 4
        } else {
315 2
            throw new InvalidArgumentException(
316
                'Argument $resource should be an instance of League\Fractal\Resource\Item'
317
                .' or League\Fractal\Resource\Collection'
318 2
            );
319
        }
320
321 42
        return [$transformedData, $includedData];
322
    }
323
324
    /**
325
     * Serialize a resource
326
     *
327
     * @internal
328
     *
329
     * @param SerializerAbstract $serializer
330
     * @param mixed              $data
331
     *
332
     * @return array
333
     */
334 42
    protected function serializeResource(SerializerAbstract $serializer, $data)
335
    {
336 42
        $resourceKey = $this->resource->getResourceKey();
337
338 42
        if ($this->resource instanceof Collection) {
339 25
            return $serializer->collection($resourceKey, $data);
340
        }
341
342 31
        if ($this->resource instanceof Item) {
343 29
            return $serializer->item($resourceKey, $data);
344
        }
345
346 4
        return $serializer->null();
347
    }
348
349
    /**
350
     * Fire the main transformer.
351
     *
352
     * @internal
353
     *
354
     * @param TransformerAbstract|callable $transformer
355
     * @param mixed                        $data
356
     *
357
     * @return array
358
     */
359 40
    protected function fireTransformer($transformer, $data)
360
    {
361 40
        $includedData = [];
362
363 40
        if (is_callable($transformer)) {
364 4
            $transformedData = call_user_func($transformer, $data);
365 4
        } else {
366 36
            $transformer->setCurrentScope($this);
367 36
            $transformedData = $transformer->transform($data);
368
        }
369
370 40
        list($transformedData, $includedData) = $this->getIncludes($transformer, $data, $transformedData, $includedData);
371
372 40
        return $this->getHalTransformedData($data, $transformedData, $includedData);
373
    }
374
375 40
    protected function getHalTransformedData($data, $transformedData, $includedData)
376
    {
377 40
        if ($data instanceof HalInterface) {
378 1
            $transformer = new HalTransformer();
379 1
            $transformedData += [HalInterface::STRUCTURE_KEY => $transformer->transform($data)];
380
381 1
            return $this->getIncludes($transformer, $data, $transformedData, $includedData);
0 ignored issues
show
Documentation introduced by
$transformer is of type object<League\Fractal\Hal\HalTransformer>, but the function expects a callable.

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...
Documentation introduced by
$data is of type object<League\Fractal\Hal\HalInterface>, but the function expects a array.

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...
382
        } else {
383 39
            return [$transformedData, $includedData];
384
        }
385
    }
386
387
    /**
388
     * Check and get transformer include data.
389
     *
390
     * @param TransformerAbstract|callable $transformer
391
     * @param array $data
392
     * @param array $transformedData
393
     * @param array $includedData
394
     *
395
     * @return array
396
     */
397 40
    protected function getIncludes($transformer, $data, $transformedData, $includedData)
398
    {
399 40
        if ($this->transformerHasIncludes($transformer)) {
400 34
            $includedData += $this->fireIncludedTransformers($transformer, $data);
0 ignored issues
show
Documentation introduced by
$transformer is of type callable, but the function expects a object<League\Fractal\TransformerAbstract>.

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...
401 34
            $transformedData = $this->manager->getSerializer()->mergeIncludes($transformedData, $includedData);
402 34
        }
403
404 40
        return [$transformedData, $includedData];
405
    }
406
407
    /**
408
     * Fire the included transformers.
409
     *
410
     * @internal
411
     *
412
     * @param \League\Fractal\TransformerAbstract $transformer
413
     * @param mixed                               $data
414
     *
415
     * @return array
416
     */
417 34
    protected function fireIncludedTransformers($transformer, $data)
418
    {
419 34
        $this->availableIncludes = $transformer->getAvailableIncludes();
420
421 34
        return $transformer->processIncludedResources($this, $data) ?: [];
422
    }
423
424
    /**
425
     * Determine if a transformer has any available includes.
426
     *
427
     * @internal
428
     *
429
     * @param TransformerAbstract|callable $transformer
430
     *
431
     * @return bool
432
     */
433 40
    protected function transformerHasIncludes($transformer)
434
    {
435 40
        if (! $transformer instanceof TransformerAbstract) {
436 4
            return false;
437
        }
438
439 36
        $defaultIncludes = $transformer->getDefaultIncludes();
440 36
        $availableIncludes = $transformer->getAvailableIncludes();
441
442 36
        return ! empty($defaultIncludes) || ! empty($availableIncludes);
443
    }
444
445
    /**
446
     * Check, if this is the root scope.
447
     *
448
     * @return bool
449
     */
450 25
    protected function isRootScope()
451
    {
452 25
        return empty($this->parentScopes);
453
    }
454
}
455