Completed
Push — master ( 141581...0bfbf4 )
by Alexander
03:12
created

Responder::getErrorMessages()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 9.2
c 0
b 0
f 0
cc 4
eloc 8
nc 4
nop 2
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\Database\Eloquent\Builder;
10
use Illuminate\Http\JsonResponse;
11
use Illuminate\Support\Collection;
12
use InvalidArgumentException;
13
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
14
use League\Fractal\Resource\Collection as FractalCollection;
15
use League\Fractal\Resource\Item as FractalItem;
16
use League\Fractal\Resource\NullResource as FractalNull;
17
use League\Fractal\Resource\ResourceInterface;
18
19
/**
20
 * The responder service. This class is responsible for generating JSON API responses.
21
 * It can also transform and serialize data using Fractal behind the scenes.
22
 *
23
 * @package Laravel Responder
24
 * @author  Alexander Tømmerås <[email protected]>
25
 * @license The MIT License
26
 */
27
class Responder implements ResponderContract
28
{
29
    /**
30
     * Generate a successful JSON response.
31
     *
32
     * @param  mixed      $data
33
     * @param  int        $statusCode
34
     * @param  array|null $meta
35
     * @return JsonResponse
36
     */
37
    public function success( $data = null, $statusCode = 200, $meta = null ):JsonResponse
38
    {
39
        if ( is_integer( $data ) ) {
40
            list( $data, $statusCode, $meta ) = [ null, $data, $statusCode ];
41
        } elseif ( is_array( $statusCode ) ) {
42
            list( $statusCode, $meta ) = [ 200, $statusCode ];
43
        }
44
45
        $resource = $this->transform( $data );
46
47
        if ( is_array( $meta ) ) {
48
            $resource->setMeta( $meta );
49
        }
50
51
        $data = $this->serialize( $resource );
52
        $data = $this->includeStatusCode( $statusCode, $data );
53
54
        return response()->json( $data, $statusCode );
55
    }
56
57
    /**
58
     * Generate an unsuccessful JSON response.
59
     *
60
     * @param  string $errorCode
61
     * @param  int    $statusCode
62
     * @param  mixed  $message
63
     * @return JsonResponse
64
     */
65
    public function error( string $errorCode, int $statusCode = 500, $message = null ):JsonResponse
66
    {
67
        $response = $this->getErrorResponse( $errorCode, $statusCode );
68
        $messages = $this->getErrorMessages( $errorCode, $message );
69
70
        if ( count( $messages ) === 1 ) {
71
            $response[ 'error' ][ 'message' ] = $messages[ 0 ];
72
        } else if ( count( $messages ) > 1 ) {
73
            $response[ 'error' ][ 'messages' ] = $messages;
74
        }
75
76
        return response()->json( $response, $statusCode );
77
    }
78
79
    /**
80
     * Transforms the data.
81
     *
82
     * @param  mixed            $data
83
     * @param  Transformer|null $transformer
84
     * @return ResourceInterface
85
     */
86
    public function transform( $data = null, Transformer $transformer = null ):ResourceInterface
87
    {
88
        if ( is_null( $data ) ) {
89
            return new FractalNull();
90
        } elseif ( $data instanceof Transformable ) {
91
            return $this->transformModel( $data, $transformer );
92
        } elseif ( $data instanceof Collection ) {
93
            return $this->transformCollection( $data, $transformer );
94
        } elseif ( $data instanceof Builder ) {
95
            return $this->transformCollection( $data->get(), $transformer );
96
        } elseif ( $data instanceof LengthAwarePaginator ) {
97
            return $this->transformPaginator( $data, $transformer );
98
        }
99
100
        throw new InvalidArgumentException( 'Data must be one or multiple models implementing the Transformable contract.' );
101
    }
102
103
    /**
104
     * Serializes the data.
105
     *
106
     * @param  ResourceInterface $resource
107
     * @return array
108
     */
109
    public function serialize( ResourceInterface $resource ):array
110
    {
111
        $manager = app( Manager::class );
112
113
        $data = $resource->getData();
114
        $model = $data instanceof Collection ? $this->resolveModel( $data ) : $data;
115
116
        if ( ! is_null( $data ) ) {
117
            $transformer = $model::transformer();
118
            $includes = is_string( $transformer ) ? ( new $transformer( $model ) )->getAvailableIncludes() : [ ];
119
            $manager = $manager->parseIncludes( $includes );
120
        }
121
122
        return $manager->createData( $resource )->toArray();
123
    }
124
125
    /**
126
     * Transform a transformable Eloquent model.
127
     *
128
     * @param  Transformable    $model
129
     * @param  Transformer|null $transformer
130
     * @return ResourceInterface
131
     */
132 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...
133
    {
134
        $transformer = $transformer ?: $model::transformer();
135
136
        if ( is_null( $transformer ) ) {
137
            return new FractalItem( $model, function () use ( $model ) {
138
                return $model->toArray();
0 ignored issues
show
Bug introduced by
The method toArray() does not seem to exist on object<Flugg\Responder\Contracts\Transformable>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
139
            } );
140
        }
141
142
        return $this->transformData( $model, new $transformer( $model ), $model->getTable() );
143
    }
144
145
    /**
146
     * Transform a collection of Eloquent models.
147
     *
148
     * @param  Collection       $collection
149
     * @param  Transformer|null $transformer
150
     * @return ResourceInterface
151
     */
152 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...
153
    {
154
        $model = $this->resolveModel( $collection );
155
        $transformer = $transformer ?: $model::transformer();
156
157
        if ( is_null( $transformer ) ) {
158
            return new FractalCollection( $collection, function () use ( $collection ) {
159
                return $collection->toArray();
160
            } );
161
        }
162
163
        return $this->transformData( $collection, new $transformer( $model ), $model->getTable() );
164
    }
165
166
    /**
167
     * Transform paginated data using Laravel's paginator.
168
     *
169
     * @param LengthAwarePaginator $paginator
170
     * @param Transformer|null     $transformer
171
     * @return ResourceInterface
172
     */
173
    protected function transformPaginator( LengthAwarePaginator $paginator, Transformer $transformer = null ):ResourceInterface
174
    {
175
        $resource = $this->transformCollection( $paginator->getCollection(), $transformer );
176
        $resource->setPaginator( new IlluminatePaginatorAdapter( $paginator ) );
177
178
        return $resource;
179
    }
180
181
    /**
182
     * Transform the data using the given transformer.
183
     *
184
     * @param  Transformable|Collection $data
185
     * @param  Transformer|null         $transformer
186
     * @param  string                   $resourceKey
187
     * @return ResourceInterface
188
     */
189
    protected function transformData( $data, Transformer $transformer, string $resourceKey ):ResourceInterface
190
    {
191
        $class = $data instanceof Transformable ? FractalItem::class : FractalCollection::class;
192
        $resource = new $class( $data, $transformer );
193
        $resource->setResourceKey( $resourceKey );
194
195
        return $resource;
196
    }
197
198
    /**
199
     * Resolves model class path from a collection of models.
200
     *
201
     * @param  Collection $collection
202
     * @return Transformable
203
     * @throws InvalidArgumentException
204
     */
205
    protected function resolveModel( Collection $collection ):Transformable
206
    {
207
        $class = $collection->first();
208
209
        if ( ! $class instanceof Transformable ) {
210
            throw new InvalidArgumentException( 'Data must only contain models implementing the Transformable contract.' );
211
        }
212
213
        $collection->each( function ( $model ) use ( $class ) {
214
            if ( get_class( $model ) !== get_class( $class ) ) {
215
                throw new InvalidArgumentException( 'You cannot transform arrays or collections with multiple model types.' );
216
            }
217
        } );
218
219
        return $class;
220
    }
221
222
    /**
223
     * Here we prepend a status code to the response data, if status code is enabled in
224
     * the configuration file.
225
     *
226
     * @param  int   $statusCode
227
     * @param  array $data
228
     * @return array
229
     */
230
    protected function includeStatusCode( int $statusCode, array $data ):array
231
    {
232
        if ( ! config( 'responder.status_code' ) ) {
233
            return $data;
234
        }
235
236
        return array_merge( [
237
            'status' => $statusCode
238
        ], $data );
239
    }
240
241
    /**
242
     * Get the skeleton for an error response.
243
     *
244
     * @param string $errorCode
245
     * @param int    $statusCode
246
     * @return array
247
     */
248
    protected function getErrorResponse( string $errorCode, int $statusCode ):array
249
    {
250
        $response = [
251
            'success' => false,
252
            'error' => [
253
                'code' => $errorCode
254
            ]
255
        ];
256
257
        return $this->includeStatusCode( $statusCode, $response );
258
    }
259
260
    /**
261
     * Get any error messages for the response. If no message can be found it will
262
     * try to resolve a set message from the translator.
263
     *
264
     * @param  string $errorCode
265
     * @param  mixed  $message
266
     * @return array
267
     */
268
    protected function getErrorMessages( string $errorCode, $message ):array
269
    {
270
        if ( is_array( $message ) ) {
271
            return $message;
272
273
        } elseif ( is_string( $message ) ) {
274
            if ( strlen( $message ) === 0 ) {
275
                return [ ];
276
            }
277
278
            return [ $message ];
279
        }
280
281
        return [ app( 'translator' )->trans( 'errors.' . $errorCode ) ];
282
    }
283
}