ApiController   B
last analyzed

Complexity

Total Complexity 44

Size/Duplication

Total Lines 521
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
wmc 44
lcom 1
cbo 7
dl 0
loc 521
rs 8.3396
c 0
b 0
f 0

31 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 14 2
model() 0 1 ?
transformer() 0 1 ?
A serializer() 0 4 1
A index() 0 12 2
A store() 0 19 3
A show() 0 11 2
B update() 0 25 4
A destroy() 0 12 2
A create() 0 4 1
A edit() 0 4 1
A respondWithItem() 0 8 1
A respondWithCollection() 0 13 2
A getStatusCode() 0 4 1
A setStatusCode() 0 6 1
A respond() 0 4 1
A respondWithError() 0 9 1
A prepareRootScope() 0 4 1
A rulesForCreate() 0 4 1
A rulesForUpdate() 0 4 1
A errorWrongArgs() 0 4 1
A errorUnauthorized() 0 4 1
A errorForbidden() 0 4 1
A errorNotFound() 0 4 1
A errorNotAllowed() 0 4 1
A errorInternalError() 0 4 1
A errorNotImplemented() 0 4 1
A getEagerLoad() 0 6 2
A findItem() 0 8 2
A unguardIfNeeded() 0 6 2
A calculateLimit() 0 6 3

How to fix   Complexity   

Complex Class

Complex classes like ApiController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ApiController, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Autumn\Api\Classes;
4
5
use Validator;
6
use League\Fractal\Manager;
7
use Illuminate\Http\Request;
8
use Illuminate\Http\Response;
9
use League\Fractal\Resource\Item;
10
use Illuminate\Routing\Controller;
11
use League\Fractal\Pagination\Cursor;
12
use League\Fractal\Resource\Collection;
13
14
abstract class ApiController extends Controller
15
{
16
    /**
17
     * Http status code.
18
     *
19
     * @var int
20
     */
21
    protected $statusCode = Response::HTTP_OK;
22
23
    /**
24
     * Fractal Manager instance.
25
     *
26
     * @var Manager
27
     */
28
    protected $fractal;
29
30
    /**
31
     * Model instance.
32
     *
33
     * @var \Illuminate\Database\Eloquent\Model;
34
     */
35
    protected $model;
36
37
    /**
38
     * Fractal Transformer instance.
39
     *
40
     * @var \League\Fractal\TransformerAbstract
41
     */
42
    protected $transformer;
43
44
    /**
45
     * Illuminate\Http\Request instance.
46
     *
47
     * @var Request
48
     */
49
    protected $request;
50
51
    /**
52
     * Do we need to unguard the model before create/update?
53
     *
54
     * @var bool
55
     */
56
    protected $unguard = false;
57
58
    /**
59
     * Number of items displayed at once if not specified.
60
     * There is no limit if it is 0 or false.
61
     *
62
     * @var int|bool
63
     */
64
    protected $defaultLimit = false;
65
66
    /**
67
     * Maximum limit that can be set via $_GET['limit'].
68
     *
69
     * @var int|bool
70
     */
71
    protected $maximumLimit = false;
72
73
    /**
74
     * Resource key for an item.
75
     *
76
     * @var string
77
     */
78
    protected $resourceKeySingular = 'data';
79
80
    /**
81
     * Resource key for a collection.
82
     *
83
     * @var string
84
     */
85
    protected $resourceKeyPlural = 'data';
86
87
    /**
88
     * Constructor.
89
     *
90
     * @param Request $request
91
     */
92
    public function __construct(Request $request)
93
    {
94
        $this->model = $this->model();
95
        $this->transformer = $this->transformer();
96
97
        $this->fractal = new Manager();
98
        $this->fractal->setSerializer($this->serializer());
99
100
        if ($includes = $this->transformer->getAvailableIncludes()) {
101
            $this->fractal->parseIncludes($includes);
102
        }
103
104
        $this->request = $request;
105
    }
106
107
    /**
108
     * Eloquent model.
109
     *
110
     * @return \October\Rain\Database\Model
111
     */
112
    abstract protected function model();
113
114
    /**
115
     * Transformer for the current model.
116
     *
117
     * @return \League\Fractal\TransformerAbstract
118
     */
119
    abstract protected function transformer();
120
121
    /**
122
     * Serializer for the current model.
123
     *
124
     * @return \League\Fractal\Serializer\SerializerAbstract
125
     */
126
    protected function serializer()
127
    {
128
        return new ArraySerializer();
129
    }
130
131
    /**
132
     * Display a listing of the resource.
133
     * GET /api/{resource}.
134
     *
135
     * @return Response
136
     */
137
    public function index()
138
    {
139
        $with = $this->getEagerLoad();
140
        $skip = (int) $this->request->input('skip', 0);
141
        $limit = $this->calculateLimit();
142
143
        $items = $limit
144
            ? $this->model->with($with)->skip($skip)->limit($limit)->get()
145
            : $this->model->with($with)->get();
146
147
        return $this->respondWithCollection($items, $skip, $limit);
148
    }
149
150
    /**
151
     * Store a newly created resource in storage.
152
     * POST /api/{resource}.
153
     *
154
     * @return Response
155
     */
156
    public function store()
157
    {
158
        $data = $this->request->json()->get($this->resourceKeySingular);
159
160
        if (! $data) {
161
            return $this->errorWrongArgs('Empty data');
162
        }
163
164
        $validator = Validator::make($data, $this->rulesForCreate());
165
        if ($validator->fails()) {
166
            return $this->errorWrongArgs($validator->messages());
167
        }
168
169
        $this->unguardIfNeeded();
170
171
        $item = $this->model->create($data);
172
173
        return $this->respondWithItem($item);
174
    }
175
176
    /**
177
     * Display the specified resource.
178
     * GET /api/{resource}/{id}.
179
     *
180
     * @param int $id
181
     *
182
     * @return Response
183
     */
184
    public function show($id)
185
    {
186
        $with = $this->getEagerLoad();
187
188
        $item = $this->findItem($id, $with);
189
        if (! $item) {
190
            return $this->errorNotFound();
191
        }
192
193
        return $this->respondWithItem($item);
194
    }
195
196
    /**
197
     * Update the specified resource in storage.
198
     * PUT /api/{resource}/{id}.
199
     *
200
     * @param int $id
201
     *
202
     * @return Response
203
     */
204
    public function update($id)
205
    {
206
        $data = $this->request->json()->get($this->resourceKeySingular);
207
208
        if (! $data) {
209
            return $this->errorWrongArgs('Empty data');
210
        }
211
212
        $item = $this->findItem($id);
213
        if (! $item) {
214
            return $this->errorNotFound();
215
        }
216
217
        $validator = Validator::make($data, $this->rulesForUpdate($item->id));
218
        if ($validator->fails()) {
219
            return $this->errorWrongArgs($validator->messages());
220
        }
221
222
        $this->unguardIfNeeded();
223
224
        $item->fill($data);
225
        $item->save();
226
227
        return $this->respondWithItem($item);
228
    }
229
230
    /**
231
     * Remove the specified resource from storage.
232
     * DELETE /api/{resource}/{id}.
233
     *
234
     * @param int $id
235
     *
236
     * @return Response
237
     */
238
    public function destroy($id)
239
    {
240
        $item = $this->findItem($id);
241
242
        if (! $item) {
243
            return $this->errorNotFound();
244
        }
245
246
        $item->delete();
247
248
        return $this->respond(['message' => 'Deleted']);
249
    }
250
251
    /**
252
     * Show the form for creating the specified resource.
253
     *
254
     * @return Response
255
     */
256
    public function create()
257
    {
258
        return $this->errorNotImplemented();
259
    }
260
261
    /**
262
     * Show the form for editing the specified resource.
263
     *
264
     * @param int $id
265
     *
266
     * @return Response
267
     */
268
    public function edit($id)
269
    {
270
        return $this->errorNotImplemented();
271
    }
272
273
    /**
274
     * Respond with a given item.
275
     *
276
     * @param $item
277
     *
278
     * @return mixed
279
     */
280
    protected function respondWithItem($item)
281
    {
282
        $resource = new Item($item, $this->transformer, $this->resourceKeySingular);
283
284
        $rootScope = $this->prepareRootScope($resource);
285
286
        return $this->respond($rootScope->toArray());
287
    }
288
289
    /**
290
     * Respond with a given collection.
291
     *
292
     * @param $collection
293
     * @param int $skip
294
     * @param int $limit
295
     *
296
     * @return mixed
297
     */
298
    protected function respondWithCollection($collection, $skip = 0, $limit = 0)
299
    {
300
        $resource = new Collection($collection, $this->transformer, $this->resourceKeyPlural);
301
302
        if ($limit) {
303
            $cursor = new Cursor($skip, $skip + $limit, $collection->count());
304
            $resource->setCursor($cursor);
305
        }
306
307
        $rootScope = $this->prepareRootScope($resource);
308
309
        return $this->respond($rootScope->toArray());
310
    }
311
312
    /**
313
     * Get the http status code.
314
     *
315
     * @return int
316
     */
317
    protected function getStatusCode()
318
    {
319
        return $this->statusCode;
320
    }
321
322
    /**
323
     * Set the http status code.
324
     *
325
     * @param int $statusCode
326
     *
327
     * @return $this
328
     */
329
    protected function setStatusCode($statusCode)
330
    {
331
        $this->statusCode = $statusCode;
332
333
        return $this;
334
    }
335
336
    /**
337
     * Send the response as json.
338
     *
339
     * @param array $data
340
     * @param array $headers
341
     *
342
     * @return \Illuminate\Http\JsonResponse
343
     */
344
    protected function respond($data = [], array $headers = [])
345
    {
346
        return response()->json($data, $this->statusCode, $headers);
347
    }
348
349
    /**
350
     * Send the error response as json.
351
     *
352
     * @param string $message
353
     *
354
     * @return \Illuminate\Http\JsonResponse
355
     */
356
    protected function respondWithError($message)
357
    {
358
        return $this->respond([
359
            'error' => [
360
                'message' => $message,
361
                'status_code' => $this->statusCode,
362
            ],
363
        ]);
364
    }
365
366
    /**
367
     * Prepare root scope and set some meta information.
368
     *
369
     * @param Item|Collection $resource
370
     *
371
     * @return \League\Fractal\Scope
372
     */
373
    protected function prepareRootScope($resource)
374
    {
375
        return $this->fractal->createData($resource);
376
    }
377
378
    /**
379
     * Get the validation rules for create.
380
     *
381
     * @return array
382
     */
383
    protected function rulesForCreate()
384
    {
385
        return [];
386
    }
387
388
    /**
389
     * Get the validation rules for update.
390
     *
391
     * @param int $id
392
     *
393
     * @return array
394
     */
395
    protected function rulesForUpdate($id)
396
    {
397
        return [];
398
    }
399
400
    /**
401
     * Generate a Response with a 400 HTTP header and a given message.
402
     *
403
     * @param string $message
404
     *
405
     * @return Response
406
     */
407
    protected function errorWrongArgs($message = 'Wrong Arguments')
408
    {
409
        return $this->setStatusCode(Response::HTTP_BAD_REQUEST)->respondWithError($message);
410
    }
411
412
    /**
413
     * Generate a Response with a 401 HTTP header and a given message.
414
     *
415
     * @param string $message
416
     *
417
     * @return Response
418
     */
419
    protected function errorUnauthorized($message = 'Unauthorized')
420
    {
421
        return $this->setStatusCode(Response::HTTP_UNAUTHORIZED)->respondWithError($message);
422
    }
423
424
    /**
425
     * Generate a Response with a 403 HTTP header and a given message.
426
     *
427
     * @param string $message
428
     *
429
     * @return Response
430
     */
431
    protected function errorForbidden($message = 'Forbidden')
432
    {
433
        return $this->setStatusCode(Response::HTTP_FORBIDDEN)->respondWithError($message);
434
    }
435
436
    /**
437
     * Generate a Response with a 404 HTTP header and a given message.
438
     *
439
     * @param string $message
440
     *
441
     * @return Response
442
     */
443
    protected function errorNotFound($message = 'Resource Not Found')
444
    {
445
        return $this->setStatusCode(Response::HTTP_NOT_FOUND)->respondWithError($message);
446
    }
447
448
    /**
449
     * Generate a Response with a 405 HTTP header and a given message.
450
     *
451
     * @param string $message
452
     *
453
     * @return Response
454
     */
455
    protected function errorNotAllowed($message = 'Method Not Allowed')
456
    {
457
        return $this->setStatusCode(Response::HTTP_METHOD_NOT_ALLOWED)->respondWithError($message);
458
    }
459
460
    /**
461
     * Generate a Response with a 500 HTTP header and a given message.
462
     *
463
     * @param string $message
464
     *
465
     * @return Response
466
     */
467
    protected function errorInternalError($message = 'Internal Error')
468
    {
469
        return $this->setStatusCode(Response::HTTP_INTERNAL_SERVER_ERROR)->respondWithError($message);
470
    }
471
472
    /**
473
     * Generate a Response with a 501 HTTP header and a given message.
474
     *
475
     * @param string $message
476
     *
477
     * @return Response
478
     */
479
    protected function errorNotImplemented($message = 'Not implemented')
480
    {
481
        return $this->setStatusCode(Response::HTTP_NOT_IMPLEMENTED)->respondWithError($message);
482
    }
483
484
    /**
485
     * Specify relations for eager loading.
486
     *
487
     * @return array
488
     */
489
    protected function getEagerLoad()
490
    {
491
        $includes = $this->transformer->getAvailableIncludes();
492
493
        return $includes ?: [];
494
    }
495
496
    /**
497
     * Get item according to mode.
498
     *
499
     * @param int   $id
500
     * @param array $with
501
     *
502
     * @return mixed
503
     */
504
    protected function findItem($id, array $with = [])
505
    {
506
        if ($this->request->has('use_as_id')) {
507
            return $this->model->with($with)->where($this->request->input('use_as_id'), '=', $id)->first();
508
        }
509
510
        return $this->model->with($with)->find($id);
511
    }
512
513
    /**
514
     * Unguard eloquent model if needed.
515
     */
516
    protected function unguardIfNeeded()
517
    {
518
        if ($this->unguard) {
519
            $this->model->unguard();
520
        }
521
    }
522
523
    /**
524
     * Calculates limit for a number of items displayed in list.
525
     *
526
     * @return int
527
     */
528
    protected function calculateLimit()
529
    {
530
        $limit = (int) $this->request->input('limit', $this->defaultLimit);
531
532
        return ($this->maximumLimit && $this->maximumLimit < $limit) ? $this->maximumLimit : $limit;
533
    }
534
}
535