Completed
Push — master ( ba0c74...804aaf )
by Eliurkis
04:55
created

CrudController   D

Complexity

Total Complexity 114

Size/Duplication

Total Lines 578
Duplicated Lines 5.71 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 15
Bugs 2 Features 1
Metric Value
wmc 114
c 15
b 2
f 1
lcom 1
cbo 2
dl 33
loc 578
rs 4.8717

24 Methods

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