Completed
Pull Request — master (#304)
by Benoît
03:33
created

Scope   B

Complexity

Total Complexity 40

Size/Duplication

Total Lines 400
Duplicated Lines 6.5 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 99.14%

Importance

Changes 19
Bugs 7 Features 5
Metric Value
wmc 40
c 19
b 7
f 5
lcom 1
cbo 5
dl 26
loc 400
ccs 115
cts 116
cp 0.9914
rs 8.2608

19 Methods

Rating   Name   Duplication   Size   Complexity  
A embedChildScope() 0 4 1
A getScopeIdentifier() 0 4 1
A getParentScopes() 0 4 1
A getResource() 0 4 1
A getManager() 0 4 1
A pushParentScope() 0 4 1
A getIdentifier() 0 6 1
A isRequested() 13 13 2
A isExcluded() 13 13 2
A setParentScopes() 0 6 1
A __construct() 0 6 1
A toJson() 0 4 1
B executeResourceTransformers() 0 25 5
A fireTransformer() 0 18 3
A fireIncludedTransformers() 0 6 2
A transformerHasIncludes() 0 11 3
A isRootScope() 0 4 1
C toArray() 0 46 8
A serializeResource() 0 16 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Scope often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Scope, and based on these observations, apply Extract Interface, too.

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 55
    public function __construct(Manager $manager, ResourceInterface $resource, $scopeIdentifier = null)
65
    {
66 55
        $this->manager = $manager;
67 55
        $this->resource = $resource;
68 55
        $this->scopeIdentifier = $scopeIdentifier;
69 55
    }
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 27
    public function embedChildScope($scopeIdentifier, $resource)
82
    {
83 27
        return $this->manager->createData($resource, $scopeIdentifier, $this);
84
    }
85
86
    /**
87
     * Get the current identifier.
88
     *
89
     * @return string
90
     */
91 27
    public function getScopeIdentifier()
92
    {
93 27
        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 23
    public function getIdentifier($appendIdentifier = null)
104
    {
105 23
        $identifierParts = array_merge($this->parentScopes, [$this->scopeIdentifier, $appendIdentifier]);
106
107 23
        return implode('.', array_filter($identifierParts));
108
    }
109
110
    /**
111
     * Getter for parentScopes.
112
     *
113
     * @return mixed
114
     */
115 28
    public function getParentScopes()
116
    {
117 28
        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 23
    public function getManager()
136
    {
137 23
        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 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...
154
    {
155 32
        if ($this->parentScopes) {
156 14
            $scopeArray = array_slice($this->parentScopes, 1);
157 14
            array_push($scopeArray, $this->scopeIdentifier, $checkScopeSegment);
158 14
        } else {
159 32
            $scopeArray = [$checkScopeSegment];
160
        }
161
162 32
        $scopeString = implode('.', (array) $scopeArray);
163
164 32
        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 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...
182
    {
183 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...
184 3
            $scopeArray = array_slice($this->parentScopes, 1);
185 3
            array_push($scopeArray, $this->scopeIdentifier, $checkScopeSegment);
186 3
        } else {
187 23
            $scopeArray = [$checkScopeSegment];
188
        }
189
190 23
        $scopeString = implode('.', (array) $scopeArray);
191
192 23
        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 27
    public function setParentScopes($parentScopes)
221
    {
222 27
        $this->parentScopes = $parentScopes;
223
224 27
        return $this;
225
    }
226
227
    /**
228
     * Convert the current data for this scope to an array.
229
     *
230
     * In case Serializer used could return null, this could also return null
231
     *
232
     * @return array|null
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 24
            if ($this->resource->hasCursor()) {
265 1
                $pagination = $serializer->cursor($this->resource->getCursor());
266 24
            } elseif ($this->resource->hasPaginator()) {
267 4
                $pagination = $serializer->paginator($this->resource->getPaginator());
268 4
            }
269
270 24
            if (! empty($pagination)) {
271 5
                $this->resource->setMetaValue(key($pagination), current($pagination));
272 5
            }
273 24
        }
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 null === $data ? null : array_merge($data, $meta);
279
    }
280
281
    /**
282
     * Convert the current data for this scope to JSON.
283
     *
284
     * @return string
285
     */
286 31
    public function toJson()
287
    {
288 31
        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 28
            list($transformedData, $includedData[]) = $this->fireTransformer($transformer, $data);
307 44
        } elseif ($this->resource instanceof Collection) {
308 24
            foreach ($data as $value) {
309 23
                list($transformedData[], $includedData[]) = $this->fireTransformer($transformer, $value);
310 24
            }
311 30
        } elseif ($this->resource instanceof NullResource) {
312 5
            $transformedData = null;
313 5
            $includedData = [];
314 5
        } 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 (null !== $data) {
339 39
            if ($this->resource instanceof Collection) {
340 24
                return $serializer->collection($resourceKey, $data);
341
            }
342
343 28
            if ($this->resource instanceof Item) {
344 28
                return $serializer->item($resourceKey, $data);
345
            }
346
        }
347
348 5
        return $serializer->null($resourceKey);
349
    }
350
351
    /**
352
     * Fire the main transformer.
353
     *
354
     * @internal
355
     *
356
     * @param TransformerAbstract|callable $transformer
357
     * @param mixed                        $data
358
     *
359
     * @return array
360
     */
361 39
    protected function fireTransformer($transformer, $data)
362
    {
363 39
        $includedData = [];
364
365 39
        if (is_callable($transformer)) {
366 4
            $transformedData = call_user_func($transformer, $data);
367 4
        } else {
368 35
            $transformer->setCurrentScope($this);
369 35
            $transformedData = $transformer->transform($data);
370
        }
371
372 39
        if ($this->transformerHasIncludes($transformer)) {
373 33
            $includedData = $this->fireIncludedTransformers($transformer, $data);
374 33
            $transformedData = $this->manager->getSerializer()->mergeIncludes($transformedData, $includedData);
375 33
        }
376
377 39
        return [$transformedData, $includedData];
378
    }
379
380
    /**
381
     * Fire the included transformers.
382
     *
383
     * @internal
384
     *
385
     * @param \League\Fractal\TransformerAbstract $transformer
386
     * @param mixed                               $data
387
     *
388
     * @return array
389
     */
390 33
    protected function fireIncludedTransformers($transformer, $data)
391
    {
392 33
        $this->availableIncludes = $transformer->getAvailableIncludes();
393
394 33
        return $transformer->processIncludedResources($this, $data) ?: [];
395
    }
396
397
    /**
398
     * Determine if a transformer has any available includes.
399
     *
400
     * @internal
401
     *
402
     * @param TransformerAbstract|callable $transformer
403
     *
404
     * @return bool
405
     */
406 39
    protected function transformerHasIncludes($transformer)
407
    {
408 39
        if (! $transformer instanceof TransformerAbstract) {
409 4
            return false;
410
        }
411
412 35
        $defaultIncludes = $transformer->getDefaultIncludes();
413 35
        $availableIncludes = $transformer->getAvailableIncludes();
414
415 35
        return ! empty($defaultIncludes) || ! empty($availableIncludes);
416
    }
417
418
    /**
419
     * Check, if this is the root scope.
420
     *
421
     * @return bool
422
     */
423 25
    protected function isRootScope()
424
    {
425 25
        return empty($this->parentScopes);
426
    }
427
}
428