Completed
Push — master ( 11ab51...fc8810 )
by Alexander
04:57
created

SuccessResponseBuilder::resolveSerializer()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
c 0
b 0
f 0
nc 4
nop 1
dl 0
loc 12
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;
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
    public function setStatus(int $statusCode):ResponseBuilder
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);
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 return function () use ($model) {
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_RETURN, expecting ';'
Loading history...
256
                return $model->toArray();
257
            };
258
        }
259
260
        return $model::transformer();
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
}