Completed
Pull Request — master (#273)
by Gonçalo
04:11
created

Scope::setParentScopes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
crap 1
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\Resource\Collection;
16
use League\Fractal\Resource\Item;
17
use League\Fractal\Resource\NullResource;
18
use League\Fractal\Resource\ResourceInterface;
19
use League\Fractal\Serializer\SerializerAbstract;
20
21
/**
22
 * Scope
23
 *
24
 * The scope class acts as a tracker, relating a specific resource in a specific
25
 * context. For example, the same resource could be attached to multiple scopes.
26
 * There are root scopes, parent scopes and child scopes.
27
 */
28
class Scope
29
{
30
    /**
31
     * @var array
32
     */
33
    protected $availableIncludes = [];
34
35
    /**
36
     * @var string
37
     */
38
    protected $scopeIdentifier;
39
40
    /**
41
     * @var \League\Fractal\Manager
42
     */
43
    protected $manager;
44
45
    /**
46
     * @var ResourceInterface
47
     */
48
    protected $resource;
49
50
    /**
51
     * @var array
52
     */
53
    protected $parentScopes = [];
54
55
    /**
56
     * Create a new scope instance.
57
     *
58
     * @param Manager           $manager
59
     * @param ResourceInterface $resource
60
     * @param string            $scopeIdentifier
61
     *
62
     * @return void
63
     */
64 60
    public function __construct(Manager $manager, ResourceInterface $resource, $scopeIdentifier = null)
65
    {
66 60
        $this->manager = $manager;
67 60
        $this->resource = $resource;
68 60
        $this->scopeIdentifier = $scopeIdentifier;
69 60
    }
70
71
    /**
72
     * Embed a scope as a child of the current scope.
73
     *
74
     * @internal
75
     *
76
     * @param string            $scopeIdentifier
77
     * @param ResourceInterface $resource
78
     *
79
     * @return \League\Fractal\Scope
80
     */
81 31
    public function embedChildScope($scopeIdentifier, $resource)
82
    {
83 31
        return $this->manager->createData($resource, $scopeIdentifier, $this);
84
    }
85
86
    /**
87
     * Get the current identifier.
88
     *
89
     * @return string
90
     */
91 31
    public function getScopeIdentifier()
92
    {
93 31
        return $this->scopeIdentifier;
94
    }
95
96
    /**
97
     * Get the unique identifier for this scope.
98
     *
99
     * @param string $appendIdentifier
100
     *
101
     * @return string
102
     */
103 27
    public function getIdentifier($appendIdentifier = null)
104
    {
105 27
        $identifierParts = array_merge($this->parentScopes, [$this->scopeIdentifier, $appendIdentifier]);
106
107 27
        return implode('.', array_filter($identifierParts));
108
    }
109
110
    /**
111
     * Getter for parentScopes.
112
     *
113
     * @return mixed
114
     */
115 32
    public function getParentScopes()
116
    {
117 32
        return $this->parentScopes;
118
    }
119
120
    /**
121
     * Getter for resource.
122
     *
123
     * @return ResourceInterface
124
     */
125 1
    public function getResource()
126
    {
127 1
        return $this->resource;
128
    }
129
130
    /**
131
     * Getter for manager.
132
     *
133
     * @return \League\Fractal\Manager
134
     */
135 27
    public function getManager()
136
    {
137 27
        return $this->manager;
138
    }
139
140
    /**
141
     * Is Requested.
142
     *
143
     * Check if - in relation to the current scope - this specific segment is allowed.
144
     * That means, if a.b.c is requested and the current scope is a.b, then c is allowed. If the current
145
     * scope is a then c is not allowed, even if it is there and potentially transformable.
146
     *
147
     * @internal
148
     *
149
     * @param string $checkScopeSegment
150
     *
151
     * @return bool Returns the new number of elements in the array.
152
     */
153 36 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...
154
    {
155 36
        if ($this->parentScopes) {
156 18
            $scopeArray = array_slice($this->parentScopes, 1);
157 18
            array_push($scopeArray, $this->scopeIdentifier, $checkScopeSegment);
158 18
        } else {
159 36
            $scopeArray = [$checkScopeSegment];
160
        }
161
162 36
        $scopeString = implode('.', (array) $scopeArray);
163
164 36
        return in_array($scopeString, $this->manager->getRequestedIncludes());
165
    }
166
167
    /**
168
     * Is Excluded.
169
     *
170
     * Check if - in relation to the current scope - this specific segment should
171
     * be excluded. That means, if a.b.c is excluded and the current scope is a.b,
172
     * then c will not be allowed in the transformation whether it appears in
173
     * the list of default or available, requested includes.
174
     *
175
     * @internal
176
     *
177
     * @param string $checkScopeSegment
178
     *
179
     * @return bool
180
     */
181 27 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...
182
    {
183 27
        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...
184 7
            $scopeArray = array_slice($this->parentScopes, 1);
185 7
            array_push($scopeArray, $this->scopeIdentifier, $checkScopeSegment);
186 7
        } else {
187 27
            $scopeArray = [$checkScopeSegment];
188
        }
189
190 27
        $scopeString = implode('.', (array) $scopeArray);
191
192 27
        return in_array($scopeString, $this->manager->getRequestedExcludes());
193
    }
194
195
    /**
196
     * Push Parent Scope.
197
     *
198
     * Push a scope identifier into parentScopes
199
     *
200
     * @internal
201
     *
202
     * @param string $identifierSegment
203
     *
204
     * @return int Returns the new number of elements in the array.
205
     */
206 1
    public function pushParentScope($identifierSegment)
207
    {
208 1
        return array_push($this->parentScopes, $identifierSegment);
209
    }
210
211
    /**
212
     * Set parent scopes.
213
     *
214
     * @internal
215
     *
216
     * @param string[] $parentScopes Value to set.
217
     *
218
     * @return $this
219
     */
220 31
    public function setParentScopes($parentScopes)
221
    {
222 31
        $this->parentScopes = $parentScopes;
223
224 31
        return $this;
225
    }
226
227
    /**
228
     * Convert the current data for this scope to an array.
229
     *
230
     * @return array
231
     */
232 56
    public function toArray()
233
    {
234 56
        list($rawData, $rawIncludedData) = $this->executeResourceTransformers();
235
236 55
        $serializer = $this->manager->getSerializer();
237
238 55
        $data = $this->serializeResource($serializer, $rawData);
239
240
        // If the serializer wants the includes to be side-loaded then we'll
241
        // serialize the included data and merge it with the data.
242 54
        if ($serializer->sideloadIncludes()) {
243
            //Filter out any relation that wasn't requested
244 31
            $rawIncludedData = array_map(array($this, 'filterFieldsets'), $rawIncludedData);
245
246 31
            $includedData = $serializer->includedData($this->resource, $rawIncludedData);
247
248
            // If the serializer wants to inject additional information
249
            // about the included resources, it can do so now.
250 31
            $data = $serializer->injectData($data, $rawIncludedData);
251
252 31
            if ($this->isRootScope()) {
253
                // If the serializer wants to have a final word about all
254
                // the objects that are sideloaded, it can do so now.
255 31
                $includedData = $serializer->filterIncludes(
256 31
                    $includedData,
257
                    $data
258 31
                );
259 31
            }
260
261 31
            $data = array_merge($data, $includedData);
262 31
        }
263
264 54
        if ($this->resource instanceof Collection) {
265 28
            if ($this->resource->hasCursor()) {
266 1
                $pagination = $serializer->cursor($this->resource->getCursor());
267 28
            } elseif ($this->resource->hasPaginator()) {
268 4
                $pagination = $serializer->paginator($this->resource->getPaginator());
269 4
            }
270
271 28
            if (! empty($pagination)) {
272 5
                $this->resource->setMetaValue(key($pagination), current($pagination));
273 5
            }
274 28
        }
275
276
        // Pull out all of OUR metadata and any custom meta data to merge with the main level data
277 54
        $meta = $serializer->meta($this->resource->getMeta());
278
279 54
        return array_merge($data, $meta);
280
    }
281
282
    /**
283
     * Convert the current data for this scope to JSON.
284
     *
285
     * @return string
286
     */
287 30
    public function toJson()
288
    {
289 30
        return json_encode($this->toArray());
290
    }
291
292
    /**
293
     * Execute the resources transformer and return the data and included data.
294
     *
295
     * @internal
296
     *
297
     * @return array
298
     */
299 49
    protected function executeResourceTransformers()
300
    {
301 49
        $transformer = $this->resource->getTransformer();
302 49
        $data = $this->resource->getData();
303
304 49
        $transformedData = $includedData = [];
305
306 49
        if ($this->resource instanceof Item) {
307 34
            list($transformedData, $includedData[]) = $this->fireTransformer($transformer, $data);
308 49
        } elseif ($this->resource instanceof Collection) {
309 28
            foreach ($data as $value) {
310 27
                list($transformedData[], $includedData[]) = $this->fireTransformer($transformer, $value);
311 28
            }
312 33
        } elseif ($this->resource instanceof NullResource) {
313 4
            $transformedData = null;
314 4
            $includedData = [];
315 4
        } else {
316 2
            throw new InvalidArgumentException(
317
                'Argument $resource should be an instance of League\Fractal\Resource\Item'
318
                .' or League\Fractal\Resource\Collection'
319 2
            );
320
        }
321
322 47
        return [$transformedData, $includedData];
323
    }
324
325
    /**
326
     * Serialize a resource
327
     *
328
     * @internal
329
     *
330
     * @param SerializerAbstract $serializer
331
     * @param mixed              $data
332
     *
333
     * @return array
334
     */
335 47
    protected function serializeResource(SerializerAbstract $serializer, $data)
336
    {
337 47
        $resourceKey = $this->resource->getResourceKey();
338
339 47
        if ($this->resource instanceof Collection) {
340 28
            return $serializer->collection($resourceKey, $data);
341
        }
342
343 36
        if ($this->resource instanceof Item) {
344 34
            return $serializer->item($resourceKey, $data);
345
        }
346
347 4
        return $serializer->null();
348
    }
349
350
    /**
351
     * Fire the main transformer.
352
     *
353
     * @internal
354
     *
355
     * @param TransformerAbstract|callable $transformer
356
     * @param mixed                        $data
357
     *
358
     * @return array
359
     */
360 45
    protected function fireTransformer($transformer, $data)
361
    {
362 45
        $includedData = [];
363
364 45
        if (is_callable($transformer)) {
365 4
            $transformedData = call_user_func($transformer, $data);
366 4
        } else {
367 41
            $transformer->setCurrentScope($this);
368 41
            $transformedData = $transformer->transform($data);
369
        }
370
371 45
        if ($this->transformerHasIncludes($transformer)) {
372 39
            $includedData = $this->fireIncludedTransformers($transformer, $data);
373 39
            $transformedData = $this->manager->getSerializer()->mergeIncludes($transformedData, $includedData);
374 39
        }
375
376
        //Stick only with requested fields
377 45
        $transformedData = $this->filterFieldsets($transformedData);
378
379 45
        return [$transformedData, $includedData];
380
    }
381
382
    /**
383
     * Fire the included transformers.
384
     *
385
     * @internal
386
     *
387
     * @param \League\Fractal\TransformerAbstract $transformer
388
     * @param mixed                               $data
389
     *
390
     * @return array
391
     */
392 39
    protected function fireIncludedTransformers($transformer, $data)
393
    {
394 39
        $this->availableIncludes = $transformer->getAvailableIncludes();
395
396 39
        return $transformer->processIncludedResources($this, $data) ?: [];
397
    }
398
399
    /**
400
     * Determine if a transformer has any available includes.
401
     *
402
     * @internal
403
     *
404
     * @param TransformerAbstract|callable $transformer
405
     *
406
     * @return bool
407
     */
408 45
    protected function transformerHasIncludes($transformer)
409
    {
410 45
        if (! $transformer instanceof TransformerAbstract) {
411 4
            return false;
412
        }
413
414 41
        $defaultIncludes = $transformer->getDefaultIncludes();
415 41
        $availableIncludes = $transformer->getAvailableIncludes();
416
417 41
        return ! empty($defaultIncludes) || ! empty($availableIncludes);
418
    }
419
420
    /**
421
     * Check, if this is the root scope.
422
     *
423
     * @return bool
424
     */
425 29
    protected function isRootScope()
426
    {
427 29
        return empty($this->parentScopes);
428
    }
429
430
    /**
431
     * Filter the provided data with the requested filter fieldset for
432
     * the scope resource
433
     *
434
     * @internal
435
     *
436
     * @param array  $data
437
     *
438
     * @return array
439
     */
440 45
    protected function filterFieldsets(array $data)
441
    {
442 45
        if (!$this->hasFilterFieldset()) {
443 41
            return $data;
444
        }
445 10
        $serializer = $this->manager->getSerializer();
446 10
        $requestedFieldset = iterator_to_array($this->getFilterFieldset());
447
        //Build the array of requested fieldsets with the mandatory serializer fields
448 10
        $filterFieldset = array_flip(
449 10
            array_merge(
450 10
                $serializer->getMandatoryFields(),
451
                $requestedFieldset
452 10
            )
453 10
        );
454 10
        return array_intersect_key($data, $filterFieldset);
455
    }
456
457
    /**
458
     * Return the requested filter fieldset for the scope resource
459
     *
460
     * @internal
461
     *
462
     * @return \League\Fractal\ParamBag|null
463
     */
464 45
    protected function getFilterFieldset()
465
    {
466 45
        return $this->manager->getFieldset($this->getResourceType());
467
    }
468
469
    /**
470
     * Check if there are requested filter fieldsets for the scope resource.
471
     *
472
     * @internal
473
     *
474
     * @return bool
475
     */
476 45
    protected function hasFilterFieldset()
477
    {
478 45
        return $this->getFilterFieldset() !== null;
479
    }
480
481
    /**
482
     * Return the scope resource type.
483
     *
484
     * @internal
485
     *
486
     * @return string
487
     */
488 45
    protected function getResourceType()
489
    {
490 45
        return $this->resource->getResourceKey();
491
    }
492
}
493