Passed
Push — master ( 7994a9...5d160b )
by Alexander
06:35
created

TransformBuilder::removeExcludedRelations()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 1
dl 0
loc 6
ccs 3
cts 3
cp 1
crap 1
rs 9.4285
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 League\Fractal\Pagination\Cursor;
15
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
16
use League\Fractal\Resource\Collection as CollectionResource;
17
use League\Fractal\Resource\NullResource;
18
use League\Fractal\Serializer\SerializerAbstract;
19
20
/**
21
 * A builder class responsible for building transformed arrays.
22
 *
23
 * @package flugger/laravel-responder
24
 * @author  Alexander Tømmerås <[email protected]>
25
 * @license The MIT License
26
 */
27
class TransformBuilder
28
{
29
    /**
30
     * A factory class for making Fractal resources.
31
     *
32
     * @var \Flugg\Responder\Contracts\Resources\ResourceFactory
33
     */
34
    protected $resourceFactory;
35
36
    /**
37
     * A factory for making transformed arrays.
38
     *
39
     * @var \Flugg\Responder\Contracts\TransformFactory
40
     */
41
    private $transformFactory;
42
43
    /**
44
     * A factory used to build Fractal paginator adapters.
45
     *
46
     * @var \Flugg\Responder\Contracts\Pagination\PaginatorFactory
47
     */
48
    protected $paginatorFactory;
49
50
    /**
51
     * The resource that's being built.
52
     *
53
     * @var \League\Fractal\Resource\ResourceInterface
54
     */
55
    protected $resource;
56
57
    /**
58
     * A serializer for formatting data after transforming.
59
     *
60
     * @var \League\Fractal\Serializer\SerializerAbstract
61
     */
62
    protected $serializer;
63
64
    /**
65
     * A list of included relations.
66
     *
67
     * @var array
68
     */
69
    protected $with = [];
70
71
    /**
72
     * A list of excluded relations.
73
     *
74
     * @var array
75
     */
76
    protected $without = [];
77
78
    /**
79
     * A list of sparse fieldsets.
80
     *
81
     * @var array
82
     */
83
    protected $only = [];
84
85
    /**
86
     * Construct the builder class.
87
     *
88
     * @param \Flugg\Responder\Contracts\Resources\ResourceFactory   $resourceFactory
89
     * @param \Flugg\Responder\Contracts\TransformFactory            $transformFactory
90
     * @param \Flugg\Responder\Contracts\Pagination\PaginatorFactory $paginatorFactory
91
     */
92 74
    public function __construct(ResourceFactory $resourceFactory, TransformFactory $transformFactory, PaginatorFactory $paginatorFactory)
93
    {
94 74
        $this->resourceFactory = $resourceFactory;
95 74
        $this->transformFactory = $transformFactory;
96 74
        $this->paginatorFactory = $paginatorFactory;
97 74
    }
98
99
    /**
100
     * Make a resource from the given data and transformer and set the resource key.
101
     *
102
     * @param  mixed                                                          $data
103
     * @param  \Flugg\Responder\Transformers\Transformer|callable|string|null $transformer
104
     * @param  string|null                                                    $resourceKey
105
     * @return $this
106
     */
107 73
    public function resource($data = null, $transformer = null, string $resourceKey = null)
108
    {
109 73
        $this->resource = $this->resourceFactory->make($data, $transformer, $resourceKey);
110
111 73
        if ($data instanceof CursorPaginator) {
112 1
            $this->cursor($this->paginatorFactory->makeCursor($data));
113
        } elseif ($data instanceof LengthAwarePaginator) {
114 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...
115
        }
116
117 73
        return $this;
118
    }
119
120
    /**
121
     * Manually set the cursor on the resource.
122
     *
123
     * @param  \League\Fractal\Pagination\Cursor $cursor
124
     * @return $this
125
     */
126 3
    public function cursor(Cursor $cursor)
127
    {
128 3
        if ($this->resource instanceof CollectionResource) {
129 3
            $this->resource->setCursor($cursor);
130
        }
131
132 3
        return $this;
133
    }
134
135
    /**
136
     * Manually set the paginator on the resource.
137
     *
138
     * @param  \League\Fractal\Pagination\IlluminatePaginatorAdapter $paginator
139
     * @return $this
140
     */
141 4
    public function paginator(IlluminatePaginatorAdapter $paginator)
142
    {
143 4
        if ($this->resource instanceof CollectionResource) {
144 4
            $this->resource->setPaginator($paginator);
145
        }
146
147 4
        return $this;
148
    }
149
150
    /**
151
     * Add meta data appended to the response data.
152
     *
153
     * @param  array $data
154
     * @return $this
155
     */
156 2
    public function meta(array $data)
157
    {
158 2
        $this->resource->setMeta($data);
159
160 2
        return $this;
161
    }
162
163
    /**
164
     * Include relations to the transform.
165
     *
166
     * @param  string[]|string $relations
167
     * @return $this
168
     */
169 60
    public function with($relations)
170
    {
171 60
        $relations = is_array($relations) ? $relations : func_get_args();
172
173 60
        foreach ($relations as $relation => $constraint) {
174 24
            if (is_numeric($relation)) {
175 22
                $relation = $constraint;
176 22
                $constraint = null;
177
            }
178
179 24
            $this->with = array_merge($this->with, [$relation => $constraint]);
180
        }
181
182 60
        return $this;
183
    }
184
185
    /**
186
     * Exclude relations from the transform.
187
     *
188
     * @param  string[]|string $relations
189
     * @return $this
190
     */
191 3
    public function without($relations)
192
    {
193 3
        $this->without = array_merge($this->without, is_array($relations) ? $relations : func_get_args());
194
195 3
        return $this;
196
    }
197
198
    /**
199
     * Filter fields to output using sparse fieldsets.
200
     *
201
     * @param  string[]|string $fields
202
     * @return $this
203
     */
204 56
    public function only($fields)
205
    {
206 56
        $this->only = array_merge($this->only, is_array($fields) ? $fields : func_get_args());
207
208 56
        return $this;
209
    }
210
211
    /**
212
     * Set the serializer.
213
     *
214
     * @param  \League\Fractal\Serializer\SerializerAbstract|string $serializer
215
     * @return $this
216
     * @throws \Flugg\Responder\Exceptions\InvalidSuccessSerializerException
217
     */
218 74
    public function serializer($serializer)
219
    {
220 74
        if (is_string($serializer)) {
221 5
            $serializer = new $serializer;
222
        }
223
224 74
        if (! $serializer instanceof SerializerAbstract) {
225 1
            throw new InvalidSuccessSerializerException;
226
        }
227
228 74
        $this->serializer = $serializer;
229
230 74
        return $this;
231
    }
232
233
    /**
234
     * Transform and serialize the data and return the transformed array.
235
     *
236
     * @return array|null
237
     */
238 67
    public function transform()
239
    {
240 67
        $this->prepareRelations($this->resource->getData(), $this->resource->getTransformer());
241
242 67
        return $this->transformFactory->make($this->resource ?: new NullResource, $this->serializer, [
243 67
            'includes' => $this->with,
244 67
            'excludes' => $this->without,
245 67
            'fieldsets' => $this->only,
246
        ]);
247
    }
248
249
    /**
250
     * Prepare requested relations for the transformation.
251
     *
252
     * @param  mixed                                                          $data
253
     * @param  \Flugg\Responder\Transformers\Transformer|callable|string|null $transformer
254
     * @return void
255
     */
256 67
    protected function prepareRelations($data, $transformer)
257
    {
258 67
        if ($transformer instanceof Transformer) {
259 45
            $relations = $transformer->relations($this->with);
260 45
            $defaultRelations = $this->removeExcludedRelations($transformer->defaultRelations($this->with));
261 45
            $this->with = array_merge($relations, $defaultRelations);
262
        }
263
264 67
        if ($data instanceof Model || $data instanceof Collection) {
265 40
            $this->eagerLoadRelations($data, $this->with, $transformer);
266
        }
267
268 67
        $this->with = array_keys($this->with);
269 67
    }
270
271
    /**
272
     * Filter out relations that have been explicitly excluded using the [without] method.
273
     *
274
     * @param  array $relations
275
     * @return array
276
     */
277
    protected function removeExcludedRelations(array $relations): array
278
    {
279 45
        return array_filter($relations, function ($relation) {
280 6
            return ! in_array($this->stripParametersFromRelation($relation), $this->without);
281 45
        }, ARRAY_FILTER_USE_KEY);
282
    }
283
284
    /**
285
     * Strip parameter suffix from the relation string by only taking what is in front of
286
     * the colon.
287
     *
288
     * @param  string $relation
289
     * @return string
290
     */
291 23
    protected function stripParametersFromRelation(string $relation): string
292
    {
293 23
        return explode(':', $relation)[0];
294
    }
295
296
    /**
297
     * Eager load all requested relations except the ones defined as an "include" method
298
     * in the transformers. We also strip away any parameters from the relation name
299
     * and normalize relations by swapping "null" constraints to empty closures.
300
     *
301
     * @param  mixed                                                          $data
302
     * @param  array                                                          $requested
303
     * @param  \Flugg\Responder\Transformers\Transformer|callable|string|null $transformer
304
     * @return void
305
     */
306
    protected function eagerLoadRelations($data, array $requested, $transformer)
307
    {
308 40
        $relations = collect(array_keys($requested))->reduce(function ($eagerLoads, $relation) use ($requested, $transformer) {
309 23
            $identifier = camel_case($this->stripParametersFromRelation($relation));
310
311 23
            if (method_exists($transformer, 'include' . ucfirst($identifier))) {
312 3
                return $eagerLoads;
313
            }
314
315
            return array_merge($eagerLoads, [$identifier => $requested[$relation] ?: function () { }]);
316 40
        }, []);
317
318 40
        $data->load($relations);
319
    }
320
}