Passed
Pull Request — master (#32)
by
unknown
03:40
created

SuccessResponseBuilder::isSuccessResponse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Flugg\Responder\Http;
4
5
use Flugg\Responder\Contracts\Transformable;
6
use Flugg\Responder\Exceptions\InvalidSerializerException;
7
use Flugg\Responder\Exceptions\InvalidTransformerException;
8
use Flugg\Responder\ResourceFactory;
9
use Flugg\Responder\ResourceResolver;
10
use Flugg\Responder\Transformation;
11
use Flugg\Responder\Transformer;
12
use Illuminate\Database\Eloquent\Model;
13
use InvalidArgumentException;
14
use League\Fractal\Manager;
15
use League\Fractal\Resource\ResourceInterface;
16
use League\Fractal\Serializer\SerializerAbstract;
17
18
/**
19
 * This class is a response builder for building successful JSON API responses and is
20
 * responsible for transforming and serializing the data.
21
 *
22
 * @package flugger/laravel-responder
23
 * @author  Alexander Tømmerås <[email protected]>
24
 * @license The MIT License
25
 */
26
class SuccessResponseBuilder extends ResponseBuilder
27
{
28
    /**
29
     * The manager responsible for transforming and serializing data.
30
     *
31
     * @var \League\Fractal\Manager
32
     */
33
    protected $manager;
34
35
    /**
36
     * The meta data appended to the serialized data.
37
     *
38
     * @var array
39
     */
40
    protected $meta = [];
41
42
    /**
43
     * The included relations.
44
     *
45
     * @var array
46
     */
47
    protected $relations = [];
48
49
    /**
50
     * The Fractal resource instance containing the data and transformer.
51
     *
52
     * @var \League\Fractal\Resource\ResourceInterface
53
     */
54
    protected $resource;
55
56
    /**
57
     * The resource factory used to generate resource instances.
58
     *
59
     * @var \Flugg\Responder\ResourceFactory
60
     */
61
    protected $resourceFactory;
62
63
    /**
64
     * The HTTP status code for the response.
65
     *
66
     * @var int
67
     */
68
    protected $statusCode = 200;
69
70
    /**
71
     * SuccessResponseBuilder constructor.
72
     *
73
     * @param \Illuminate\Contracts\Routing\ResponseFactory|\Laravel\Lumen\Http\ResponseFactory $responseFactory
74
     * @param \Flugg\Responder\ResourceFactory                                                  $resourceFactory
75
     * @param \League\Fractal\Manager                                                           $manager
76
     */
77
    public function __construct($responseFactory, ResourceFactory $resourceFactory, Manager $manager)
78
    {
79
        $this->resourceFactory = $resourceFactory;
80
        $this->manager = $manager;
81
        $this->resource = $this->resourceFactory->make();
82
83
        parent::__construct($responseFactory);
84
    }
85
86
    /**
87
     * Add data to the meta data appended to the response data.
88
     *
89
     * @param  array $data
90
     * @return self
91
     */
92
    public function addMeta(array $data):SuccessResponseBuilder
93
    {
94
        $this->meta = array_merge($this->meta, $data);
95
96
        return $this;
97
    }
98
99
    /**
100
     * Set the serializer used to serialize the resource data.
101
     *
102
     * @param  array|string $relations
103
     * @return self
104
     */
105
    public function include($relations):SuccessResponseBuilder
106
    {
107
        if (is_string($relations)) {
108
            $relations = explode(',', $relations);
109
        }
110
111
        $this->relations = array_merge($this->relations, (array) $relations);
112
113
        return $this;
114
    }
115
116
    /**
117
     * Set the serializer used to serialize the resource data.
118
     *
119
     * @param  \League\Fractal\Serializer\SerializerAbstract|string $serializer
120
     * @return self
121
     */
122
    public function serializer($serializer):SuccessResponseBuilder
123
    {
124
        $this->manager->setSerializer($this->resolveSerializer($serializer));
125
126
        return $this;
127
    }
128
129
    /**
130
     * Set the HTTP status code for the response.
131
     *
132
     * @param  int $statusCode
133
     * @return self
134
     * @throws \InvalidArgumentException
135
     */
136 View Code Duplication
    public function setStatus(int $statusCode):ResponseBuilder
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
137
    {
138
        if ($statusCode < 100 || $statusCode >= 400) {
139
            throw new InvalidArgumentException("{$statusCode} is not a valid success HTTP status code.");
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $statusCode instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
140
        }
141
142
        return parent::setStatus($statusCode);
143
    }
144
145
    /**
146
     * Return response success flag as true
147
     *
148
     * @return bool
149
     */
150
    protected function isSuccessResponse():bool 
151
    {
152
        return true;
153
    }
154
155
    /**
156
     * Set the transformation data. This will set a new resource instance on the response
157
     * builder depending on what type of data is provided.
158
     *
159
     * @param  mixed|null           $data
160
     * @param  callable|string|null $transformer
161
     * @param  string|null          $resourceKey
162
     * @return self
163
     */
164
    public function transform($data = null, $transformer = null, string $resourceKey = null):SuccessResponseBuilder
165
    {
166
        $resource = $this->resourceFactory->make($data);
167
168
        if (! is_null($resource->getData())) {
169
            $model = $this->resolveModel($resource->getData());
170
            $transformer = $this->resolveTransformer($model, $transformer);
171
            $resourceKey = $this->resolveResourceKey($model, $resourceKey);
172
        }
173
174
        if ($transformer instanceof Transformer) {
175
            $this->include($relations = $this->resolveNestedRelations($resource->getData()));
176
177
            if ($transformer->allRelationsAllowed()) {
178
                $transformer->setRelations($relations);
179
            }
180
        }
181
182
        $this->resource = $resource->setTransformer($transformer)->setResourceKey($resourceKey);
0 ignored issues
show
Bug introduced by
It seems like $transformer defined by parameter $transformer on line 164 can also be of type null; however, League\Fractal\Resource\...rface::setTransformer() does only seem to accept callable, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
183
184
        return $this;
185
    }
186
187
    /**
188
     * Convert the response to an array.
189
     *
190
     * @return array
191
     */
192
    public function toArray():array
193
    {
194
        return $this->serialize($this->getResource());
195
    }
196
197
    /**
198
     * Get the Fractal resource instance.
199
     *
200
     * @return \League\Fractal\Resource\ResourceInterface
201
     */
202
    public function getResource():ResourceInterface
203
    {
204
        $this->manager->parseIncludes($this->relations);
205
        $transformer = $this->resource->getTransformer();
206
207
        if ($transformer instanceof Transformer && $transformer->allRelationsAllowed()) {
208
            $this->resource->setTransformer($transformer->setRelations($this->manager->getRequestedIncludes()));
0 ignored issues
show
Documentation introduced by
$transformer->setRelatio...getRequestedIncludes()) is of type object<Flugg\Responder\Transformer>, but the function expects a callable.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
209
        }
210
211
        return $this->resource->setMeta($this->meta);
212
    }
213
214
    /**
215
     * Get the Fractal manager responsible for transforming and serializing the data.
216
     *
217
     * @return \League\Fractal\Manager
218
     */
219
    public function getManager():Manager
220
    {
221
        return $this->manager;
222
    }
223
224
    /**
225
     * Resolve a serializer instance from the value.
226
     *
227
     * @param  \League\Fractal\Serializer\SerializerAbstract|string $serializer
228
     * @return \League\Fractal\Serializer\SerializerAbstract
229
     * @throws \Flugg\Responder\Exceptions\InvalidSerializerException
230
     */
231
    protected function resolveSerializer($serializer):SerializerAbstract
232
    {
233
        if (is_string($serializer)) {
234
            $serializer = new $serializer;
235
        }
236
237
        if (! $serializer instanceof SerializerAbstract) {
238
            throw new InvalidSerializerException();
239
        }
240
241
        return $serializer;
242
    }
243
244
    /**
245
     * Resolve a model instance from the data.
246
     *
247
     * @param  \Illuminate\Database\Eloquent\Model|array $data
248
     * @return \Illuminate\Database\Eloquent\Model
249
     * @throws \InvalidArgumentException
250
     */
251
    protected function resolveModel($data):Model
252
    {
253
        if ($data instanceof Model) {
254
            return $data;
255
        }
256
257
        $model = array_values($data)[0];
258
        if (! $model instanceof Model) {
259
            throw new InvalidArgumentException('You can only transform data containing Eloquent models.');
260
        }
261
262
        return $model;
263
    }
264
265
    /**
266
     * Resolve a transformer.
267
     *
268
     * @param  \Illuminate\Database\ELoquent\Model        $model
269
     * @param  \Flugg\Responder\Transformer|callable|null $transformer
270
     * @return \Flugg\Responder\Transformer|callable
271
     */
272
    protected function resolveTransformer(Model $model, $transformer = null)
273
    {
274
        $transformer = $transformer ?: $this->resolveTransformerFromModel($model);
275
276
        if (is_string($transformer)) {
277
            $transformer = new $transformer;
278
        }
279
280
        return $this->parseTransformer($transformer, $model);
281
    }
282
283
    /**
284
     * Resolve a transformer from the model. If the model is not transformable, a closure
285
     * based transformer will be created instead, from the model's fillable attributes.
286
     *
287
     * @param  \Illuminate\Database\ELoquent\Model $model
288
     * @return \Flugg\Responder\Transformer|callable
289
     */
290
    protected function resolveTransformerFromModel(Model $model)
291
    {
292
        if (! $model instanceof Transformable) {
293
            return function ($model) {
294
                return $model->toArray();
295
            };
296
        }
297
298
        return $model::transformer();
299
    }
300
301
    /**
302
     * Parse a transformer class and set relations.
303
     *
304
     * @param  \Flugg\Responder\Transformer|callable $transformer
305
     * @param  \Illuminate\Database\ELoquent\Model   $model
306
     * @return \Flugg\Responder\Transformer|callable
307
     * @throws \InvalidTransformerException
308
     */
309
    protected function parseTransformer($transformer, Model $model)
310
    {
311
        if ($transformer instanceof Transformer) {
312
            $relations = $transformer->allRelationsAllowed() ? $this->resolveRelations($model) : $transformer->getRelations();
313
            $transformer = $transformer->setRelations($relations);
314
315
        } elseif (! is_callable($transformer)) {
316
            throw new InvalidTransformerException($model);
317
        }
318
319
        return $transformer;
320
    }
321
322
    /**
323
     * Resolve eager loaded relations from the model.
324
     *
325
     * @param  \Illuminate\Database\Eloquent\Model $model
326
     * @return array
327
     */
328
    protected function resolveRelations(Model $model):array
329
    {
330
        return array_keys($model->getRelations());
331
    }
332
333
    /**
334
     * Resolve eager loaded relations from the model including any nested relations.
335
     *
336
     * @param  \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model $data
337
     * @return array
338
     */
339
    protected function resolveNestedRelations($data):array
340
    {
341
        if (is_null($data)) {
342
            return [];
343
        }
344
345
        $data = $data instanceof Model ? [$data] : $data;
346
347
        return collect($data)->flatMap(function ($model) {
348
            $relations = collect($model->getRelations());
349
350
            return $relations->keys()->merge($relations->flatMap(function ($relation, $key) {
351
                return collect($this->resolveNestedRelations($relation))->map(function ($nestedRelation) use ($key) {
352
                    return $key . '.' . $nestedRelation;
353
                });
354
            }));
355
        })->unique()->toArray();
356
    }
357
358
    /**
359
     * Resolve the resource key from the model.
360
     *
361
     * @param  \Illuminate\Database\Eloquent\Model $model
362
     * @param  string|null                         $resourceKey
363
     * @return string
364
     */
365
    protected function resolveResourceKey(Model $model, string $resourceKey = null):string
366
    {
367
        if (! is_null($resourceKey)) {
368
            return $resourceKey;
369
        }
370
371
        if (method_exists($model, 'getResourceKey')) {
372
            return $model->getResourceKey();
373
        }
374
375
        return $model->getTable();
376
    }
377
378
    /**
379
     * Serialize the transformation data.
380
     *
381
     * @param  ResourceInterface $resource
382
     * @return array
383
     */
384
    protected function serialize(ResourceInterface $resource):array
385
    {
386
        return $this->manager->createData($resource)->toArray();
387
    }
388
}
0 ignored issues
show
Coding Style introduced by
As per coding style, files should not end with a newline character.

This check marks files that end in a newline character, i.e. an empy line.

Loading history...
389