TransformBuilder::with()   A
last analyzed

Complexity

Conditions 4
Paths 6

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
nc 6
nop 1
dl 0
loc 15
ccs 8
cts 8
cp 1
crap 4
rs 9.7666
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 75
    public function __construct(ResourceFactory $resourceFactory, TransformFactory $transformFactory, PaginatorFactory $paginatorFactory)
94
    {
95 75
        $this->resourceFactory = $resourceFactory;
96 75
        $this->transformFactory = $transformFactory;
97 75
        $this->paginatorFactory = $paginatorFactory;
98 75
    }
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 74
    public function resource($data = null, $transformer = null, string $resourceKey = null)
109
    {
110 74
        $this->resource = $this->resourceFactory->make($data, $transformer, $resourceKey);
111
112 74
        if ($data instanceof CursorPaginator) {
113 1
            $this->cursor($this->paginatorFactory->makeCursor($data));
114 73
        } 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 74
        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 61
    public function with($relations)
171
    {
172 61
        $relations = is_array($relations) ? $relations : func_get_args();
173
174 61
        foreach ($relations as $relation => $constraint) {
175 25
            if (is_numeric($relation)) {
176 23
                $relation = $constraint;
177 23
                $constraint = null;
178
            }
179
180 25
            $this->with = array_merge($this->with, [$relation => $constraint]);
181
        }
182
183 61
        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 57
    public function only($fields)
206
    {
207 57
        $this->only = array_merge($this->only, is_array($fields) ? $fields : func_get_args());
208
209 57
        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 75
    public function serializer($serializer)
220
    {
221 75
        if (is_string($serializer)) {
222 5
            $serializer = new $serializer;
223
        }
224
225 75
        if (! $serializer instanceof SerializerAbstract) {
226 1
            throw new InvalidSuccessSerializerException;
227
        }
228
229 75
        $this->serializer = $serializer;
230
231 75
        return $this;
232
    }
233
234
    /**
235
     * Transform and serialize the data and return the transformed array.
236
     *
237
     * @return array|null
238
     */
239 68
    public function transform()
240
    {
241 68
        $this->prepareRelations($this->resource->getData(), $this->resource->getTransformer());
242
243 68
        return $this->transformFactory->make($this->resource ?: new NullResource, $this->serializer, [
244 68
            'includes' => $this->with,
245 68
            'excludes' => $this->without,
246 68
            '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 68
    protected function prepareRelations($data, $transformer)
258
    {
259 68
        if ($transformer instanceof Transformer) {
260 46
            $relations = $transformer->relations($this->with);
261 46
            $defaultRelations = $this->removeExcludedRelations($transformer->defaultRelations($this->with));
262 46
            $this->with = array_merge($relations, $defaultRelations);
263
        }
264
265 68
        if ($data instanceof Model || $data instanceof Collection) {
266 41
            $this->eagerLoadRelations($data, $this->with, $transformer);
267
        }
268
269 68
        $this->with = array_keys($this->with);
270 68
    }
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 46
    protected function removeExcludedRelations(array $relations): array
279
    {
280
        return array_filter($relations, function ($relation) {
281 7
            return ! in_array($this->stripParametersFromRelation($relation), $this->without);
282 46
        }, 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 24
    protected function stripParametersFromRelation(string $relation): string
293
    {
294 24
        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 41
    protected function eagerLoadRelations($data, array $requested, $transformer)
308
    {
309
        $relations = collect(array_keys($requested))->reduce(function ($eagerLoads, $relation) use ($requested, $transformer) {
310 24
            $identifier = $this->stripParametersFromRelation($relation);
311
312 24
            if(config('responder.use_camel_case_relations')) {
313 23
                $identifier = Str::camel($identifier);
314
            }
315
316 24
            if (method_exists($transformer, 'include' . ucfirst($identifier))) {
317 3
                return $eagerLoads;
318
            }
319
320
            return array_merge($eagerLoads, [$identifier => $requested[$relation] ?: function () { }]);
321 41
        }, []);
322
323 41
        $data->load($relations);
324
    }
325
}