Completed
Push — master ( a3397f...4001aa )
by Alexander
03:06
created

Responder::success()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 11
nc 6
nop 3
dl 0
loc 19
rs 9.2
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( $data, $statusCode, $meta ) = [ null, $data, $statusCode ];
40
        } elseif ( is_array( $statusCode ) ) {
41
            list( $statusCode, $meta ) = [ 200, $statusCode ];
42
        }
43
44
        $resource = $this->transform( $data );
45
46
        if ( is_array( $meta ) ) {
47
            $resource->setMeta( $meta );
48
        }
49
50
        $data = $this->serialize( $resource );
51
        $data = $this->includeStatusCode( $statusCode, $data );
52
53
        return response()->json( $data, $statusCode );
54
    }
55
56
    /**
57
     * Generate an unsuccessful JSON response.
58
     *
59
     * @param  string $errorCode
60
     * @param  int    $statusCode
61
     * @param  mixed  $message
62
     * @return JsonResponse
63
     */
64
    public function error( string $errorCode, int $statusCode = 500, $message = null ):JsonResponse
65
    {
66
        $response = $this->getErrorResponse( $errorCode, $statusCode );
67
        $messages = $this->getErrorMessages( $errorCode, $message );
68
69
        if ( count( $messages ) === 1 ) {
70
            $response[ 'error' ][ 'message' ] = $messages[ 0 ];
71
        } else if ( count( $messages ) > 1 ) {
72
            $response[ 'error' ][ 'messages' ] = $messages;
73
        }
74
75
        return response()->json( $response, $statusCode );
76
    }
77
78
    /**
79
     * Transforms the data.
80
     *
81
     * @param  mixed            $data
82
     * @param  Transformer|null $transformer
83
     * @return ResourceInterface
84
     */
85
    public function transform( $data = null, Transformer $transformer = null ):ResourceInterface
86
    {
87
        if ( is_null( $data ) ) {
88
            return new FractalNull();
89
        } elseif ( $data instanceof Transformable ) {
90
            return $this->transformModel( $data, $transformer );
91
        } elseif ( $data instanceof Collection ) {
92
            return $this->transformCollection( $data, $transformer );
93
        } elseif ( $data instanceof Builder ) {
0 ignored issues
show
Bug introduced by
The class Flugg\Responder\Builder does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
94
            return $this->transformCollection( $data->get(), $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
}