Completed
Push — master ( e86b6e...1708fa )
by Jodie
02:29
created

Pipeline   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 269
Duplicated Lines 0 %

Test Coverage

Coverage 94.79%

Importance

Changes 0
Metric Value
wmc 44
dl 0
loc 269
ccs 91
cts 96
cp 0.9479
rs 8.8798
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A setTransformerResolver() 0 3 1
A __construct() 0 2 1
A setSerializer() 0 3 1
A setRelationLoader() 0 3 1
A resolveTransformerForResource() 0 9 2
B transformData() 0 44 7
A loadRelations() 0 4 3
A executeTransformerInclude() 0 11 2
B transform() 0 49 11
A autoWireInclude() 0 18 6
A getSerializer() 0 3 1
B serialize() 0 26 8

How to fix   Complexity   

Complex Class

Complex classes like Pipeline 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.

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 Pipeline, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Rexlabs\Smokescreen\Transformer;
4
5
use Rexlabs\Smokescreen\Exception\IncludeException;
6
use Rexlabs\Smokescreen\Exception\InvalidTransformerException;
7
use Rexlabs\Smokescreen\Exception\UnhandledResourceType;
8
use Rexlabs\Smokescreen\Helpers\ArrayHelper;
9
use Rexlabs\Smokescreen\Relations\RelationLoaderInterface;
10
use Rexlabs\Smokescreen\Resource\Collection;
11
use Rexlabs\Smokescreen\Resource\Item;
12
use Rexlabs\Smokescreen\Resource\ResourceInterface;
13
use Rexlabs\Smokescreen\Serializer\SerializerInterface;
14
15
class Pipeline
16
{
17
    /** @var SerializerInterface */
18
    protected $serializer;
19
20
    /** @var TransformerResolverInterface|null */
21
    protected $transformerResolver;
22
23
    /** @var RelationLoaderInterface|null */
24
    protected $relationLoader;
25
26 16
    public function __construct()
27
    {
28 16
    }
29
30 2
    public function setTransformerResolver(TransformerResolverInterface $transformerResolver)
31
    {
32 2
        $this->transformerResolver = $transformerResolver;
33 2
    }
34
35 16
    public function setSerializer(SerializerInterface $serializer)
36
    {
37 16
        $this->serializer = $serializer;
38 16
    }
39
40 1
    public function setRelationLoader(RelationLoaderInterface $relationLoader)
41
    {
42 1
        $this->relationLoader = $relationLoader;
43 1
    }
44
45
    /**
46
     * @param Scope $scope
47
     *
48
     * @throws IncludeException
49
     *
50
     * @return array|null
51
     */
52 16
    public function transform(Scope $scope)
53
    {
54 16
        $resource = $scope->resource();
55
        
56
        // When we encounter a null resource, we just return null because
57
        // there's nothing else we can do with it.
58 16
        if ($resource === null) {
59
            return null;
60
        }
61
        
62
        // If the resource is not a ResourceInterface instance we allow it to be
63
        // something array-like.
64 16
        if (!($resource instanceof ResourceInterface)) {
65 3
            if (\is_array($resource)) {
66 1
                return $resource;
67
            }
68 2
            if (\is_object($resource) && method_exists($resource, 'toArray')) {
69 1
                return $resource->toArray();
70
            }
71
72 1
            throw new UnhandledResourceType('Unable to serialize resource of type ' . \gettype($resource));
73
        }
74
75
        // Try to resolve a transformer for a resource that does not have one
76
        // assigned.
77 14
        if (!$resource->hasTransformer()) {
78 10
            $transformer = $this->resolveTransformerForResource($resource);
79 10
            $resource->setTransformer($transformer);
80
        }
81
82
        // Call the relationship loader for any relations
83 14
        $this->loadRelations($resource, $scope->resolvedRelationshipKeys());
84
85
        // We can only transform the resource data if we have some data to
86
        // transform ...
87 14
        if (($data = $resource->getData()) !== null) {
88
            // Build the data by recursively transforming each resource.
89 14
            if ($resource instanceof Collection) {
90 5
                $data = [];
91 5
                foreach ($resource as $itemData) {
92 5
                    $data[] = $this->transformData($scope, $itemData);
93
                }
94 12
            } elseif ($resource instanceof Item) {
95 12
                $data = $this->transformData($scope, $data);
96
            }
97
        }
98
99
        // Serialize the transformed data
100 14
        return $this->serialize($resource, $data);
101
    }
102
103
    /**
104
     * Serialize the data
105
     * @param ResourceInterface $resource
106
     * @param                   $data
107
     *
108
     * @return array|mixed
109
     */
110 14
    protected function serialize(ResourceInterface $resource, $data)
111
    {
112
        // Get the serializer from the resource, or use the default.
113 14
        $serializer = $resource->getSerializer() ?? $this->getSerializer();
114
        
115 14
        if ($serializer instanceof SerializerInterface) {
116 11
            if ($resource instanceof Collection) {
117 3
                if ($data === null) {
118
                    return $serializer->nullCollection();
119
                }
120 3
                $data = $serializer->collection($resource->getResourceKey(), $data);
121 3
                if ($resource->hasPaginator()) {
122 3
                    $data = array_merge($data, $serializer->paginator($resource->getPaginator()));
123
                }
124 11
            } elseif ($resource instanceof Item) {
125 11
                if ($data === null) {
126 1
                    return $serializer->nullItem();
127
                }
128 11
                $data = $serializer->item($resource->getResourceKey(), $data);
129
            }
130 3
        } elseif (\is_callable($serializer)) {
131
            // Serialize via a callable/closure
132 2
            $data = $serializer($resource->getResourceKey(), $data);
133
        }
134
       
135 14
        return $data;
136
    }
137
138
    /**
139
     * Apply transformation to the item data.
140
     *
141
     * @param Scope $scope
142
     * @param mixed $data
143
     *
144
     * @throws IncludeException
145
     *
146
     * @return array
147
     */
148 14
    protected function transformData(Scope $scope, $data): array
149
    {
150 14
        $transformer = $scope->transformer();
151
152
        // Handle when no transformer is present
153 14
        if (empty($transformer)) {
154
            // No transformation
155 7
            return (array) $data;
156
        }
157
158
        // Handle when transformer is a callable
159 8
        if (\is_callable($transformer)) {
160
            // Simply run callable on the data and return the result
161 3
            return (array) $transformer($data);
162
        }
163
164
        // Ensure we're working with a real transformer from this point forward.
165 7
        if (!($transformer instanceof TransformerInterface)) {
166
            throw new InvalidTransformerException('Expected a valid transformer');
167
        }
168
169
        // Transform the data, and filter any sparse field-set for the scope.
170 7
        $transformedData = $scope->filterData($transformer->getTransformedData($data));
171
172
        // Add includes to the payload.
173 7
        $includeMap = $scope->includeMap();
174 7
        foreach ($scope->resolvedIncludeKeys() as $includeKey) {
175 6
            $child = $this->executeTransformerInclude($scope, $includeKey, $includeMap[$includeKey], $data);
176
177
            // Create a new child scope.
178 6
            $childScope = new Scope($child, $scope->includes()->splice($includeKey), $scope);
179
180
            // If working with a ResourceInterface, use it's own key (if present).
181 6
            $childKey = $child instanceof ResourceInterface && $child->getResourceKey() ?
182 6
                $child->getResourceKey() : $includeKey;
183
184 6
            ArrayHelper::mutate(
185
                $transformedData,
186 6
                $childKey,
187 6
                $this->transform($childScope)
188
            );
189
        }
190
191 7
        return $transformedData;
192
    }
193
194
    /**
195
     * Execute the transformer.
196
     *
197
     * @param Scope  $scope
198
     * @param string $includeKey
199
     * @param array  $includeDefinition
200
     * @param mixed  $data
201
     *
202
     * @throws IncludeException
203
     *
204
     * @return ResourceInterface
205
     */
206 6
    protected function executeTransformerInclude(Scope $scope, $includeKey, $includeDefinition, $data)
207
    {
208
        // Transformer explicitly provided an include method
209 6
        $transformer = $scope->transformer();
210 6
        $method = $includeDefinition['method'];
211 6
        if (method_exists($transformer, $method)) {
212 3
            return $transformer->$method($data, $scope);
213
        }
214
215
        // Otherwise try handle the include automatically
216 3
        return $this->autoWireInclude($includeKey, $includeDefinition, $data);
217
    }
218
219
    /**
220
     * Resolve the transformer to be used for a resource.
221
     * Returns an interface, callable or null when a transformer cannot be resolved.
222
     *
223
     * @param $resource
224
     *
225
     * @return TransformerInterface|mixed|null
226
     */
227 10
    protected function resolveTransformerForResource($resource)
228
    {
229 10
        $transformer = null;
230
231 10
        if ($this->transformerResolver !== null) {
232 2
            $transformer = $this->transformerResolver->resolve($resource);
233
        }
234
235 10
        return $transformer;
236
    }
237
238
    /**
239
     * Fire the relation loader (if defined) for this resource.
240
     *
241
     * @param ResourceInterface $resource
242
     * @param array             $relationshipKeys
243
     */
244 14
    protected function loadRelations(ResourceInterface $resource, array $relationshipKeys)
245
    {
246 14
        if ($this->relationLoader !== null && !empty($relationshipKeys)) {
247 1
            $this->relationLoader->load($resource, $relationshipKeys);
248
        }
249 14
    }
250
251
    /**
252
     * @param string $includeKey
253
     * @param array  $includeDefinition
254
     * @param        $item
255
     *
256
     * @throws \Rexlabs\Smokescreen\Exception\InvalidTransformerException
257
     * @throws \Rexlabs\Smokescreen\Exception\IncludeException
258
     *
259
     * @return Collection|Item|ResourceInterface
260
     */
261 3
    protected function autoWireInclude($includeKey, $includeDefinition, $item)
262
    {
263
        // Get the included data
264 3
        $data = null;
265 3
        if (\is_array($item) || $item instanceof \ArrayAccess) {
266 2
            $data = $item[$includeKey] ?? null;
267 1
        } elseif (\is_object($item)) {
268 1
            $data = $item->$includeKey ?? null;
269
        } else {
270
            throw new IncludeException("Cannot auto-wire include for $includeKey: Cannot get include data");
271
        }
272
273 3
        if (!empty($includeDefinition['resource_type']) && $includeDefinition['resource_type'] === 'collection') {
274 1
            return new Collection($data);
275
        }
276
277
        // Assume unless declared, that the resource is an item.
278 3
        return new Item($data);
279
    }
280
281 11
    protected function getSerializer()
282
    {
283 11
        return $this->serializer;
284
    }
285
}
286