Completed
Push — master ( 114f7e...334f44 )
by Alexander
11:37 queued 08:18
created

TransformBuilder::transform()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
nc 1
nop 0
dl 0
loc 10
ccs 6
cts 6
cp 1
crap 2
rs 9.9332
c 0
b 0
f 0
1
<?php
2
3
namespace Flugg\Responder;
4
5
use Flugg\Responder\Contracts\Pagination\PaginatorFactory;
6
use Flugg\Responder\Contracts\Resources\ResourceFactory;
7
use Flugg\Responder\Contracts\TransformFactory;
8
use Flugg\Responder\Exceptions\InvalidSuccessSerializerException;
9
use Flugg\Responder\Pagination\CursorPaginator;
10
use Flugg\Responder\Transformers\Transformer;
11
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
12
use Illuminate\Database\Eloquent\Collection;
13
use Illuminate\Database\Eloquent\Model;
14
use Illuminate\Support\Str;
15
use League\Fractal\Pagination\Cursor;
16
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
17
use League\Fractal\Resource\Collection as CollectionResource;
18
use League\Fractal\Resource\NullResource;
19
use League\Fractal\Serializer\SerializerAbstract;
20
21
/**
22
 * A builder class responsible for building transformed arrays.
23
 *
24
 * @package flugger/laravel-responder
25
 * @author  Alexander Tømmerås <[email protected]>
26
 * @license The MIT License
27
 */
28
class TransformBuilder
29
{
30
    /**
31
     * A factory class for making Fractal resources.
32
     *
33
     * @var \Flugg\Responder\Contracts\Resources\ResourceFactory
34
     */
35
    protected $resourceFactory;
36
37
    /**
38
     * A factory for making transformed arrays.
39
     *
40
     * @var \Flugg\Responder\Contracts\TransformFactory
41
     */
42
    private $transformFactory;
43
44
    /**
45
     * A factory used to build Fractal paginator adapters.
46
     *
47
     * @var \Flugg\Responder\Contracts\Pagination\PaginatorFactory
48
     */
49
    protected $paginatorFactory;
50
51
    /**
52
     * The resource that's being built.
53
     *
54
     * @var \League\Fractal\Resource\ResourceInterface
55
     */
56
    protected $resource;
57
58
    /**
59
     * A serializer for formatting data after transforming.
60
     *
61
     * @var \League\Fractal\Serializer\SerializerAbstract
62
     */
63
    protected $serializer;
64
65
    /**
66
     * A list of included relations.
67
     *
68
     * @var array
69
     */
70
    protected $with = [];
71
72
    /**
73
     * A list of excluded relations.
74
     *
75
     * @var array
76
     */
77
    protected $without = [];
78
79
    /**
80
     * A list of sparse fieldsets.
81
     *
82
     * @var array
83
     */
84
    protected $only = [];
85
86
    /**
87
     * Construct the builder class.
88
     *
89
     * @param \Flugg\Responder\Contracts\Resources\ResourceFactory   $resourceFactory
90
     * @param \Flugg\Responder\Contracts\TransformFactory            $transformFactory
91
     * @param \Flugg\Responder\Contracts\Pagination\PaginatorFactory $paginatorFactory
92
     */
93 74
    public function __construct(ResourceFactory $resourceFactory, TransformFactory $transformFactory, PaginatorFactory $paginatorFactory)
94
    {
95 74
        $this->resourceFactory = $resourceFactory;
96 74
        $this->transformFactory = $transformFactory;
97 74
        $this->paginatorFactory = $paginatorFactory;
98 74
    }
99
100
    /**
101
     * Make a resource from the given data and transformer and set the resource key.
102
     *
103
     * @param  mixed                                                          $data
104
     * @param  \Flugg\Responder\Transformers\Transformer|callable|string|null $transformer
105
     * @param  string|null                                                    $resourceKey
106
     * @return $this
107
     */
108 73
    public function resource($data = null, $transformer = null, string $resourceKey = null)
109
    {
110 73
        $this->resource = $this->resourceFactory->make($data, $transformer, $resourceKey);
111
112 73
        if ($data instanceof CursorPaginator) {
113 1
            $this->cursor($this->paginatorFactory->makeCursor($data));
114 72
        } elseif ($data instanceof LengthAwarePaginator) {
115 2
            $this->paginator($this->paginatorFactory->make($data));
0 ignored issues
show
Compatibility introduced by
$this->paginatorFactory->make($data) of type object<League\Fractal\Pa...ion\PaginatorInterface> is not a sub-type of object<League\Fractal\Pa...minatePaginatorAdapter>. It seems like you assume a concrete implementation of the interface League\Fractal\Pagination\PaginatorInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
116
        }
117
118 73
        return $this;
119
    }
120
121
    /**
122
     * Manually set the cursor on the resource.
123
     *
124
     * @param  \League\Fractal\Pagination\Cursor $cursor
125
     * @return $this
126
     */
127 3
    public function cursor(Cursor $cursor)
128
    {
129 3
        if ($this->resource instanceof CollectionResource) {
130 3
            $this->resource->setCursor($cursor);
131
        }
132
133 3
        return $this;
134
    }
135
136
    /**
137
     * Manually set the paginator on the resource.
138
     *
139
     * @param  \League\Fractal\Pagination\IlluminatePaginatorAdapter $paginator
140
     * @return $this
141
     */
142 4
    public function paginator(IlluminatePaginatorAdapter $paginator)
143
    {
144 4
        if ($this->resource instanceof CollectionResource) {
145 4
            $this->resource->setPaginator($paginator);
146
        }
147
148 4
        return $this;
149
    }
150
151
    /**
152
     * Add meta data appended to the response data.
153
     *
154
     * @param  array $data
155
     * @return $this
156
     */
157 2
    public function meta(array $data)
158
    {
159 2
        $this->resource->setMeta($data);
160
161 2
        return $this;
162
    }
163
164
    /**
165
     * Include relations to the transform.
166
     *
167
     * @param  string[]|string $relations
168
     * @return $this
169
     */
170 60
    public function with($relations)
171
    {
172 60
        $relations = is_array($relations) ? $relations : func_get_args();
173
174 60
        foreach ($relations as $relation => $constraint) {
175 24
            if (is_numeric($relation)) {
176 22
                $relation = $constraint;
177 22
                $constraint = null;
178
            }
179
180 24
            $this->with = array_merge($this->with, [$relation => $constraint]);
181
        }
182
183 60
        return $this;
184
    }
185
186
    /**
187
     * Exclude relations from the transform.
188
     *
189
     * @param  string[]|string $relations
190
     * @return $this
191
     */
192 3
    public function without($relations)
193
    {
194 3
        $this->without = array_merge($this->without, is_array($relations) ? $relations : func_get_args());
195
196 3
        return $this;
197
    }
198
199
    /**
200
     * Filter fields to output using sparse fieldsets.
201
     *
202
     * @param  string[]|string $fields
203
     * @return $this
204
     */
205 56
    public function only($fields)
206
    {
207 56
        $this->only = array_merge($this->only, is_array($fields) ? $fields : func_get_args());
208
209 56
        return $this;
210
    }
211
212
    /**
213
     * Set the serializer.
214
     *
215
     * @param  \League\Fractal\Serializer\SerializerAbstract|string $serializer
216
     * @return $this
217
     * @throws \Flugg\Responder\Exceptions\InvalidSuccessSerializerException
218
     */
219 74
    public function serializer($serializer)
220
    {
221 74
        if (is_string($serializer)) {
222 5
            $serializer = new $serializer;
223
        }
224
225 74
        if (! $serializer instanceof SerializerAbstract) {
226 1
            throw new InvalidSuccessSerializerException;
227
        }
228
229 74
        $this->serializer = $serializer;
230
231 74
        return $this;
232
    }
233
234
    /**
235
     * Transform and serialize the data and return the transformed array.
236
     *
237
     * @return array|null
238
     */
239 67
    public function transform()
240
    {
241 67
        $this->prepareRelations($this->resource->getData(), $this->resource->getTransformer());
242
243 67
        return $this->transformFactory->make($this->resource ?: new NullResource, $this->serializer, [
244 67
            'includes' => $this->with,
245 67
            'excludes' => $this->without,
246 67
            'fieldsets' => $this->only,
247
        ]);
248
    }
249
250
    /**
251
     * Prepare requested relations for the transformation.
252
     *
253
     * @param  mixed                                                          $data
254
     * @param  \Flugg\Responder\Transformers\Transformer|callable|string|null $transformer
255
     * @return void
256
     */
257 67
    protected function prepareRelations($data, $transformer)
258
    {
259 67
        if ($transformer instanceof Transformer) {
260 45
            $relations = $transformer->relations($this->with);
261 45
            $defaultRelations = $this->removeExcludedRelations($transformer->defaultRelations($this->with));
262 45
            $this->with = array_merge($relations, $defaultRelations);
263
        }
264
265 67
        if ($data instanceof Model || $data instanceof Collection) {
266 40
            $this->eagerLoadRelations($data, $this->with, $transformer);
267
        }
268
269 67
        $this->with = array_keys($this->with);
270 67
    }
271
272
    /**
273
     * Filter out relations that have been explicitly excluded using the [without] method.
274
     *
275
     * @param  array $relations
276
     * @return array
277
     */
278 45
    protected function removeExcludedRelations(array $relations): array
279
    {
280
        return array_filter($relations, function ($relation) {
281 6
            return ! in_array($this->stripParametersFromRelation($relation), $this->without);
282 45
        }, ARRAY_FILTER_USE_KEY);
283
    }
284
285
    /**
286
     * Strip parameter suffix from the relation string by only taking what is in front of
287
     * the colon.
288
     *
289
     * @param  string $relation
290
     * @return string
291
     */
292 23
    protected function stripParametersFromRelation(string $relation): string
293
    {
294 23
        return explode(':', $relation)[0];
295
    }
296
297
    /**
298
     * Eager load all requested relations except the ones defined as an "include" method
299
     * in the transformers. We also strip away any parameters from the relation name
300
     * and normalize relations by swapping "null" constraints to empty closures.
301
     *
302
     * @param  mixed                                                          $data
303
     * @param  array                                                          $requested
304
     * @param  \Flugg\Responder\Transformers\Transformer|callable|string|null $transformer
305
     * @return void
306
     */
307 40
    protected function eagerLoadRelations($data, array $requested, $transformer)
308
    {
309
        $relations = collect(array_keys($requested))->reduce(function ($eagerLoads, $relation) use ($requested, $transformer) {
310 23
            $identifier = Str::camel($this->stripParametersFromRelation($relation));
311
312 23
            if (method_exists($transformer, 'include' . ucfirst($identifier))) {
313 3
                return $eagerLoads;
314
            }
315
316
            return array_merge($eagerLoads, [$identifier => $requested[$relation] ?: function () { }]);
317 40
        }, []);
318
319 40
        $data->load($relations);
320
    }
321
}