ColumnExtractionTrait   F
last analyzed

Complexity

Total Complexity 81

Size/Duplication

Total Lines 443
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
wmc 81
eloc 214
c 1
b 0
f 1
dl 0
loc 443
rs 2

17 Methods

Rating   Name   Duplication   Size   Complexity  
A getDefaultSort() 0 7 2
B tryIncludeAnnotationCache() 0 25 8
A includeAnnotationCache() 0 7 2
A populateAnnotationCacheFilename() 0 30 4
A setCacheDir() 0 3 1
A autoDiscoverColumns() 0 10 2
B getAnnotationColumns() 0 30 10
B populateAndCacheAnnotationColumns() 0 37 7
A setAnnotationReader() 0 3 1
A setDebug() 0 3 1
C readGridAnnotations() 0 72 13
A extractSortInfo() 0 11 2
A hasIdColumn() 0 3 2
B validateSortInfo() 0 24 8
B sortGridColumns() 0 32 7
A getIdColumn() 0 12 3
B getReflectionColumns() 0 25 8

How to fix   Complexity   

Complex Class

Complex classes like ColumnExtractionTrait 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.

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

1
<?php
2
3
namespace Dtc\GridBundle\Grid\Source;
4
5
use Doctrine\Common\Annotations\Reader;
0 ignored issues
show
Bug introduced by
The type Doctrine\Common\Annotations\Reader was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo;
0 ignored issues
show
Bug introduced by
The type Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
7
use Dtc\GridBundle\Annotation\Action;
8
use Dtc\GridBundle\Annotation\DeleteAction;
9
use Dtc\GridBundle\Annotation\Grid;
10
use Dtc\GridBundle\Annotation\ShowAction;
11
use Dtc\GridBundle\Annotation\Sort;
12
use Dtc\GridBundle\Grid\Column\GridColumn;
13
use Dtc\GridBundle\Util\CamelCase;
14
15
/**
16
 * Trait ColumnExtractionTrait.
17
 *
18
 * @deprecated
19
 */
20
trait ColumnExtractionTrait
21
{
22
    /** @var Reader|null */
23
    protected $reader;
24
25
    /** @var string|null */
26
    protected $cacheDir;
27
28
    /** @var bool */
29
    protected $debug = false;
30
31
    /** @var string */
32
    protected $annotationCacheFilename;
33
34
    /** @var array|null */
35
    protected $annotationColumns;
36
37
    /**
38
     * @var array|null
39
     */
40
    protected $annotationSort;
41
42
    public function setDebug($flag)
43
    {
44
        $this->debug = $flag;
45
    }
46
47
    public function setAnnotationReader(Reader $reader)
48
    {
49
        $this->reader = $reader;
50
    }
51
52
    /**
53
     * @param string|null $cacheDir
54
     */
55
    public function setCacheDir($cacheDir)
56
    {
57
        $this->cacheDir = $cacheDir;
58
    }
59
60
    /**
61
     * @return ClassMetadataInfo|ClassMetadataInfo
62
     */
63
    abstract public function getClassMetadata();
64
65
    public function autoDiscoverColumns()
66
    {
67
        $annotationColumns = $this->getAnnotationColumns();
68
        if ($annotationColumns) {
69
            $this->setColumns($annotationColumns);
0 ignored issues
show
Bug introduced by
It seems like setColumns() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

69
            $this->/** @scrutinizer ignore-call */ 
70
                   setColumns($annotationColumns);
Loading history...
70
71
            return;
72
        }
73
74
        $this->setColumns($this->getReflectionColumns());
75
    }
76
77
    /**
78
     * @return array|null
79
     */
80
    public function getDefaultSort()
81
    {
82
        if (null !== $this->getAnnotationColumns()) {
83
            return $this->annotationSort;
84
        }
85
86
        return null;
87
    }
88
89
    /**
90
     * Populates the filename for the annotationCache.
91
     *
92
     * @return string
93
     *
94
     * @throws \Exception
95
     */
96
    protected function populateAnnotationCacheFilename()
97
    {
98
        if (isset($this->annotationCacheFilename)) {
99
            return $this->annotationCacheFilename;
100
        }
101
        $directory = $this->cacheDir.'/DtcGridBundle';
102
        $metadata = $this->getClassMetadata();
103
        $reflectionClass = $metadata->getReflectionClass();
104
        $name = $reflectionClass->getName();
105
        $namespace = $reflectionClass->getNamespaceName();
106
        $namespace = str_replace('\\', DIRECTORY_SEPARATOR, $namespace);
107
        $namespaceDir = $directory.DIRECTORY_SEPARATOR.$namespace;
108
109
        $umask = decoct(umask());
110
        $umask = str_pad($umask, 4, '0', STR_PAD_LEFT);
111
112
        // Is there a better way to do this?
113
        $permissions = '0777';
114
        $permissions[1] = intval($permissions[1]) - intval($umask[1]);
115
        $permissions[2] = intval($permissions[2]) - intval($umask[2]);
116
        $permissions[3] = intval($permissions[3]) - intval($umask[3]);
117
118
        if (!is_dir($namespaceDir) && !mkdir($namespaceDir, octdec($permissions), true)) {
0 ignored issues
show
Bug introduced by
It seems like octdec($permissions) can also be of type double; however, parameter $permissions of mkdir() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

118
        if (!is_dir($namespaceDir) && !mkdir($namespaceDir, /** @scrutinizer ignore-type */ octdec($permissions), true)) {
Loading history...
119
            throw new \Exception("Can't create: ".$namespaceDir);
120
        }
121
122
        $name = str_replace('\\', DIRECTORY_SEPARATOR, $name);
123
        $this->annotationCacheFilename = $directory.DIRECTORY_SEPARATOR.$name.'.php';
124
125
        return $this->annotationCacheFilename;
126
    }
127
128
    /**
129
     * Attempt to discover columns using the GridColumn annotation.
130
     *
131
     * @throws \Exception
132
     */
133
    protected function getAnnotationColumns()
134
    {
135
        if (!isset($this->reader)) {
136
            return null;
137
        }
138
139
        if (!isset($this->cacheDir)) {
140
            return null;
141
        }
142
143
        if (!isset($this->annotationCacheFilename)) {
144
            $this->populateAnnotationCacheFilename();
145
        }
146
147
        if (!$this->debug && null !== $this->annotationColumns) {
148
            return $this->annotationColumns ?: null;
149
        }
150
151
        // Check mtime of class
152
        if (is_file($this->annotationCacheFilename)) {
153
            $result = $this->tryIncludeAnnotationCache();
154
            if ($result) {
155
                return $this->annotationColumns;
156
            }
157
        }
158
159
        // cache annotation
160
        $this->populateAndCacheAnnotationColumns();
161
162
        return $this->annotationColumns ?: null;
163
    }
164
165
    /**
166
     * Cached annotation info from the file, if the mtime of the file has not changed (or if not in debug).
167
     *
168
     * @return bool
169
     */
170
    protected function tryIncludeAnnotationCache()
171
    {
172
        if (!$this->debug) {
173
            $this->includeAnnotationCache();
174
175
            return true;
176
        }
177
178
        $metadata = $this->getClassMetadata();
179
        $reflectionClass = $metadata->getReflectionClass();
180
        $filename = $reflectionClass->getFileName();
181
        if ($filename && is_file($filename)) {
182
            $mtime = filemtime($filename);
183
            if (($currentfileMtime = filemtime(__FILE__)) > $mtime) {
184
                $mtime = $currentfileMtime;
185
            }
186
            $mtimeAnnotation = filemtime($this->annotationCacheFilename);
187
            if ($mtime && $mtimeAnnotation && $mtime <= $mtimeAnnotation) {
188
                $this->includeAnnotationCache();
189
190
                return true;
191
            }
192
        }
193
194
        return false;
195
    }
196
197
    /**
198
     * Retrieves the cached annotations from the cache file.
199
     */
200
    protected function includeAnnotationCache()
201
    {
202
        $annotationInfo = include $this->annotationCacheFilename;
203
        $this->annotationColumns = $annotationInfo['columns'];
204
        $this->annotationSort = $annotationInfo['sort'];
205
        if ($this->annotationSort) {
206
            $this->validateSortInfo($this->annotationSort, $this->annotationColumns);
207
        }
208
    }
209
210
    /**
211
     * Caches the annotation columns result into a file.
212
     */
213
    protected function populateAndCacheAnnotationColumns()
214
    {
215
        $gridAnnotations = $this->readGridAnnotations();
216
        $annotationColumns = $gridAnnotations['columns'];
217
218
        $sort = $gridAnnotations['sort'];
219
        if ($annotationColumns) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $annotationColumns of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
220
            $output = "<?php\nreturn array('columns' => array(\n";
221
            foreach ($annotationColumns as $field => $info) {
222
                $class = $info['class'];
223
                $output .= "'$field' => new $class(";
224
                $first = true;
225
                foreach ($info['arguments'] as $argument) {
226
                    if ($first) {
227
                        $first = false;
228
                    } else {
229
                        $output .= ',';
230
                    }
231
                    $output .= var_export($argument, true);
232
                }
233
                $output .= '),';
234
            }
235
            $output .= "), 'sort' => array(";
236
            foreach ($sort as $key => $value) {
237
                $output .= "'$key'".' => ';
238
                if (null === $value) {
239
                    $output .= 'null,';
240
                } else {
241
                    $output .= "'$value',";
242
                }
243
            }
244
            $output .= "));\n";
245
        } else {
246
            $output = "<?php\nreturn false;\n";
247
        }
248
        file_put_contents($this->annotationCacheFilename, $output);
249
        $this->includeAnnotationCache();
250
    }
251
252
    /**
253
     * Generates a list of property name and labels based on finding the GridColumn annotation.
254
     *
255
     * @return array Hash of grid annotation results: ['columns' => array, 'sort' => string]
256
     */
257
    protected function readGridAnnotations()
258
    {
259
        $metadata = $this->getClassMetadata();
260
        $reflectionClass = $metadata->getReflectionClass();
261
        $properties = $reflectionClass->getProperties();
262
263
        /** @var Grid $gridAnnotation */
264
        $sort = null;
265
        if ($gridAnnotation = $this->reader->getClassAnnotation($reflectionClass, 'Dtc\GridBundle\Annotation\Grid')) {
0 ignored issues
show
Bug introduced by
The method getClassAnnotation() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

265
        if ($gridAnnotation = $this->reader->/** @scrutinizer ignore-call */ getClassAnnotation($reflectionClass, 'Dtc\GridBundle\Annotation\Grid')) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
266
            $actions = $gridAnnotation->actions;
267
            $sort = $gridAnnotation->sort;
268
        }
269
270
        $gridColumns = [];
271
        foreach ($properties as $property) {
272
            /** @var \Dtc\GridBundle\Annotation\Column $annotation */
273
            $annotation = $this->reader->getPropertyAnnotation($property, 'Dtc\GridBundle\Annotation\Column');
274
            if ($annotation) {
275
                $name = $property->getName();
276
                $label = $annotation->label ?: CamelCase::fromCamelCase($name);
277
                $gridColumns[$name] = ['class' => '\Dtc\GridBundle\Grid\Column\GridColumn', 'arguments' => [$name, $label]];
278
                $gridColumns[$name]['arguments'][] = null;
279
                if ($annotation->sortable) {
280
                    $gridColumns[$name]['arguments'][] = ['sortable' => true];
281
                } else {
282
                    $gridColumns[$name]['arguments'][] = [];
283
                }
284
                $gridColumns[$name]['arguments'][] = $annotation->searchable;
285
                $gridColumns[$name]['arguments'][] = $annotation->order;
286
            }
287
        }
288
289
        // Fall back to default column list if list is not specified
290
        if (!$gridColumns) {
291
            $gridColumnList = $this->getReflectionColumns();
292
            /** @var GridColumn $gridColumn */
293
            foreach ($gridColumnList as $field => $gridColumn) {
294
                $gridColumns[$field] = ['class' => '\Dtc\GridBundle\Grid\Column\GridColumn', 'arguments' => [$field, $gridColumn->getLabel(), null, ['sortable' => true], true, null]];
295
            }
296
        }
297
298
        if (isset($actions)) {
299
            $field = '\$-action';
300
            $actionArgs = [$field];
301
            $actionDefs = [];
302
            /* @var Action $action */
303
            foreach ($actions as $action) {
304
                $actionDef = ['label' => $action->label, 'route' => $action->route, 'button_class' => $action->buttonClass, 'onclick' => $action->onclick];
305
                if ($action instanceof ShowAction) {
306
                    $actionDef['action'] = 'show';
307
                } elseif ($action instanceof DeleteAction) {
308
                    $actionDef['action'] = 'delete';
309
                } else {
310
                    $actionDef['action'] = 'custom';
311
                }
312
                $actionDefs[] = $actionDef;
313
            }
314
            $actionArgs[] = $actionDefs;
315
316
            $gridColumns[$field] = ['class' => '\Dtc\GridBundle\Grid\Column\ActionGridColumn',
317
                'arguments' => $actionArgs, ];
318
        }
319
320
        $this->sortGridColumns($gridColumns);
321
        try {
322
            $sortInfo = $this->extractSortInfo($sort);
323
            $this->validateSortInfo($sortInfo, $gridColumns);
324
        } catch (\InvalidArgumentException $exception) {
325
            throw new \InvalidArgumentException($reflectionClass->getName().' - '.$exception->getMessage(), $exception->getCode(), $exception);
326
        }
327
328
        return ['columns' => $gridColumns, 'sort' => $sortInfo];
329
    }
330
331
    /**
332
     * @throws \InvalidArgumentException
333
     */
334
    protected function validateSortInfo(array $sortInfo, array $gridColumns)
335
    {
336
        if ($sortInfo['direction']) {
337
            switch ($sortInfo['direction']) {
338
                case 'ASC':
339
                case 'DESC':
340
                    break;
341
                default:
342
                    throw new \InvalidArgumentException("Grid's sort annotation direction '{$sortInfo['direction']}' is invalid");
343
            }
344
        }
345
346
        if (isset($sortInfo['column'])) {
347
            $column = $sortInfo['column'];
348
349
            if (!isset($sortInfo['direction'])) {
350
                throw new \InvalidArgumentException("Grid's sort annotation column '$column' specified but a sort direction was not");
351
            }
352
            foreach (array_keys($gridColumns) as $name) {
353
                if ($name === $column) {
354
                    return;
355
                }
356
            }
357
            throw new \InvalidArgumentException("Grid's sort annotation column '$column' not in list of columns (".implode(', ', array_keys($gridColumns)).')');
358
        }
359
    }
360
361
    /**
362
     * @param Sort|null $sortAnnotation
363
     *
364
     * @return array
365
     */
366
    protected function extractSortInfo($sortAnnotation)
367
    {
368
        $sortInfo = ['direction' => null, 'column' => null];
369
        if ($sortAnnotation) {
370
            $direction = $sortAnnotation->direction;
371
            $sortInfo['direction'] = $direction;
372
            $column = $sortAnnotation->column;
373
            $sortInfo['column'] = $column;
374
        }
375
376
        return $sortInfo;
377
    }
378
379
    protected function sortGridColumns(array &$columnDefs)
380
    {
381
        $unordered = [];
382
        $ordered = [];
383
        foreach ($columnDefs as $name => $columnDef) {
384
            $columnParts = $columnDef['arguments'];
385
            if (!isset($columnParts[5]) || null === $columnParts[5]) {
386
                $unordered[$name] = $columnDef;
387
                continue;
388
            }
389
            $ordered[$name] = $columnDef;
390
        }
391
392
        if (empty($ordered)) {
393
            return;
394
        }
395
396
        uasort($ordered, function ($columnDef1, $columnDef2) {
397
            $columnParts1 = $columnDef1['arguments'];
398
            $columnParts2 = $columnDef2['arguments'];
399
            $order1 = $columnParts1[5];
400
            $order2 = $columnParts2[5];
401
402
            return $order1 > $order2;
403
        });
404
405
        if ($unordered) {
406
            foreach ($unordered as $name => $columnDef) {
407
                $ordered[$name] = $columnDef;
408
            }
409
        }
410
        $columnDefs = $ordered;
411
    }
412
413
    /**
414
     * Generate Columns based on document's Metadata.
415
     */
416
    protected function getReflectionColumns()
417
    {
418
        $metadata = $this->getClassMetadata();
419
        $fields = $metadata->getFieldNames();
420
        $identifier = $metadata->getIdentifier();
421
        $identifier = isset($identifier[0]) ? $identifier[0] : null;
422
423
        $columns = [];
424
        foreach ($fields as $field) {
425
            $mapping = $metadata->getFieldMapping($field);
426
            if (isset($mapping['options']) && isset($mapping['options']['label'])) {
427
                $label = $mapping['options']['label'];
428
            } else {
429
                $label = CamelCase::fromCamelCase($field);
430
            }
431
432
            if ($identifier === $field) {
433
                if (isset($mapping['strategy']) && 'auto' == $mapping['strategy']) {
434
                    continue;
435
                }
436
            }
437
            $columns[$field] = new GridColumn($field, $label);
438
        }
439
440
        return $columns;
441
    }
442
443
    /**
444
     * @return string|null
445
     */
446
    protected function getIdColumn()
447
    {
448
        static $identifier = false;
449
        if (false !== $identifier) {
450
            return $identifier;
451
        }
452
453
        $metadata = $this->getClassMetadata();
454
        $identifier = $metadata->getIdentifier();
455
        $identifier = isset($identifier[0]) ? $identifier[0] : null;
456
457
        return $identifier;
458
    }
459
460
    public function hasIdColumn()
461
    {
462
        return $this->getIdColumn() ? true : false;
463
    }
464
}
465