Completed
Push — master ( 9beb61...fcf0cf )
by Jodie
03:20
created

Pipeline   A

Complexity

Total Complexity 39

Size/Duplication

Total Lines 281
Duplicated Lines 0 %

Test Coverage

Coverage 96.97%

Importance

Changes 0
Metric Value
wmc 39
dl 0
loc 281
ccs 96
cts 99
cp 0.9697
rs 9.28
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A transformItemResource() 0 28 3
A setTransformerResolver() 0 3 1
A resolveTransformerForResource() 0 9 2
A __construct() 0 2 1
A transformData() 0 31 5
A loadRelations() 0 4 3
A setSerializer() 0 3 1
A transformCollectionResource() 0 33 5
A executeTransformerInclude() 0 11 2
B transform() 0 31 8
A autoWireInclude() 0 18 6
A getSerializer() 0 3 1
A setRelationLoader() 0 3 1
1
<?php
2
3
namespace Rexlabs\Smokescreen\Transformer;
4
5
use Rexlabs\Smokescreen\Exception\IncludeException;
6
use Rexlabs\Smokescreen\Exception\UnhandledResourceType;
7
use Rexlabs\Smokescreen\Helpers\ArrayHelper;
8
use Rexlabs\Smokescreen\Relations\RelationLoaderInterface;
9
use Rexlabs\Smokescreen\Resource\Collection;
10
use Rexlabs\Smokescreen\Resource\Item;
11
use Rexlabs\Smokescreen\Resource\ResourceInterface;
12
use Rexlabs\Smokescreen\Serializer\SerializerInterface;
13
14
class Pipeline
15
{
16
    /** @var SerializerInterface */
17
    protected $serializer;
18
19
    /** @var TransformerResolverInterface|null */
20
    protected $transformerResolver;
21
22
    /** @var RelationLoaderInterface|null */
23
    protected $relationLoader;
24
25 16
    public function __construct()
26
    {
27 16
    }
28
29 2
    public function setTransformerResolver(TransformerResolverInterface $transformerResolver)
30
    {
31 2
        $this->transformerResolver = $transformerResolver;
32 2
    }
33
34 16
    public function setSerializer(SerializerInterface $serializer)
35
    {
36 16
        $this->serializer = $serializer;
37 16
    }
38
39 1
    public function setRelationLoader(RelationLoaderInterface $relationLoader)
40
    {
41 1
        $this->relationLoader = $relationLoader;
42 1
    }
43
44
    /**
45
     * @param Scope $scope
46
     *
47
     * @return array
48
     * @throws IncludeException
49
     */
50 16
    public function transform(Scope $scope): array
51
    {
52 16
        $resource = $scope->resource();
53 16
        if (!($resource instanceof ResourceInterface)) {
54 3
            if (\is_array($resource)) {
55 1
                return $resource;
56
            }
57 2
            if (\is_object($resource) && method_exists($resource, 'toArray')) {
58 1
                return $resource->toArray();
59
            }
60 1
            throw new UnhandledResourceType('Unable to serialize resource of type '.\gettype($resource));
61
        }
62
63
        // Try to resolve a transformer for a resource that does not have one assigned.
64 14
        if (!$resource->hasTransformer()) {
65 9
            $transformer = $this->resolveTransformerForResource($resource);
66 9
            $resource->setTransformer($transformer);
67
        }
68
69
        // Call the relationship loader for any relations
70 14
        $this->loadRelations($resource, $scope->resolvedRelationshipKeys());
71
72
        // Build the output by recursively transforming each resource.
73 14
        $output = null;
74 14
        if ($resource instanceof Collection) {
75 5
            $output = $this->transformCollectionResource($scope);
76 12
        } elseif ($resource instanceof Item) {
77 12
            $output = $this->transformItemResource($scope);
78
        }
79
80 14
        return $output;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $output could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
81
    }
82
83
    /**
84
     * Resolve the transformer to be used for a resource.
85
     * Returns an interface, callable or null when a transformer cannot be resolved.
86
     *
87
     * @param $resource
88
     *
89
     * @return TransformerInterface|mixed|null
90
     */
91 9
    protected function resolveTransformerForResource($resource)
92
    {
93 9
        $transformer = null;
94
95 9
        if ($this->transformerResolver !== null) {
96 2
            $transformer = $this->transformerResolver->resolve($resource);
97
        }
98
99 9
        return $transformer;
100
    }
101
102
    /**
103
     * Applies the serializer to the Item resource.
104
     *
105
     * @param Scope $scope
106
     *
107
     * @return array
108
     * @throws IncludeException
109
     */
110 12
    protected function transformItemResource(Scope $scope): array
111
    {
112
        // Get the globally set serializer (resource may override)
113 12
        $defaultSerializer = $this->getSerializer();
114
115
116
        // The collection can have a custom serializer defined
117
        // TODO: Check resource type is item
118 12
        $item = $scope->resource();
119 12
        $serializer = $item->getSerializer() ?? $defaultSerializer;
120 12
        $isSerializerInterface = $serializer instanceof SerializerInterface;
121
122
        // Transform the item data
123 12
        $itemData = $this->transformData($scope, $item->getData());
124
125
        // Serialize the item data
126 12
        if ($isSerializerInterface) {
127
            // Serialize via object implementing SerializerInterface
128 10
            $output = $serializer->item($item->getResourceKey(), $itemData);
129 2
        } elseif (\is_callable($serializer)) {
130
            // Serialize via a callable/closure
131 1
            $output = $serializer($item->getResourceKey(), $itemData);
132
        } else {
133
            // No serialization
134 1
            $output = $itemData;
135
        }
136
137 12
        return $output;
138
    }
139
140
141
    /**
142
     * @param Scope $scope
143
     *
144
     * @return array
145
     * @throws IncludeException
146
     */
147 5
    protected function transformCollectionResource(Scope $scope): array
148
    {
149
        // Get the globally set serializer (resource may override).
150 5
        $defaultSerializer = $this->getSerializer();
151
152
        // Collection resources implement IteratorAggregate ... so that's nice.
153
        // TODO: Check type?
154 5
        $collection = $scope->resource();
155
156 5
        $items = [];
157 5
        foreach ($collection as $itemData) {
158
            // $item might be a Model or an array etc.
159 5
            $items[] = $this->transformData($scope, $itemData);
160
        }
161
162
        // The collection can have a custom serializer defined.
163 5
        $serializer = $collection->getSerializer() ?? $defaultSerializer;
164
165 5
        if ($serializer instanceof SerializerInterface) {
166
            // Serialize via object implementing SerializerInterface
167 3
            $output = $serializer->collection($collection->getResourceKey(), $items);
168 3
            if ($collection->hasPaginator()) {
0 ignored issues
show
Bug introduced by
The method hasPaginator() does not exist on Rexlabs\Smokescreen\Resource\ResourceInterface. It seems like you code against a sub-type of Rexlabs\Smokescreen\Resource\ResourceInterface such as Rexlabs\Smokescreen\Resource\Collection. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

168
            if ($collection->/** @scrutinizer ignore-call */ hasPaginator()) {
Loading history...
169 3
                $output = array_merge($output, $serializer->paginator($collection->getPaginator()));
0 ignored issues
show
Bug introduced by
The method getPaginator() does not exist on Rexlabs\Smokescreen\Resource\ResourceInterface. It seems like you code against a sub-type of Rexlabs\Smokescreen\Resource\ResourceInterface such as Rexlabs\Smokescreen\Resource\Collection. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

169
                $output = array_merge($output, $serializer->paginator($collection->/** @scrutinizer ignore-call */ getPaginator()));
Loading history...
170
            }
171 2
        } elseif (\is_callable($serializer)) {
172
            // Serialize via a callable/closure
173 1
            $output = $serializer($collection->getResourceKey(), $items);
174
        } else {
175
            // Serialization disabled for this resource
176 1
            $output = $items;
177
        }
178
179 5
        return $output;
180
    }
181
182
183
    /**
184
     * Apply transformation to the item data.
185
     *
186
     * @param Scope $scope
187
     * @param mixed $data
188
     *
189
     * @return array
190
     * @throws IncludeException
191
     */
192 14
    protected function transformData(Scope $scope, $data): array
193
    {
194
        // Get the base data from the transformation
195 14
        $transformedData = $scope->transform($data);
196
197
        // Add includes to the payload
198 14
        $includeMap = $scope->includeMap();
199 14
        foreach ($scope->resolvedIncludeKeys() as $includeKey) {
200 6
            $resource = $this->executeTransformerInclude($scope, $includeKey, $includeMap[$includeKey], $data);
201
202
            // Create a new scope
203 6
            $newScope = new Scope($resource, $scope->includes()->splice($includeKey), $scope);
204
205 6
            if ($resource instanceof ResourceInterface) {
206
                // Resource object
207 5
                ArrayHelper::mutate(
208
                    $transformedData,
209 5
                    $resource->getResourceKey() ?: $includeKey,
210 5
                    $resource->getData() ? $this->transform($newScope) : null
211
                );
212
            } else {
213
                // Plain old array
214 1
                ArrayHelper::mutate(
215
                    $transformedData,
216 1
                    $includeKey,
217 6
                    $this->transform($newScope)
218
                );
219
            }
220
        }
221
222 14
        return $transformedData;
223
    }
224
225
    /**
226
     * Execute the transformer.
227
     *
228
     * @param Scope  $scope
229
     * @param string $includeKey
230
     * @param array  $includeDefinition
231
     * @param mixed  $data
232
     *
233
     * @return ResourceInterface
234
     * @throws IncludeException
235
     */
236 6
    protected function executeTransformerInclude(Scope $scope, $includeKey, $includeDefinition, $data)
237
    {
238
        // Transformer explicitly provided an include method
239 6
        $transformer = $scope->transformer();
240 6
        $method = $includeDefinition['method'];
241 6
        if (method_exists($transformer, $method)) {
242 3
            return $transformer->$method($data, $scope);
243
        }
244
245
        // Otherwise try handle the include automatically
246 3
        return $this->autoWireInclude($includeKey, $includeDefinition, $data);
247
    }
248
249
    /**
250
     * Fire the relation loader (if defined) for this resource.
251
     *
252
     * @param ResourceInterface $resource
253
     * @param array             $relationshipKeys
254
     */
255 14
    protected function loadRelations(ResourceInterface $resource, array $relationshipKeys)
256
    {
257 14
        if ($this->relationLoader !== null && !empty($relationshipKeys)) {
258 1
            $this->relationLoader->load($resource, $relationshipKeys);
259
        }
260 14
    }
261
262
    /**
263
     * @param string $includeKey
264
     * @param array  $includeDefinition
265
     * @param        $item
266
     *
267
     * @throws \Rexlabs\Smokescreen\Exception\InvalidTransformerException
268
     * @throws \Rexlabs\Smokescreen\Exception\IncludeException
269
     *
270
     * @return Collection|Item|ResourceInterface
271
     */
272 3
    protected function autoWireInclude($includeKey, $includeDefinition, $item)
273
    {
274
        // Get the included data
275 3
        $data = null;
276 3
        if (\is_array($item) || $item instanceof \ArrayAccess) {
277 2
            $data = $item[$includeKey] ?? null;
278 1
        } elseif (\is_object($item)) {
279 1
            $data = $item->$includeKey ?? null;
280
        } else {
281
            throw new IncludeException("Cannot auto-wire include for $includeKey: Cannot get include data");
282
        }
283
284 3
        if (!empty($includeDefinition['resource_type']) && $includeDefinition['resource_type'] === 'collection') {
285 1
            return new Collection($data);
286
        }
287
288
        // Assume unless declared, that the resource is an item.
289 3
        return new Item($data);
290
    }
291
292 14
    protected function getSerializer()
293
    {
294 14
        return $this->serializer;
295
    }
296
}