Completed
Push — master ( c703ef...5f521e )
by Eliurkis
01:36
created

CrudController   D

Complexity

Total Complexity 132

Size/Duplication

Total Lines 658
Duplicated Lines 5.62 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 22
Bugs 2 Features 1
Metric Value
wmc 132
c 22
b 2
f 1
lcom 1
cbo 4
dl 37
loc 658
rs 4.6323

24 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 4
C index() 0 59 10
A show() 18 18 2
A create() 0 14 1
B manageFiles() 0 18 5
B store() 0 30 4
A edit() 19 19 2
B update() 0 36 4
B destroy() 0 27 5
A download() 0 16 4
B filters() 0 21 7
B htmlFilters() 0 26 6
A paginate() 0 16 4
B search() 0 20 6
A getForeignRelationsFields() 0 11 3
A getBelongToFields() 0 11 4
A updateForeignRelations() 0 9 2
A getParamsFilters() 0 14 3
A prepareLinks() 0 12 4
C validateRequest() 0 31 7
D prepareRelationalFields() 0 29 9
A prepareFields() 0 14 3
F prepareFieldShow() 0 49 21
C prepareField() 0 51 12

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like CrudController 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 CrudController, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Eliurkis\Crud;
4
5
use App\Http\Controllers\Controller;
6
use DB;
7
use Illuminate\Database\Eloquent\Builder;
8
use Illuminate\Database\Eloquent\ModelNotFoundException;
9
use Illuminate\Database\QueryException;
10
use Illuminate\Http\Request;
11
use Illuminate\Support\Carbon;
12
use Illuminate\Support\Facades\Route;
13
14
class CrudController extends Controller
15
{
16
    protected $route;
17
    protected $entity;
18
    protected $entityInstance = null;
19
    protected $fields = [];
20
    protected $columns = [];
21
    protected $buttons = [
22
        'show',
23
        'create',
24
        'edit',
25
        'delete',
26
    ];
27
    protected $paginate = null;
28
    protected $searchable = [];
29
    protected $filters = [];
30
    protected $queryFilters = [];
31
    protected $orderBy = [];
32
    protected $orderByRaw = null;
33
    protected $filterRequire = [];
34
    protected $textsGeneral = [
35
        'list_title'   => 'Contents',
36
        'create_title' => '',
37
        'edit_title'   => '',
38
    ];
39
    protected $texts = [];
40
    protected $htmlFilters = [];
41
    protected $action = null;
42
    protected $formColsClasses = [
43
        'col-md-10 col-md-offset-1',
44
        'col-md-2',
45
        'col-md-10',
46
    ];
47
    protected $formCols = [
48
        'show'   => 2,
49
        'create' => 2,
50
        'edit'   => 2,
51
    ];
52
    protected $links = [];
53
    protected $listDisplay = [
54
        'action-buttons' => true,
55
    ];
56
57
    public function __construct($entity, $config = [])
58
    {
59
        $this->entity = $entity;
60
61
        $config = count($config) ? $config : config('crud.'.$this->route);
62
63
        if (is_array($config)) {
64
            foreach ($config as $key => $value) {
65
                $this->$key = $value;
66
            }
67
        }
68
    }
69
70
    public function index(Request $request)
71
    {
72
        // If DataTable is activated
73
        if (isset($this->dataTableActivated)) {
74
            return $this->indexDataTable($request);
75
        }
76
77
        $entity = $this->entity;
78
79
        // Relation Fields
80
        if ($belongToFields = $this->getBelongToFields()) {
81
            $entity = $this->entity->with($belongToFields);
82
        }
83
84
        // Filters
85
        $entity = $this->filters($entity, $request);
86
87
        // Search
88
        $entity = $this->search($entity, $request);
89
90
        // Order By
91
        if (!empty($this->orderBy)) {
92
            foreach ($this->orderBy as $column => $direction) {
93
                $entity = $entity->orderBy($column, $direction);
94
            }
95
        }
96
97
        if ($this->orderByRaw) {
98
            $entity = $entity->orderByRaw($this->orderByRaw);
99
        }
100
101
        // Pagination
102
        $rows = $this->paginate > 0 ? $this->paginate($entity, $request) : $entity->get();
103
104
        // Sort By Rows
105
        if (!empty($this->sortBy)) {
106
            foreach ($this->sortBy as $column => $direction) {
107
                $rows = strtolower($direction) == 'desc'
108
                    ? $rows->sortByDesc($column)
109
                    : $rows->sortBy($column);
110
            }
111
        }
112
113
        // HTML Filters
114
        $this->htmlFilters();
115
116
        return view('crud::list', compact('rows'))
117
            ->with('fields', $this->fields)
118
            ->with('columns', $this->columns)
119
            ->with('searchable', $this->searchable)
120
            ->with('buttons', $this->buttons)
121
            ->with('paginate', $this->paginate)
122
            ->with('t', $this->texts)
123
            ->with('htmlFilters', $this->htmlFilters)
124
            ->with('listDisplay', $this->listDisplay)
125
            ->with('links', $this->prepareLinks())
126
            ->with('request', $request)
127
            ->with('route', $this->route);
128
    }
129
130 View Code Duplication
    public function show($id)
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...
131
    {
132
        if (!$this->entityInstance) {
133
            $this->entityInstance = $this->entity->findOrFail($id);
134
        }
135
136
        $this->prepareFields();
137
138
        return view('crud::show')
139
            ->with('type', 'show')
140
            ->with('route', $this->route)
141
            ->with('t', $this->texts)
142
            ->with('formColsClasses', $this->formColsClasses)
143
            ->with('colsNumber', $this->formCols['show'])
144
            ->with('fieldsGroup', collect($this->fields)->split($this->formCols['show']))
145
            ->with('fields', $this->fields)
146
            ->with('data', $this->entityInstance);
147
    }
148
149
    public function create()
150
    {
151
        $this->prepareFields();
152
153
        return view('crud::create')
154
            ->with('type', 'create')
155
            ->with('route', $this->route)
156
            ->with('t', $this->texts)
157
            ->with('formColsClasses', $this->formColsClasses)
158
            ->with('links', $this->prepareLinks())
159
            ->with('colsNumber', $this->formCols['create'])
160
            ->with('fieldsGroup', collect($this->fields)->split($this->formCols['create']))
161
            ->with('fields', $this->fields);
162
    }
163
164
    protected function manageFiles($row, $request)
165
    {
166
        $mediaFiles = [];
167
168
        foreach ($this->fields as $fieldName => $field) {
169
            if ($field['type'] === 'file' && $request->file($fieldName)) {
170
                $customProperties = ['route' => $this->route, 'field' => $fieldName];
171
                if (isset($field['storage_path'])) {
172
                    $customProperties['storage_path'] = $field['storage_path'];
173
                }
174
                $mediaFiles[] = $row->addMedia($request->file($fieldName))
175
                    ->withCustomProperties($customProperties)
176
                    ->toMediaCollection($fieldName);
177
            }
178
        }
179
180
        return $mediaFiles;
181
    }
182
183
    public function store(Request $request)
184
    {
185
        $this->validateRequest($request, 'store');
186
187
        DB::beginTransaction();
188
189
        try {
190
            $row = $this->entity->create(array_merge($request->all(), $this->queryFilters));
191
            $this->updateForeignRelations($row, $request);
192
            $mediaFiles = $this->manageFiles($row, $request);
193
        } catch (QueryException $e) {
194
            \Log::error($e);
195
            if (config('app.debug')) {
196
                throw new \Exception($e);
197
            }
198
            return redirect()
199
                ->back()
200
                ->with('error', 'Ha ocurrido un error, intente nuevamente');
201
        }
202
203
        DB::commit();
204
205
        event($this->route.'.store', [$row, $mediaFiles]);
206
207
        return redirect()
208
            ->route($this->route.'.index')
209
            ->with('success', isset($this->textsGeneral['save_action'])
210
                ? $this->textsGeneral['save_action']
211
                : trans('eliurkis::crud.save_action'));
212
    }
213
214 View Code Duplication
    public function edit($id)
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...
215
    {
216
        if (!$this->entityInstance) {
217
            $this->entityInstance = $this->entity->findOrFail($id);
218
        }
219
220
        $this->prepareFields();
221
222
        return view('crud::create')
223
            ->with('type', 'edit')
224
            ->with('route', $this->route)
225
            ->with('t', $this->texts)
226
            ->with('formColsClasses', $this->formColsClasses)
227
            ->with('links', $this->prepareLinks())
228
            ->with('colsNumber', $this->formCols['edit'])
229
            ->with('fieldsGroup', collect($this->fields)->split($this->formCols['edit']))
230
            ->with('fields', $this->fields)
231
            ->with('data', $this->entityInstance);
232
    }
233
234
    public function update(Request $request, $id)
235
    {
236
        $this->validateRequest($request, 'update');
237
238
        DB::beginTransaction();
239
240
        try {
241
            $row = $this->entity->findOrFail($id);
242
            $row->update(
243
                array_merge(
244
                    $request->all(),
245
                    $this->queryFilters
246
                )
247
            );
248
            $this->updateForeignRelations($row, $request);
249
            $mediaFiles = $this->manageFiles($row, $request);
250
        } catch (QueryException $e) {
251
            \Log::error($e);
252
            if (config('app.debug')) {
253
                throw new \Exception($e);
254
            }
255
            return redirect()
256
                ->back()
257
                ->with('error', 'Ha ocurrido un error, intente nuevamente');
258
        }
259
260
        DB::commit();
261
262
        event($this->route.'.update', [$row, $mediaFiles]);
263
264
        return redirect()
265
            ->route($this->route.'.index', $this->getParamsFilters($row))
266
            ->with('success', isset($this->textsGeneral['save_action'])
267
                ? $this->textsGeneral['save_action']
268
                : trans('eliurkis::crud.save_action'));
269
    }
270
271
    public function destroy($id)
272
    {
273
        try {
274
            $row = $this->entity->findOrFail($id);
275
            $row->delete();
276
277
            event($this->route.'.destroy', [$row]);
278
        } catch (ModelNotFoundException $e) {
279
            return redirect()
280
                ->route($this->route.'.index')
281
                ->with('error', __('The element that you are trying to delete does not exist'));
282
        } catch (\Exception $e) {
283
            \Log::error($e);
284
            if (config('app.debug')) {
285
                throw new \Exception($e);
286
            }
287
            return redirect()
288
                ->route($this->route.'.index')
289
                ->with('error', __('An error occurred, try again'));
290
        }
291
292
        return redirect()
293
            ->route($this->route.'.index')
294
            ->with('success', isset($this->textsGeneral['delete_action'])
295
                ? $this->textsGeneral['delete_action']
296
                : trans('eliurkis::crud.delete_action'));
297
    }
298
299
    public function download($id, $fieldName)
300
    {
301
        if (!$this->entityInstance) {
302
            $this->entityInstance = $this->entity->findOrFail($id);
303
        }
304
305
        $media = $this->entityInstance->getMedia($fieldName)->last();
306
307
        if ($media && $media->disk === 's3') {
308
            $tempImage = tempnam(sys_get_temp_dir(), $media->file_name);
309
            copy($media->getTemporaryUrl(\Carbon::now()->addMinutes(5)), $tempImage);
310
            return response()->file($tempImage, ['Content-Type' => $media->mime_type]);
311
        }
312
313
        return $media;
314
    }
315
316
    /* Private Actions */
317
318
    /**
319
     * @param         $entity
320
     * @param Request $request
321
     *
322
     * @return mixed
323
     */
324
    protected function filters($entity, $request)
325
    {
326
        if ($request->query('filter')) {
327
            $filters = is_array($request->query('filter')) ? $request->query('filter') : [];
328
            foreach ($filters as $field => $value) {
329
                $entity = $entity->where($field, $value);
330
            }
331
        }
332
333
        if (count($this->queryFilters)) {
334
            foreach ($this->queryFilters as $field => $value) {
335
                if (is_array($value)) {
336
                    $entity = $entity->whereIn($field, $value);
337
                } else {
338
                    $entity = $entity->where($field, $value);
339
                }
340
            }
341
        }
342
343
        return $entity;
344
    }
345
346
    protected function htmlFilters()
347
    {
348
        $this->htmlFilters = [];
349
        if (count($this->filters)) {
350
            foreach ($this->filters as $filter) {
351
                // Build params
352
                $urlParams = \Input::query();
353
354
                // Default Value
355
                $this->fields[$filter]['config']['default_value'] = isset($urlParams['filter'][$filter])
356
                    ? $urlParams['filter'][$filter]
357
                    : null;
358
359
                // Create URL
360
                if (isset($urlParams['filter'][$filter])) {
361
                    unset($urlParams['filter'][$filter]);
362
                }
363
                $this->fields[$filter]['attributes']['data-filter-url'] = route($this->route.'.index', $urlParams)
364
                    .(count($urlParams) ? '&' : '?');
365
366
                // Create array
367
                $this->action = 'list';
368
                $this->htmlFilters[$filter] = $this->prepareField($filter);
369
            }
370
        }
371
    }
372
373
    /**
374
     * @param         $entity
375
     * @param Request $request
376
     *
377
     * @return mixed
378
     */
379
    protected function paginate($entity, $request)
380
    {
381
        $rows = $entity->paginate($this->paginate);
382
383
        if ($request->get('q') != '') {
384
            $rows->appends(['q' => $request->get('q')]);
385
        }
386
387
        if ($request->get('filter')) {
388
            foreach ($request->get('filter') as $field => $value) {
389
                $rows->appends(['filter['.$field.']' => $value]);
390
            }
391
        }
392
393
        return $rows;
394
    }
395
396
    /**
397
     * @param         $entity
398
     * @param Request $request
399
     *
400
     * @return mixed
401
     */
402
    protected function search($entity, $request)
403
    {
404
        if ($request->get('q') != '') {
405
            $searchableCols = isset($this->searchable['columns']) ? $this->searchable['columns'] : $this->searchable;
406
407
            $entity = $entity->where(function (Builder $query) use ($request, $searchableCols) {
408
                foreach ($searchableCols as $field) {
409
                    $query->orWhere($field, 'like', '%'.$request->get('q').'%');
410
                }
411
            });
412
413
            if (isset($this->searchable['joins'])) {
414
                foreach ($this->searchable['joins'] as $table => $joinFields) {
415
                    $entity = $entity->join($table, $joinFields[0], '=', $joinFields[1]);
416
                }
417
            }
418
        }
419
420
        return $entity;
421
    }
422
423
    protected function getForeignRelationsFields()
424
    {
425
        $foreignRelations = [];
426
        foreach ($this->fields as $field => $options) {
427
            if ($options['type'] === 'foreign') {
428
                $foreignRelations[] = $field;
429
            }
430
        }
431
432
        return $foreignRelations;
433
    }
434
435
    protected function getBelongToFields()
436
    {
437
        $fields = [];
438
        foreach ($this->fields as $field => $options) {
439
            if ($options['type'] === 'select' && isset($options['config']['rel'])) {
440
                $fields[] = $options['config']['rel'];
441
            }
442
        }
443
444
        return $fields;
445
    }
446
447
    /**
448
     * @param object  $row
449
     * @param Request $request
450
     */
451
    protected function updateForeignRelations($row, $request)
452
    {
453
        $foreignRelations = $this->getForeignRelationsFields();
454
455
        foreach ($foreignRelations as $foreignRelation) {
456
            $values = $request->get($foreignRelation);
457
            $row->$foreignRelation()->sync((array) $values);
458
        }
459
    }
460
461
    protected function getParamsFilters($row)
462
    {
463
        $params = [];
464
465
        if (count($this->filterRequire)) {
466
            $params['filter'] = [];
467
468
            foreach ($this->filterRequire as $field) {
469
                $params['filter'][$field] = $row->$field;
470
            }
471
        }
472
473
        return $params;
474
    }
475
476
    protected function prepareLinks()
477
    {
478
        $links = ['index', 'create', 'store'];
479
480
        foreach ($links as $link) {
481
            if (!isset($this->links[$link]) && Route::has($this->route.'.'.$link)) {
482
                $this->links[$link] = route($this->route.'.'.$link);
483
            }
484
        }
485
486
        return $this->links;
487
    }
488
489
    /**
490
     * @param Request $request
491
     * @param         $type
492
     */
493
    protected function validateRequest($request, $type)
494
    {
495
        $validations = [
496
            'rules'            => [],
497
            'messages'         => [],
498
            'customAttributes' => [],
499
        ];
500
501
        foreach ($this->fields as $field => $options) {
502
            $validation = null;
503
            if (isset($options['validation'][$type])) {
504
                $validation = $options['validation'][$type];
505
            } elseif (isset($options['validation']) && is_string($options['validation'])) {
506
                $validation = $options['validation'];
507
            }
508
509
            if ($validation != '') {
510
                $validations['rules'][$field] = $validation;
511
                $validations['customAttributes'][$field] = $options['label'];
512
            }
513
        }
514
515
        if ($validations['rules']) {
516
            $this->validate(
517
                $request,
518
                $validations['rules'],
519
                $validations['messages'],
520
                $validations['customAttributes']
521
            );
522
        }
523
    }
524
525
    protected function prepareRelationalFields($name)
526
    {
527
        // Default values
528
        $config = isset($this->fields[$name]['config']) ? $this->fields[$name]['config'] : [];
529
        $config['options'] = isset($config['options']) ? $config['options'] : [];
530
        $config['cols'] = isset($config['cols']) ? $config['cols'] : 1;
531
532
        // Get foreign values
533
        if (!count($config['options']) && isset($config['entity'])) {
534
            $config['options'] = $config['entity']::get()
535
                ->pluck($config['field_value'], $config['field_key'])
536
                ->toArray();
537
        }
538
539
        // No selection for filters
540
        if ($this->action == 'list' && isset($config['filter_no_selection'])) {
541
            $config['options'] = array_merge([
542
                '-1' => $config['filter_no_selection'],
543
            ], $config['options']);
544
        }
545
546
        if (isset($config['pre_options'])) {
547
            $config['options'] = $config['pre_options'] + $config['options'];
548
        }
549
550
        $this->fields[$name]['config'] = $config;
551
552
        return $this->fields[$name];
553
    }
554
555
    protected function prepareFields()
556
    {
557
        if ($this->entityInstance) {
558
            \Form::model($this->entityInstance);
559
        }
560
561
        foreach ($this->fields as $name => $properties) {
562
            $this->fields[$name]['html'] = $this->prepareField($name, $properties);
563
            $this->fields[$name]['value'] = $this->entityInstance->$name ?? null;
564
            $this->fields[$name]['value_text'] = $this->prepareFieldShow($name, $properties);
565
        }
566
567
        return $this->fields;
568
    }
569
570
    protected function prepareFieldShow($name, $properties = [])
571
    {
572
        // Init
573
        if (empty($properties)) {
574
            $properties = $this->fields[$name];
575
        }
576
577
        $this->fields[$name]['config'] = isset($properties['config']) ? $properties['config'] : [];
578
        $this->fields[$name]['attributes'] = isset($properties['attributes']) ? $properties['attributes'] : [];
579
        $config = $this->fields[$name]['config'];
580
581
        $value = $this->entityInstance
582
            ? ($this->entityInstance->$name ?? null)
583
            : (isset($config['default_value']) ? $config['default_value'] : null);
584
585
        if ($this->entityInstance) {
586
            if (($properties['type'] === 'date' || $properties['type'] === 'datetime') &&
587
                $this->entityInstance->$name != '') {
588
                $fieldValue = $this->entityInstance->$name;
589
590
                if (!is_object($fieldValue)) {
591
                    $fieldValue = Carbon::parse($this->entityInstance->$name);
592
                }
593
594
                $value = $fieldValue->diff(Carbon::now())->format('%y') != date('Y')
595
                    ? $fieldValue->format($properties['type'] === 'date' ? 'm/d/Y' : 'm/d/Y h:ia')
596
                    : null;
597
            }
598
599
            if ($properties['type'] === 'file' && $this->entityInstance->getMedia($name)->last()) {
600
                $value = '<a href="'.route($this->route.'.download', [$this->entityInstance->id, $name]).
601
                    '" target="_blank">'.(
602
                    isset($this->fields[$name]['link_name'])
603
                        ? $this->fields[$name]['link_name']
604
                        : 'download'
605
                    ).'</a>';
606
            }
607
608
            if (isset($config['entity'])) {
609
                $value = isset($this->entityInstance->{$config['rel']}->{$config['field_value']})
610
                    ? $this->entityInstance->{$config['rel']}->{$config['field_value']}
611
                    : null;
612
            } elseif (isset($config['options']) && count($config['options'])) {
613
                $value = $config['options'][$value] ?? null;
614
            }
615
        }
616
617
        return empty($value) ? 'N/A' : $value;
618
    }
619
620
    protected function prepareField($name, $properties = [])
621
    {
622
        // Init
623
        if (empty($properties)) {
624
            $properties = $this->fields[$name];
625
        }
626
627
        $this->fields[$name]['config'] = isset($properties['config']) ? $properties['config'] : [];
628
        $this->fields[$name]['attributes'] = isset($properties['attributes']) ? $properties['attributes'] : [];
629
        $this->fields[$name]['attributes']['class'] = 'form-control';
630
        $this->fields[$name]['html'] = null;
631
632
        $config = $this->fields[$name]['config'];
633
634
        $value = $this->entityInstance
635
            ? isset($properties['value_alias'])
636
                ? $this->entityInstance->{$properties['value_alias']}
637
                : $this->entityInstance->$name
638
            : ($this->fields[$name]['default_value'] ?? null);
639
640
        // Define field type class namespace
641
        $className = '\Eliurkis\Crud\FieldTypes\\'.ucfirst($properties['type']);
642
        if (!class_exists($className)) {
643
            return;
644
        }
645
646
        if ($properties['type'] == 'foreign' || $properties['type'] == 'select') {
647
            $properties = $this->prepareRelationalFields($name);
648
649
            if ($properties['type'] == 'foreign' && $this->entityInstance) {
650
                $value = $this->entityInstance->{$config['rel']}->pluck($config['field_key'])->toArray();
651
            }
652
653
            if ($properties['type'] == 'select') {
654
                $properties['attributes']['class'] = 'form-control chosen-select-width';
655
            }
656
657
            return $className::prepare(
658
                $name,
659
                $properties['config']['options'],
660
                $value,
661
                $properties
662
            );
663
        }
664
665
        return $className::prepare(
666
            $name,
667
            $value,
668
            $this->fields[$name]
669
        );
670
    }
671
}
672