Completed
Push — master ( 0a45a8...f73855 )
by Eliurkis
01:27
created

CrudController   D

Complexity

Total Complexity 90

Size/Duplication

Total Lines 502
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 13
Bugs 1 Features 1
Metric Value
wmc 90
c 13
b 1
f 1
lcom 1
cbo 2
dl 0
loc 502
rs 4.8717

21 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 4
B index() 0 45 6
A create() 0 12 1
A manageFiles() 0 10 3
B store() 0 25 3
A edit() 0 17 2
B update() 0 31 3
A destroy() 0 10 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 3
B validateRequest() 0 24 4
D prepareRelationalFields() 0 29 9
A prepareFields() 0 13 3
D prepareField() 0 49 12

How to fix   Complexity   

Complex Class

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