Completed
Push — master ( fc8810...fef6ac )
by Alexander
07:15 queued 03:53
created

SuccessResponseBuilder::resolveModel()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 7
c 0
b 0
f 0
nc 3
nop 1
dl 0
loc 13
rs 9.4285
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\Exceptions\SerializerNotFoundException;
9
use Flugg\Responder\ResourceFactory;
10
use Flugg\Responder\ResourceResolver;
11
use Flugg\Responder\Transformation;
12
use Flugg\Responder\Transformer;
13
use Illuminate\Contracts\Routing\ResponseFactory;
14
use Illuminate\Database\Eloquent\Model;
15
use InvalidArgumentException;
16
use League\Fractal\Manager;
17
use League\Fractal\Resource\ResourceInterface;
18
use League\Fractal\Serializer\SerializerAbstract;
19
20
/**
21
 * This class is a response builder for building successful JSON API responses and is
22
 * responsible for transforming and serializing the data.
23
 *
24
 * @package flugger/laravel-responder
25
 * @author  Alexander Tømmerås <[email protected]>
26
 * @license The MIT License
27
 */
28
class SuccessResponseBuilder extends ResponseBuilder
29
{
30
    /**
31
     * The manager responsible for transforming and serializing data.
32
     *
33
     * @var \Flugg\Responder\Contracts\Manager
34
     */
35
    protected $manager;
36
37
    /**
38
     * The meta data appended to the serialized data.
39
     *
40
     * @var array
41
     */
42
    protected $meta = [];
43
44
    /**
45
     * The Fractal resource instance containing the data and transformer.
46
     *
47
     * @var \League\Fractal\Resource\ResourceInterface
48
     */
49
    protected $resource;
50
51
    /**
52
     * The resource factory used to generate resource instances.
53
     *
54
     * @var \Flugg\Responder\ResourceFactory
55
     */
56
    protected $resourceFactory;
57
58
    /**
59
     * The HTTP status code for the response.
60
     *
61
     * @var int
62
     */
63
    protected $statusCode = 200;
64
65
    /**
66
     * SuccessResponseBuilder constructor.
67
     *
68
     * @param \Illuminate\Contracts\Routing\ResponseFactory $responseFactory
69
     * @param \Flugg\Responder\ResourceFactory              $resourceFactory
70
     * @param \League\Fractal\Manager                       $manager
71
     */
72
    public function __construct(ResponseFactory $responseFactory, ResourceFactory $resourceFactory, Manager $manager)
73
    {
74
        $this->resourceFactory = $resourceFactory;
75
        $this->manager = $manager;
0 ignored issues
show
Documentation Bug introduced by
It seems like $manager of type object<League\Fractal\Manager> is incompatible with the declared type object<Flugg\Responder\Contracts\Manager> of property $manager.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
76
        $this->resource = $this->resourceFactory->make();
77
78
        parent::__construct($responseFactory);
79
    }
80
81
    /**
82
     * Add data to the meta data appended to the response data.
83
     *
84
     * @param  array $data
85
     * @return self
86
     */
87
    public function addMeta(array $data):SuccessResponseBuilder
88
    {
89
        $this->meta = array_merge($this->meta, $data);
90
91
        return $this;
92
    }
93
94
    /**
95
     * Set the serializer used to serialize the resource data.
96
     *
97
     * @param  \League\Fractal\Serializer\SerializerAbstract|string $serializer
98
     * @return self
99
     */
100
    public function serializer($serializer):SuccessResponseBuilder
101
    {
102
        $this->manager->setSerializer($this->resolveSerializer($serializer));
103
104
        return $this;
105
    }
106
107
    /**
108
     * Set the HTTP status code for the response.
109
     *
110
     * @param  int $statusCode
111
     * @return self
112
     * @throws \InvalidArgumentException
113
     */
114 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...
115
    {
116
        if ($statusCode < 100 || $statusCode >= 400) {
117
            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...
118
        }
119
120
        return parent::setStatus($statusCode);
121
    }
122
123
    /**
124
     * Set the transformation data. This will set a new resource instance on the response
125
     * builder depending on what type of data is provided.
126
     *
127
     * @param  mixed|null           $data
128
     * @param  callable|string|null $transformer
129
     * @param  string|null          $resourceKey
130
     * @return self
131
     */
132
    public function transform($data = null, $transformer = null, string $resourceKey = null):SuccessResponseBuilder
133
    {
134
        $resource = $this->resourceFactory->make($data);
135
136
        if (! is_null($resource->getData())) {
137
            $model = $this->resolveModel($resource->getData());
138
            $transformer = $this->resolveTransformer($model, $transformer);
139
            $resourceKey = $this->resolveResourceKey($model, $resourceKey);
140
        }
141
142
        $this->resource = $resource->setTransformer($transformer)->setResourceKey($resourceKey);
0 ignored issues
show
Bug introduced by
It seems like $transformer defined by parameter $transformer on line 132 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...
143
144
        return $this;
145
    }
146
147
    /**
148
     * Convert the response to an array.
149
     *
150
     * @return array
151
     */
152
    public function toArray():array
153
    {
154
        return $this->serialize($this->getResource());
155
    }
156
157
    /**
158
     * Get the Fractal resource instance.
159
     *
160
     * @return \League\Fractal\Resource\ResourceInterface
161
     */
162
    public function getResource():ResourceInterface
163
    {
164
        return $this->resource->setMeta($this->meta);
165
    }
166
167
    /**
168
     * Get the Fractal manager responsible for transforming and serializing the data.
169
     *
170
     * @return \League\Fractal\Manager
171
     */
172
    public function getManager():Manager
173
    {
174
        return $this->manager;
175
    }
176
177
    /**
178
     * Resolve a serializer instance from the value.
179
     *
180
     * @param  \League\Fractal\Serializer\SerializerAbstract|string $serializer
181
     * @return \League\Fractal\Serializer\SerializerAbstract
182
     * @throws \Flugg\Responder\Exceptions\InvalidSerializerException
183
     */
184
    protected function resolveSerializer($serializer):SerializerAbstract
185
    {
186
        if (is_string($serializer)) {
187
            $serializer = new $serializer;
188
        }
189
190
        if (! $serializer instanceof SerializerAbstract) {
191
            throw new InvalidSerializerException();
192
        }
193
194
        return $serializer;
195
    }
196
197
    /**
198
     * Resolve a model instance from the data.
199
     *
200
     * @param  \Illuminate\Database\Eloquent\Model|array $data
201
     * @return \Illuminate\Database\Eloquent\Model
202
     * @throws \InvalidArgumentException
203
     */
204
    protected function resolveModel($data):Model
205
    {
206
        if ($data instanceof Model) {
207
            return $data;
208
        }
209
210
        $model = $data[0];
211
        if (! $model instanceof Model) {
212
            throw new InvalidArgumentException('You can only transform data containing Eloquent models.');
213
        }
214
215
        return $model;
216
    }
217
218
    /**
219
     * Resolve a transformer.
220
     *
221
     * @param  \Illuminate\Database\ELoquent\Model        $model
222
     * @param  \Flugg\Responder\Transformer|callable|null $transformer
223
     * @return \Flugg\Responder\Transformer|callable
224
     * @throws \InvalidTransformerException
225
     */
226
    protected function resolveTransformer(Model $model, $transformer = null)
227
    {
228
        if (is_null($transformer)) {
229
            $transformer = $this->resolveTransformerFromModel($model);
230
        }
231
232
        if (is_string($transformer)) {
233
            $transformer = new $transformer;
234
        }
235
236
        if ($transformer instanceof Transformer) {
237
            $this->setRelations($transformer, $model);
238
        } elseif (! is_callable($transformer)) {
239
            throw new InvalidTransformerException($model);
240
        }
241
242
        return $transformer;
243
    }
244
245
    /**
246
     * Resolve a transformer from the model. If the model is not transformable, a closure
247
     * based transformer will be created instead, from the model's fillable attributes.
248
     *
249
     * @param  \Illuminate\Database\ELoquent\Model $model
250
     * @return \Flugg\Responder\Transformer|callable
251
     */
252
    protected function resolveTransformerFromModel(Model $model)
253
    {
254
        if (! $model instanceof Transformable) {
255
            return function () use ($model) {
256
                return $model->toArray();
257
            };
258
        }
259
260
        return $model::ransformer();
261
    }
262
263
    /**
264
     * Set relations on the transformer and parse them using the manager.
265
     *
266
     * @param  \Flugg\Responder\Transformer        $transformer
267
     * @param  \Illuminate\Database\Eloquent\Model $model
268
     * @return void
269
     */
270
    protected function setRelations(Transformer $transformer, Model $model)
271
    {
272
        $transformer->setRelations($this->resolveRelations($model));
273
274
        $this->manager->parseIncludes($transformer->getRelations());
275
    }
276
277
    /**
278
     * Resolve eager loaded relations from the model.
279
     *
280
     * @param  \Illuminate\Database\Eloquent\Model $model
281
     * @return array
282
     */
283
    protected function resolveRelations(Model $model):array
284
    {
285
        return array_keys($model->getRelations());
286
    }
287
288
    /**
289
     * Resolve the resource key from the model.
290
     *
291
     * @param  \Illuminate\Database\Eloquent\Model $model
292
     * @param  string|null                         $resourceKey
293
     * @return string
294
     */
295
    protected function resolveResourceKey(Model $model, string $resourceKey = null):string
296
    {
297
        if (! is_null($resourceKey)) {
298
            return $resourceKey;
299
        }
300
301
        if (method_exists($model, 'getResourceKey')) {
302
            return $model->getResourceKey();
303
        }
304
305
        return $model->getTable();
306
    }
307
308
    /**
309
     * Serialize the transformation data.
310
     *
311
     * @param  ResourceInterface $resource
312
     * @return array
313
     */
314
    protected function serialize(ResourceInterface $resource):array
315
    {
316
        return $this->manager->createData($resource)->toArray();
317
    }
318
}