Completed
Pull Request — master (#1350)
by
unknown
03:06
created

Column   C

Complexity

Total Complexity 58

Size/Duplication

Total Lines 578
Duplicated Lines 2.42 %

Coupling/Cohesion

Components 2
Dependencies 3

Importance

Changes 0
Metric Value
dl 14
loc 578
rs 6.3005
c 0
b 0
f 0
wmc 58
lcom 2
cbo 3

30 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A extend() 0 4 1
A define() 0 4 1
A setGrid() 0 6 1
A setModel() 0 6 3
A setOriginalGridData() 0 4 1
A setAttributes() 0 6 1
A getAttributes() 0 4 1
A style() 0 4 1
A getName() 0 4 1
A formatLabel() 0 6 2
A getLabel() 0 4 1
A setRelation() 0 7 1
A isRelation() 0 4 1
A sortable() 0 6 1
A display() 0 6 1
A hasDisplayCallbacks() 0 4 1
A callDisplayCallbacks() 0 9 2
A bindOriginalRow() 0 6 1
A fill() 0 21 4
A isDefinedColumn() 0 4 1
B useDefinedColumn() 0 26 4
A htmlEntityEncode() 0 12 2
A sorter() 0 21 4
A isSorted() 0 10 3
A resolveDisplayer() 0 8 2
A callSupportDisplayer() 0 14 4
A callBuiltinDisplayer() 0 21 4
A setLabel() 14 14 3
A __call() 0 14 4

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 Column 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 Column, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Encore\Admin\Grid;
4
5
use Closure;
6
use Encore\Admin\Grid;
7
use Encore\Admin\Grid\Displayers\AbstractDisplayer;
8
use Illuminate\Contracts\Support\Arrayable;
9
use Illuminate\Database\Eloquent\Model;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Encore\Admin\Grid\Model.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
10
use Illuminate\Support\Facades\URL;
11
use Illuminate\Support\Str;
12
use Lang;
13
14
class Column
15
{
16
    /**
17
     * @var Grid
18
     */
19
    protected $grid;
20
21
    /**
22
     * Name of column.
23
     *
24
     * @var string
25
     */
26
    protected $name;
27
28
    /**
29
     * Label of column.
30
     *
31
     * @var string
32
     */
33
    protected $label;
34
35
    /**
36
     * Original value of column.
37
     *
38
     * @var mixed
39
     */
40
    protected $original;
41
42
    /**
43
     * Is column sortable.
44
     *
45
     * @var bool
46
     */
47
    protected $sortable = false;
48
49
    /**
50
     * Sort arguments.
51
     *
52
     * @var array
53
     */
54
    protected $sort;
55
56
    /**
57
     * Attributes of column.
58
     *
59
     * @var array
60
     */
61
    protected $attributes = [];
62
63
    /**
64
     * Relation name.
65
     *
66
     * @var bool
67
     */
68
    protected $relation = false;
69
70
    /**
71
     * Relation column.
72
     *
73
     * @var string
74
     */
75
    protected $relationColumn;
76
77
    /**
78
     * Original grid data.
79
     *
80
     * @var array
81
     */
82
    protected static $originalGridData = [];
83
84
    /**
85
     * @var []Closure
86
     */
87
    protected $displayCallbacks = [];
88
89
    /**
90
     * Displayers for grid column.
91
     *
92
     * @var array
93
     */
94
    public static $displayers = [];
95
96
    /**
97
     * Defined columns.
98
     *
99
     * @var array
100
     */
101
    public static $defined = [];
102
103
    /**
104
     * @var array
105
     */
106
    protected static $htmlAttributes = [];
107
108
    /**
109
     * @var
110
     */
111
    protected static $model;
112
113
    const SELECT_COLUMN_NAME = '__row_selector__';
114
115
    /**
116
     * @param string $name
117
     * @param string $label
118
     */
119
    public function __construct($name, $label)
120
    {
121
        $this->name = $name;
122
123
        $this->label = $this->formatLabel($label);
124
    }
125
126
    /**
127
     * Extend column displayer.
128
     *
129
     * @param $name
130
     * @param $displayer
131
     */
132
    public static function extend($name, $displayer)
133
    {
134
        static::$displayers[$name] = $displayer;
135
    }
136
137
    /**
138
     * Define a column globally.
139
     *
140
     * @param string $name
141
     * @param mixed  $definition
142
     */
143
    public static function define($name, $definition)
144
    {
145
        static::$defined[$name] = $definition;
146
    }
147
148
    /**
149
     * Set grid instance for column.
150
     *
151
     * @param Grid $grid
152
     */
153
    public function setGrid(Grid $grid)
154
    {
155
        $this->grid = $grid;
156
157
        $this->setModel($grid->model()->eloquent());
158
    }
159
160
    /**
161
     * Set model for column.
162
     *
163
     * @param $model
164
     */
165
    public function setModel($model)
166
    {
167
        if (is_null(static::$model) && ($model instanceof Model)) {
168
            static::$model = $model->newInstance();
169
        }
170
    }
171
172
    /**
173
     * Set original data for column.
174
     *
175
     * @param array $input
176
     */
177
    public static function setOriginalGridData(array $input)
178
    {
179
        static::$originalGridData = $input;
180
    }
181
182
    /**
183
     * Set column attributes.
184
     *
185
     * @param array $attributes
186
     *
187
     * @return $this
188
     */
189
    public function setAttributes($attributes = [])
190
    {
191
        static::$htmlAttributes[$this->name] = $attributes;
192
193
        return $this;
194
    }
195
196
    /**
197
     * Get column attributes.
198
     *
199
     * @param string $name
200
     *
201
     * @return mixed
202
     */
203
    public static function getAttributes($name)
204
    {
205
        return array_get(static::$htmlAttributes, $name, '');
206
    }
207
208
    /**
209
     * Set style of this column.
210
     *
211
     * @param string $style
212
     *
213
     * @return Column
214
     */
215
    public function style($style)
216
    {
217
        return $this->setAttributes(compact('style'));
218
    }
219
220
    /**
221
     * Get name of this column.
222
     *
223
     * @return mixed
224
     */
225
    public function getName()
226
    {
227
        return $this->name;
228
    }
229
230
    /**
231
     * Format label.
232
     *
233
     * @param $label
234
     *
235
     * @return mixed
236
     */
237
    protected function formatLabel($label)
238
    {
239
        $label = $label ?: ucfirst($this->name);
240
241
        return str_replace(['.', '_'], ' ', $label);
242
    }
243
244
    /**
245
     * Get label of the column.
246
     *
247
     * @return mixed
248
     */
249
    public function getLabel()
250
    {
251
        return $this->label;
252
    }
253
254
    /**
255
     * Set relation.
256
     *
257
     * @param string $relation
258
     * @param string $relationColumn
259
     *
260
     * @return $this
261
     */
262
    public function setRelation($relation, $relationColumn = null)
263
    {
264
        $this->relation = $relation;
0 ignored issues
show
Documentation Bug introduced by
The property $relation was declared of type boolean, but $relation is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
265
        $this->relationColumn = $relationColumn;
266
267
        return $this;
268
    }
269
270
    /**
271
     * If this column is relation column.
272
     *
273
     * @return bool
274
     */
275
    protected function isRelation()
276
    {
277
        return (bool) $this->relation;
278
    }
279
280
    /**
281
     * Mark this column as sortable.
282
     *
283
     * @return Column
284
     */
285
    public function sortable()
286
    {
287
        $this->sortable = true;
288
289
        return $this;
290
    }
291
292
    /**
293
     * Add a display callback.
294
     *
295
     * @param Closure $callback
296
     *
297
     * @return $this
298
     */
299
    public function display(Closure $callback)
300
    {
301
        $this->displayCallbacks[] = $callback;
302
303
        return $this;
304
    }
305
306
    /**
307
     * If has display callbacks.
308
     *
309
     * @return bool
310
     */
311
    protected function hasDisplayCallbacks()
312
    {
313
        return !empty($this->displayCallbacks);
314
    }
315
316
    /**
317
     * Call all of the "display" callbacks column.
318
     *
319
     * @param mixed $value
320
     * @param int   $key
321
     *
322
     * @return mixed
323
     */
324
    protected function callDisplayCallbacks($value, $key)
325
    {
326
        foreach ($this->displayCallbacks as $callback) {
327
            $callback = $this->bindOriginalRow($callback, $key);
328
            $value = call_user_func($callback, $value);
329
        }
330
331
        return $value;
332
    }
333
334
    /**
335
     * Set original grid data to column.
336
     *
337
     * @param Closure $callback
338
     * @param int     $key
339
     *
340
     * @return Closure
341
     */
342
    protected function bindOriginalRow(Closure $callback, $key)
343
    {
344
        $originalRow = static::$originalGridData[$key];
345
346
        return $callback->bindTo(static::$model->newFromBuilder($originalRow));
347
    }
348
349
    /**
350
     * Fill all data to every column.
351
     *
352
     * @param array $data
353
     *
354
     * @return mixed
355
     */
356
    public function fill(array $data)
357
    {
358
        foreach ($data as $key => &$row) {
359
            $this->original = $value = array_get($row, $this->name);
360
361
            $value = $this->htmlEntityEncode($value);
362
363
            array_set($row, $this->name, $value);
364
365
            if ($this->isDefinedColumn()) {
366
                $this->useDefinedColumn();
367
            }
368
369
            if ($this->hasDisplayCallbacks()) {
370
                $value = $this->callDisplayCallbacks($this->original, $key);
371
                array_set($row, $this->name, $value);
372
            }
373
        }
374
375
        return $data;
376
    }
377
378
    /**
379
     * If current column is a defined column.
380
     *
381
     * @return bool
382
     */
383
    protected function isDefinedColumn()
384
    {
385
        return array_key_exists($this->name, static::$defined);
386
    }
387
388
    /**
389
     * Use a defined column.
390
     *
391
     * @throws \Exception
392
     */
393
    protected function useDefinedColumn()
394
    {
395
        // clear all display callbacks.
396
        $this->displayCallbacks = [];
397
398
        $class = static::$defined[$this->name];
399
400
        if ($class instanceof Closure) {
401
            $this->display($class);
402
403
            return;
404
        }
405
406
        if (!class_exists($class) || !is_subclass_of($class, AbstractDisplayer::class)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \Encore\Admin\Grid\Displ...bstractDisplayer::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
407
            throw new \Exception("Invalid column definition [$class]");
408
        }
409
410
        $grid = $this->grid;
411
        $column = $this;
412
413
        $this->display(function ($value) use ($grid, $column, $class) {
414
            $definition = new $class($value, $grid, $column, $this);
415
416
            return $definition->display();
417
        });
418
    }
419
420
    /**
421
     * Convert characters to HTML entities recursively.
422
     *
423
     * @param array|string $item
424
     *
425
     * @return mixed
426
     */
427
    protected function htmlEntityEncode($item)
428
    {
429
        if (is_array($item)) {
430
            array_walk_recursive($item, function (&$value) {
431
                $value = htmlentities($value);
432
            });
433
        } else {
434
            $item = htmlentities($item);
435
        }
436
437
        return $item;
438
    }
439
440
    /**
441
     * Create the column sorter.
442
     *
443
     * @return string|void
444
     */
445
    public function sorter()
446
    {
447
        if (!$this->sortable) {
448
            return;
449
        }
450
451
        $icon = 'fa-sort';
452
        $type = 'desc';
453
454
        if ($this->isSorted()) {
455
            $type = $this->sort['type'] == 'desc' ? 'asc' : 'desc';
456
            $icon .= "-amount-{$this->sort['type']}";
457
        }
458
459
        $query = app('request')->all();
460
        $query = array_merge($query, [$this->grid->model()->getSortName() => ['column' => $this->name, 'type' => $type]]);
461
462
        $url = URL::current().'?'.http_build_query($query);
463
464
        return "<a class=\"fa fa-fw $icon\" href=\"$url\"></a>";
465
    }
466
467
    /**
468
     * Determine if this column is currently sorted.
469
     *
470
     * @return bool
471
     */
472
    protected function isSorted()
473
    {
474
        $this->sort = app('request')->get($this->grid->model()->getSortName());
475
476
        if (empty($this->sort)) {
477
            return false;
478
        }
479
480
        return isset($this->sort['column']) && $this->sort['column'] == $this->name;
481
    }
482
483
    /**
484
     * Find a displayer to display column.
485
     *
486
     * @param string $abstract
487
     * @param array  $arguments
488
     *
489
     * @return Column
490
     */
491
    protected function resolveDisplayer($abstract, $arguments)
492
    {
493
        if (array_key_exists($abstract, static::$displayers)) {
494
            return $this->callBuiltinDisplayer(static::$displayers[$abstract], $arguments);
495
        }
496
497
        return $this->callSupportDisplayer($abstract, $arguments);
498
    }
499
500
    /**
501
     * Call Illuminate/Support displayer.
502
     *
503
     * @param string $abstract
504
     * @param array  $arguments
505
     *
506
     * @return Column
507
     */
508
    protected function callSupportDisplayer($abstract, $arguments)
509
    {
510
        return $this->display(function ($value) use ($abstract, $arguments) {
511
            if (is_array($value) || $value instanceof Arrayable) {
512
                return call_user_func_array([collect($value), $abstract], $arguments);
513
            }
514
515
            if (is_string($value)) {
516
                return call_user_func_array([Str::class, $abstract], array_merge([$value], $arguments));
517
            }
518
519
            return $value;
520
        });
521
    }
522
523
    /**
524
     * Call Builtin displayer.
525
     *
526
     * @param string $abstract
527
     * @param array  $arguments
528
     *
529
     * @return Column
530
     */
531
    protected function callBuiltinDisplayer($abstract, $arguments)
532
    {
533
        if ($abstract instanceof Closure) {
534
            return $this->display(function ($value) use ($abstract, $arguments) {
535
                return $abstract->call($this, ...array_merge([$value], $arguments));
536
            });
537
        }
538
539
        if (class_exists($abstract) && is_subclass_of($abstract, AbstractDisplayer::class)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \Encore\Admin\Grid\Displ...bstractDisplayer::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
540
            $grid = $this->grid;
541
            $column = $this;
542
543
            return $this->display(function ($value) use ($abstract, $grid, $column, $arguments) {
544
                $displayer = new $abstract($value, $grid, $column, $this);
545
546
                return call_user_func_array([$displayer, 'display'], $arguments);
547
            });
548
        }
549
550
        return $this;
551
    }
552 View Code Duplication
    public function setLabel($label)
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...
553
    {
554
        $trans_key = 'validation.attributes.' . $label;
555
        $trans_key_low = strtolower($trans_key);
556
        if (Lang::has($trans_key)) {
557
            $label = Lang::get($trans_key);
558
        }
559
        if (Lang::has($trans_key_low)) {
560
            $label = Lang::get($trans_key_low);
561
        }else {
562
            $label = ucfirst($label);
563
        }
564
        return $label;
565
    }
566
567
    /**
568
     * Passes through all unknown calls to builtin displayer or supported displayer.
569
     *
570
     * Allow fluent calls on the Column object.
571
     *
572
     * @param string $method
573
     * @param array  $arguments
574
     *
575
     * @return $this
576
     */
577
    public function __call($method, $arguments)
578
    {
579
        if ($this->isRelation() && !$this->relationColumn) {
580
            $this->name = "{$this->relation}.$method";
581
            $this->label = isset($arguments[0]) ? $arguments[0] : ucfirst($method);
582
            $this->label = $this->setLabel($this->label);
583
584
            $this->relationColumn = $method;
585
586
            return $this;
587
        }
588
589
        return $this->resolveDisplayer($method, $arguments);
590
    }
591
}
592