Passed
Push — master ( 4e43a7...11afa9 )
by Alexander
01:16
created

TransformBuilder::prepareEagerLoadableRelations()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 24
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 11
nc 4
nop 2
dl 0
loc 24
ccs 10
cts 10
cp 1
crap 5
rs 8.5125
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 as BaseTransformer;
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 19
    public function __construct(ResourceFactory $resourceFactory, TransformFactory $transformFactory, PaginatorFactory $paginatorFactory)
93
    {
94 19
        $this->resourceFactory = $resourceFactory;
95 19
        $this->transformFactory = $transformFactory;
96 19
        $this->paginatorFactory = $paginatorFactory;
97 19
    }
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 18
    public function resource($data = null, $transformer = null, string $resourceKey = null)
108
    {
109 18
        $this->resource = $this->resourceFactory->make($data, $transformer, $resourceKey);
110
111 18
        if ($data instanceof CursorPaginator) {
112 1
            $this->cursor($this->paginatorFactory->makeCursor($data));
113
        } elseif ($data instanceof LengthAwarePaginator) {
114 1
            $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 18
        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 2
    public function cursor(Cursor $cursor)
127
    {
128 2
        if ($this->resource instanceof CollectionResource) {
129 2
            $this->resource->setCursor($cursor);
130
        }
131
132 2
        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 2
    public function paginator(IlluminatePaginatorAdapter $paginator)
142
    {
143 2
        if ($this->resource instanceof CollectionResource) {
144 2
            $this->resource->setPaginator($paginator);
145
        }
146
147 2
        return $this;
148
    }
149
150
    /**
151
     * Add meta data appended to the response data.
152
     *
153
     * @param  array $data
154
     * @return $this
155
     */
156 1
    public function meta(array $data)
157
    {
158 1
        $this->resource->setMeta($data);
159
160 1
        return $this;
161
    }
162
163
    /**
164
     * Include relations to the transform.
165
     *
166
     * @param  string[]|string $relations
167
     * @return $this
168
     */
169 5
    public function with($relations)
170
    {
171 5
        $this->with = array_merge($this->with, is_array($relations) ? $relations : func_get_args());
172
173 5
        return $this;
174
    }
175
176
    /**
177
     * Exclude relations from the transform.
178
     *
179
     * @param  string[]|string $relations
180
     * @return $this
181
     */
182 2
    public function without($relations)
183
    {
184 2
        $this->without = array_merge($this->without, is_array($relations) ? $relations : func_get_args());
185
186 2
        return $this;
187
    }
188
189
    /**
190
     * Filter fields to output using sparse fieldsets.
191
     *
192
     * @param  string[]|string $fields
193
     * @return $this
194
     */
195 2
    public function only($fields)
196
    {
197 2
        $this->only = array_merge($this->only, is_array($fields) ? $fields : func_get_args());
198
199 2
        return $this;
200
    }
201
202
    /**
203
     * Set the serializer.
204
     *
205
     * @param  \League\Fractal\Serializer\SerializerAbstract|string $serializer
206
     * @return $this
207
     * @throws \Flugg\Responder\Exceptions\InvalidSuccessSerializerException
208
     */
209 19
    public function serializer($serializer)
210
    {
211 19
        if (is_string($serializer)) {
212 2
            $serializer = new $serializer;
213
        }
214
215 19
        if (! $serializer instanceof SerializerAbstract) {
216 1
            throw new InvalidSuccessSerializerException;
217
        }
218
219 19
        $this->serializer = $serializer;
220
221 19
        return $this;
222
    }
223
224
    /**
225
     * Transform and serialize the data and return the transformed array.
226
     *
227
     * @return array
228
     */
229 12
    public function transform(): array
230
    {
231 12
        $this->prepareRelations($this->resource->getData(), $this->resource->getTransformer());
232
233 12
        return $this->transformFactory->make($this->resource ?: new NullResource, $this->serializer, [
234 12
            'includes' => $this->with,
235 12
            'excludes' => $this->without,
236 12
            'fieldsets' => $this->only,
237
        ]);
238
    }
239
240
    /**
241
     * Prepare requested relations for the transformation.
242
     *
243
     * @param  mixed                                                          $data
244
     * @param  \Flugg\Responder\Transformers\Transformer|callable|string|null $transformer
245
     * @return void
246
     */
247 12
    protected function prepareRelations($data, $transformer)
248
    {
249 12
        if ($transformer instanceof BaseTransformer) {
250 3
            $this->includeTransformerRelations($transformer);
251
        }
252
253 12
        if ($data instanceof Model || $data instanceof Collection) {
254 3
            $data->load($this->prepareEagerLoadableRelations($this->with, $transformer));
255
        }
256
257 12
        $this->with = $this->stripEagerLoadConstraints($this->with);
258 12
    }
259
260
    /**
261
     * Include default relationships and add eager load constraints from transformer.
262
     *
263
     * @param  \Flugg\Responder\Transformers\Transformer $transformer
264
     * @return void
265
     */
266
    protected function includeTransformerRelations(BaseTransformer $transformer)
267
    {
268 3
        $relations = array_filter(array_keys($this->with), function ($relation) {
269 3
            return ! is_numeric($relation);
270 3
        });
271
272 3
        $this->with(Collection::make($transformer->defaultRelations())
273 3
            ->filter(function ($constrain, $relation) use ($relations) {
274 2
                return ! in_array(is_numeric($relation) ? $constrain : $relation, $relations);
275 3
            })->all());
276 3
    }
277
278
    /**
279
     * Remove eager load constraint functions from the given relations.
280
     *
281
     * @param  array $relations
282
     * @return array
283
     */
284
    protected function stripEagerLoadConstraints(array $relations): array
285
    {
286 12
        return collect($relations)->map(function ($value, $key) {
287 5
            return is_numeric($key) ? $value : $key;
288 12
        })->values()->all();
289
    }
290
291
    /**
292
     * Remove parameters from relations that must be eager loaded and
293
     * filter out the relations which have an includeXxx method.
294
     *
295
     * @param array                                                             $relations
296
     * @param \Flugg\Responder\Transformers\Transformer|callable|string|null    $transformer
297
     * @return array
298
     */
299 3
    protected function prepareEagerLoadableRelations(array $relations, $transformer): array
300
    {
301 3
        $cleanedRelations = [];
302 3
        foreach ($relations as $key => $value) {
303
            // Strip out parameters from relation name
304 3
            $relationName = explode(':', is_numeric($key) ? $value : $key)[0];
305
            // Ignores all relation which have a includeXxx method
306
            // method_exists does not care if the $transformer is actually an object or not
307 3
            if (method_exists($transformer, 'include' . ucfirst($relationName))) {
308 1
                continue;
309
            }
310
311
            // If the key is numeric, value is the relation name: return it
312
            // Otherwise the key is the relation name and the value is a custom scope:
313
            //  return the relation with the value untouched
314 3
            if(is_numeric($key)) {
315 3
                $cleanedRelations[$key] = $relationName;
316
            } else {
317 3
                $cleanedRelations[$relationName] = $value;
318
            }
319
        }
320
321 3
        return $cleanedRelations;
322
    }
323
}