Completed
Push — master ( c915d2...2ad4b2 )
by Alexander
04:32
created

Responder::transformModel()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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