Completed
Pull Request — master (#272)
by Gonçalo
03:57
created

Scope::isExcluded()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 8

Duplication

Lines 13
Ratio 100 %

Code Coverage

Tests 8
CRAP Score 2

Importance

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