Completed
Push — master ( 47a9a3...099793 )
by Alexander
03:06
created

Responder::success()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 13
nc 8
nop 3
dl 0
loc 21
rs 8.7624
c 0
b 0
f 0
1
<?php
2
3
namespace Flugg\Responder;
4
5
use Flugg\Responder\Contracts\Manager;
6
use Flugg\Responder\Contracts\Responder as ResponderContract;
7
use Flugg\Responder\Contracts\Transformable;
8
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
9
use Illuminate\Http\JsonResponse;
10
use Illuminate\Support\Collection;
11
use InvalidArgumentException;
12
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
13
use League\Fractal\Resource\Collection as FractalCollection;
14
use League\Fractal\Resource\Item as FractalItem;
15
use League\Fractal\Resource\NullResource as FractalNull;
16
use League\Fractal\Resource\ResourceInterface;
17
18
/**
19
 * The responder service. This class is responsible for generating JSON API responses.
20
 * It can also transform and serialize data using Fractal behind the scenes.
21
 *
22
 * @package Laravel Responder
23
 * @author  Alexander Tømmerås <[email protected]>
24
 * @license The MIT License
25
 */
26
class Responder implements ResponderContract
27
{
28
    /**
29
     * Generate a successful JSON response.
30
     *
31
     * @param  mixed      $data
32
     * @param  int        $statusCode
33
     * @param  array|null $meta
34
     * @return JsonResponse
35
     */
36
    public function success( $data = null, $statusCode = 200, $meta = null ):JsonResponse
37
    {
38
        if ( is_integer( $data ) ) {
39
            list( $statusCode, $data ) = [ $data, null ];
40
        } elseif ( is_array( $statusCode ) ) {
41
            list( $statusCode, $meta ) = [ 200, $statusCode ];
42
        } elseif ( is_array( $data ) ) {
43
            list( $data, $statusCode, $meta ) = [ null, 200, $data ];
44
        }
45
46
        $resource = $this->transform( $data );
47
48
        if ( is_array( $meta ) ) {
49
            $resource->setMeta( $meta );
50
        }
51
52
        $data = $this->serialize( $resource );
53
        $data = $this->includeStatusCode( $statusCode, $data );
54
55
        return response()->json( $data, $statusCode );
56
    }
57
58
    /**
59
     * Generate an unsuccessful JSON response.
60
     *
61
     * @param  string $errorCode
62
     * @param  int    $statusCode
63
     * @param  mixed  $message
64
     * @return JsonResponse
65
     */
66
    public function error( string $errorCode, int $statusCode = 500, $message = null ):JsonResponse
67
    {
68
        $response = $this->getErrorResponse( $errorCode, $statusCode );
69
        $messages = $this->getErrorMessages( $errorCode, $message );
70
71
        if ( count( $messages ) === 1 ) {
72
            $response[ 'error' ][ 'message' ] = $messages[ 0 ];
73
        } else if ( count( $messages ) > 1 ) {
74
            $response[ 'error' ][ 'messages' ] = $messages;
75
        }
76
77
        return response()->json( $response, $statusCode );
78
    }
79
80
    /**
81
     * Transforms the data.
82
     *
83
     * @param  mixed            $data
84
     * @param  Transformer|null $transformer
85
     * @return ResourceInterface
86
     */
87
    public function transform( $data = null, Transformer $transformer = null ):ResourceInterface
88
    {
89
        if ( is_null( $data ) ) {
90
            return new FractalNull();
91
        } elseif ( $data instanceof Transformable ) {
92
            return $this->transformModel( $data, $transformer );
93
        } elseif ( $data instanceof Collection ) {
94
            return $this->transformCollection( $data, $transformer );
95
        } elseif ( $data instanceof LengthAwarePaginator ) {
96
            return $this->transformPaginator( $data, $transformer );
97
        }
98
99
        throw new InvalidArgumentException( 'Data must be one or multiple models implementing the Transformable contract.' );
100
    }
101
102
    /**
103
     * Serializes the data.
104
     *
105
     * @param  ResourceInterface $resource
106
     * @return array
107
     */
108
    public function serialize( ResourceInterface $resource ):array
109
    {
110
        $manager = app( Manager::class );
111
112
        $data = $resource->getData();
113
        $model = $data instanceof Collection ? $this->resolveModel( $data ) : $data;
114
115
        if ( ! is_null( $data ) ) {
116
            $transformer = $model::transformer();
117
            $includes = ( new $transformer( $model ) )->getAvailableIncludes();
118
            $manager = $manager->parseIncludes( $includes );
119
        }
120
121
        return $manager->createData( $resource )->toArray();
122
    }
123
124
    /**
125
     * Transform a transformable Eloquent model.
126
     *
127
     * @param  Transformable    $model
128
     * @param  Transformer|null $transformer
129
     * @return ResourceInterface
130
     */
131 View Code Duplication
    protected function transformModel( Transformable $model, Transformer $transformer = null ):ResourceInterface
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...
132
    {
133
        $transformer = $transformer ?: $model::transformer();
134
135
        if ( is_null( $transformer ) ) {
136
            return new FractalNull();
137
        }
138
139
        return $this->transformData( $model, new $transformer( $model ), $model->getTable() );
140
    }
141
142
    /**
143
     * Transform a collection of Eloquent models.
144
     *
145
     * @param  Collection       $collection
146
     * @param  Transformer|null $transformer
147
     * @return ResourceInterface
148
     */
149 View Code Duplication
    protected function transformCollection( Collection $collection, Transformer $transformer = null ):ResourceInterface
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...
150
    {
151
        $model = $this->resolveModel( $collection );
152
        $transformer = $transformer ?: $model::transformer();
153
154
        if ( is_null( $transformer ) ) {
155
            return new FractalNull();
156
        }
157
158
        return $this->transformData( $collection, new $transformer( $model ), $model->getTable() );
159
    }
160
161
    /**
162
     * Transform paginated data using Laravel's paginator.
163
     *
164
     * @param LengthAwarePaginator $paginator
165
     * @param Transformer|null     $transformer
166
     * @return ResourceInterface
167
     */
168
    protected function transformPaginator( LengthAwarePaginator $paginator, Transformer $transformer = null ):ResourceInterface
169
    {
170
        $resource = $this->transformCollection( $paginator->getCollection(), $transformer );
171
        $resource->setPaginator( new IlluminatePaginatorAdapter( $paginator ) );
172
173
        return $resource;
174
    }
175
176
    /**
177
     * Transform the data using the given transformer.
178
     *
179
     * @param  Transformable|Collection $data
180
     * @param  Transformer|null         $transformer
181
     * @param  string                   $resourceKey
182
     * @return ResourceInterface
183
     */
184
    protected function transformData( $data, Transformer $transformer, string $resourceKey ):ResourceInterface
185
    {
186
        $class = $data instanceof Transformable ? FractalItem::class : FractalCollection::class;
187
        $resource = new $class( $data, $transformer );
188
        $resource->setResourceKey( $resourceKey );
189
190
        return $resource;
191
    }
192
193
    /**
194
     * Resolves model class path from a collection of models.
195
     *
196
     * @param  Collection $collection
197
     * @return Transformable
198
     * @throws InvalidArgumentException
199
     */
200
    protected function resolveModel( Collection $collection ):Transformable
201
    {
202
        $class = $collection->first();
203
204
        if ( ! $class instanceof Transformable ) {
205
            throw new InvalidArgumentException( 'Data must only contain models implementing the Transformable contract.' );
206
        }
207
208
        $collection->each( function ( $model ) use ( $class ) {
209
            if ( get_class( $model ) !== get_class( $class ) ) {
210
                throw new InvalidArgumentException( 'You cannot transform arrays or collections with multiple model types.' );
211
            }
212
        } );
213
214
        return $class;
215
    }
216
217
    /**
218
     * Here we prepend a status code to the response data, if status code is enabled in
219
     * the configuration file.
220
     *
221
     * @param  int   $statusCode
222
     * @param  array $data
223
     * @return array
224
     */
225
    protected function includeStatusCode( int $statusCode, array $data ):array
226
    {
227
        if ( ! config( 'responder.status_code' ) ) {
228
            return $data;
229
        }
230
231
        return array_merge( [
232
            'status' => $statusCode
233
        ], $data );
234
    }
235
236
    /**
237
     * Get the skeleton for an error response.
238
     *
239
     * @param string $errorCode
240
     * @param int    $statusCode
241
     * @return array
242
     */
243
    protected function getErrorResponse( string $errorCode, int $statusCode ):array
244
    {
245
        $response = [
246
            'success' => false,
247
            'error' => [
248
                'code' => $errorCode
249
            ]
250
        ];
251
252
        return $this->includeStatusCode( $statusCode, $response );
253
    }
254
255
    /**
256
     * Get any error messages for the response. If no message can be found it will
257
     * try to resolve a set message from the translator.
258
     *
259
     * @param  string $errorCode
260
     * @param  mixed  $message
261
     * @return array
262
     */
263
    protected function getErrorMessages( string $errorCode, $message ):array
264
    {
265
        if ( is_array( $message ) ) {
266
            return $message;
267
268
        } elseif ( is_string( $message ) ) {
269
            if ( strlen( $message ) === 0 ) {
270
                return [ ];
271
            }
272
273
            return [ $message ];
274
        }
275
276
        return [ app( 'translator' )->trans( 'errors.' . $errorCode ) ];
277
    }
278
}