ColumnSource::readAndCacheGridAnnotations()   F
last analyzed

Complexity

Conditions 20
Paths 313

Size

Total Lines 94
Code Lines 61

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 61
c 1
b 0
f 0
dl 0
loc 94
rs 1.9208
cc 20
nc 313
nop 4

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 Dtc\GridBundle\Annotation\Action;
7
use Dtc\GridBundle\Annotation\Column;
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
use Dtc\GridBundle\Util\ColumnUtil;
15
use Exception;
16
use InvalidArgumentException;
17
18
class ColumnSource
19
{
20
    /** @var string|null */
21
    private $cacheDir;
22
23
    /** @var bool */
24
    private $debug = false;
25
26
    public function __construct($cacheDir, $debug)
27
    {
28
        $this->debug = $debug;
29
        $this->cacheDir = $cacheDir;
30
    }
31
32
    /**
33
     * @var array|null
34
     */
35
    private $cachedSort;
0 ignored issues
show
introduced by
The private property $cachedSort is not used, and could be removed.
Loading history...
36
37
    public function setDebug($flag)
38
    {
39
        $this->debug = $flag;
40
    }
41
42
    /**
43
     * @param string|null $cacheDir
44
     */
45
    public function setCacheDir($cacheDir)
46
    {
47
        $this->cacheDir = $cacheDir;
48
    }
49
50
    /**
51
     * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata|\Doctrine\ORM\Mapping\ClassMetadata $classMetadata
0 ignored issues
show
Bug introduced by
The type Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
Bug introduced by
The type Doctrine\ORM\Mapping\ClassMetadata 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...
52
     *
53
     * @return mixed|null
54
     */
55
    public static function getIdColumn($classMetadata)
56
    {
57
        $identifier = $classMetadata->getIdentifier();
58
59
        return isset($identifier[0]) ? $identifier[0] : null;
60
    }
61
62
    /**
63
     * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata|\Doctrine\ORM\Mapping\ClassMetadata $classMetadata
64
     * @param $cacheFilename
65
     *
66
     * @return array|null
67
     *
68
     * @throws \Exception
69
     */
70
    private function getCachedColumnInfo($cacheFilename, $classMetadata, Reader $reader = null)
71
    {
72
        $params = [$classMetadata, $cacheFilename];
73
        if ($reader) {
74
            $params[] = $reader;
75
        }
76
        if (call_user_func_array([$this, 'shouldIncludeColumnCache'], $params)) {
77
            $columnInfo = include $cacheFilename;
78
            if (!isset($columnInfo['columns'])) {
79
                throw new \Exception("Bad column cache, missing columns: {$cacheFilename}");
80
            }
81
            if (!isset($columnInfo['sort'])) {
82
                throw new \Exception("Bad column cache, missing sort: {$cacheFilename}");
83
            }
84
            if ($columnInfo['sort']) {
85
                self::validateSortInfo($columnInfo['sort'], $columnInfo['columns']);
86
            }
87
88
            return $columnInfo;
89
        }
90
91
        return null;
92
    }
93
94
    /**
95
     * @return ColumnSourceInfo|null
96
     *
97
     * @throws Exception
98
     */
99
    public function getColumnSourceInfo($objectManager, $objectName, $allowReflection, Reader $reader = null)
100
    {
101
        $metadataFactory = $objectManager->getMetadataFactory();
102
        $classMetadata = $metadataFactory->getMetadataFor($objectName);
103
        $reflectionClass = $classMetadata->getReflectionClass();
104
        $name = $reflectionClass->getName();
105
        $cacheFilename = ColumnUtil::createCacheFilename($this->cacheDir, $name);
106
107
        // Try to include them from the cached file if exists.
108
        $params = [$cacheFilename, $classMetadata];
109
        if ($reader) {
110
            $params[] = $reader;
111
        }
112
        $columnInfo = call_user_func_array([$this, 'getCachedColumnInfo'], $params);
113
114
        if (!$columnInfo && $reader) {
115
            $columnInfo = $this->readAndCacheGridAnnotations($cacheFilename, $reader, $classMetadata, $allowReflection);
116
        }
117
118
        if (!$columnInfo && $allowReflection) {
119
            $columns = self::getReflectionColumns($classMetadata);
120
            $columnInfo = ['columns' => $columns, 'sort' => []];
121
        }
122
123
        if (!$columnInfo) {
124
            return null;
125
        }
126
127
        $columnSourceInfo = new ColumnSourceInfo();
128
        $columnSourceInfo->columns = $columnInfo['columns'];
129
        $columnSourceInfo->sort = $columnInfo['sort'];
130
        $columnSourceInfo->idColumn = self::getIdColumn($classMetadata);
131
132
        return $columnSourceInfo;
133
    }
134
135
    /**
136
     * Cached annotation info from the file, if the mtime of the file has not changed (or if not in debug).
137
     *
138
     * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata|\Doctrine\ORM\Mapping\ClassMetadata $metadata
139
     *
140
     * @return bool
141
     *
142
     * @throws Exception
143
     */
144
    private function shouldIncludeColumnCache($metadata, $columnCacheFilename, Reader $reader = null)
145
    {
146
        // In production, or if we're sure there's no annotaitons, just include the cache.
147
        if (!$this->debug || !isset($reader)) {
148
            if (!is_file($columnCacheFilename) || !is_readable($columnCacheFilename)) {
149
                return false;
150
            }
151
152
            return true;
153
        }
154
155
        return self::checkTimestamps($metadata, $columnCacheFilename);
156
    }
157
158
    /**
159
     * Check timestamps of the file pointed to by the class metadata, and the columnCacheFilename and see if any
160
     * are newer (meaning we .
161
     *
162
     * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata|\Doctrine\ORM\Mapping\ClassMetadata $metadata
163
     * @param $columnCacheFilename
164
     *
165
     * @return bool
166
     */
167
    public static function checkTimestamps($metadata, $columnCacheFilename)
168
    {
169
        $reflectionClass = $metadata->getReflectionClass();
170
        $filename = $reflectionClass->getFileName();
171
        if ($filename && is_file($filename)) {
172
            $mtime = filemtime($filename);
173
            if (($currentfileMtime = filemtime(__FILE__)) > $mtime) {
174
                $mtime = $currentfileMtime;
175
            }
176
            $mtimeAnnotation = file_exists($columnCacheFilename) ? filemtime($columnCacheFilename) : null;
177
            if ($mtime && $mtimeAnnotation && $mtime <= $mtimeAnnotation) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $mtimeAnnotation of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
178
                return true;
179
            }
180
        }
181
182
        return false;
183
    }
184
185
    /**
186
     * Generates a list of property name and labels based on finding the GridColumn annotation.
187
     *
188
     * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata|\Doctrine\ORM\Mapping\ClassMetadata $metadata
189
     *
190
     * @throws \Exception
191
     *
192
     * @return array|null Hash of grid annotation results: ['columns' => array, 'sort' => string]
193
     */
194
    private function readAndCacheGridAnnotations($cacheFilename, Reader $reader, $metadata, $allowReflection)
195
    {
196
        $reflectionClass = $metadata->getReflectionClass();
197
        $properties = $reflectionClass->getProperties();
198
199
        /** @var Grid $gridAnnotation */
200
        $sort = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $sort is dead and can be removed.
Loading history...
201
        $sortMulti = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $sortMulti is dead and can be removed.
Loading history...
202
        if (!($gridAnnotation = $reader->getClassAnnotation($reflectionClass, 'Dtc\GridBundle\Annotation\Grid'))) {
203
            return null;
204
        }
205
206
        $actions = $gridAnnotation->actions;
207
        $sort = $gridAnnotation->sort;
208
        $sortMulti = $gridAnnotation->sortMulti;
209
210
        $gridColumns = [];
211
        foreach ($properties as $property) {
212
            /** @var Column $annotation */
213
            $annotation = $reader->getPropertyAnnotation($property, 'Dtc\GridBundle\Annotation\Column');
214
            if ($annotation) {
215
                $name = $property->getName();
216
                $label = $annotation->label ?: CamelCase::fromCamelCase($name);
217
                $gridColumns[$name] = ['class' => '\Dtc\GridBundle\Grid\Column\GridColumn', 'arguments' => [$name, $label]];
218
                $gridColumns[$name]['arguments'][] = isset($annotation->formatter) ? $annotation->formatter : null;
219
                if ($annotation->sortable) {
220
                    $gridColumns[$name]['arguments'][] = ['sortable' => true];
221
                } else {
222
                    $gridColumns[$name]['arguments'][] = [];
223
                }
224
                $gridColumns[$name]['arguments'][] = $annotation->searchable;
225
                $gridColumns[$name]['arguments'][] = $annotation->order;
226
            }
227
        }
228
229
        // Fall back to default column list if list is not specified
230
        if (!$gridColumns && $allowReflection) {
231
            $gridColumnList = self::getReflectionColumns($metadata);
232
            /** @var GridColumn $gridColumn */
233
            foreach ($gridColumnList as $field => $gridColumn) {
234
                $gridColumns[$field] = ['class' => '\Dtc\GridBundle\Grid\Column\GridColumn', 'arguments' => [$field, $gridColumn->getLabel(), null, ['sortable' => true], true, null]];
235
            }
236
        }
237
238
        if (isset($actions)) {
239
            $field = '\$-action';
240
            $actionArgs = [$field];
241
            $actionDefs = [];
242
            /* @var Action $action */
243
            foreach ($actions as $action) {
244
                $actionDef = ['label' => $action->label, 'route' => $action->route, 'button_class' => $action->buttonClass, 'onclick' => $action->onclick];
245
                if ($action instanceof ShowAction) {
246
                    $actionDef['action'] = 'show';
247
                } elseif ($action instanceof DeleteAction) {
248
                    $actionDef['action'] = 'delete';
249
                } else {
250
                    $actionDef['action'] = 'custom';
251
                }
252
                $actionDefs[] = $actionDef;
253
            }
254
            $actionArgs[] = $actionDefs;
255
256
            $gridColumns[$field] = ['class' => '\Dtc\GridBundle\Grid\Column\ActionGridColumn',
257
                'arguments' => $actionArgs, ];
258
        }
259
260
        $this->sortGridColumns($gridColumns);
261
262
        if ($sort) {
263
            if ($sortMulti) {
264
                throw new InvalidArgumentException($reflectionClass->getName().' - '."Can't have sort and sortMulti defined on Grid annotation");
265
            }
266
            $sortMulti = [$sort];
267
        }
268
269
        $sortList = [];
270
        if ($sortMulti) {
271
            try {
272
                foreach ($sortMulti as $sortDef) {
273
                    $sortInfo = self::extractSortInfo($sortDef);
274
                    self::validateSortInfo($sortInfo, $gridColumns);
275
                    if (isset($sortInfo['column'])) {
276
                        $sortList[$sortInfo['column']] = $sortInfo['direction'];
277
                    }
278
                }
279
            } catch (InvalidArgumentException $exception) {
280
                throw new InvalidArgumentException($reflectionClass->getName().' - '.$exception->getMessage(), $exception->getCode(), $exception);
281
            }
282
        }
283
        $columnInfo = ['columns' => $gridColumns, 'sort' => $sortList];
284
285
        ColumnUtil::populateCacheFile($cacheFilename, $columnInfo);
286
287
        return $this->getCachedColumnInfo($cacheFilename, $metadata, $reader);
288
    }
289
290
    /**
291
     * @throws InvalidArgumentException
292
     */
293
    private static function validateSortInfo(array $sortInfo, array $gridColumns)
294
    {
295
        if (isset($sortInfo['direction'])) {
296
            switch ($sortInfo['direction']) {
297
                case 'ASC':
298
                case 'DESC':
299
                    break;
300
                default:
301
                    throw new InvalidArgumentException("Grid's sort annotation direction '{$sortInfo['direction']}' is invalid");
302
            }
303
        }
304
305
        if (isset($sortInfo['column'])) {
306
            $column = $sortInfo['column'];
307
308
            if (!isset($sortInfo['direction'])) {
309
                throw new InvalidArgumentException("Grid's sort annotation column '$column' specified but a sort direction was not");
310
            }
311
            if (isset($gridColumns[$column])) {
312
                return;
313
            }
314
            throw new InvalidArgumentException("Grid's sort annotation column '$column' not in list of columns (".implode(', ', array_keys($gridColumns)).')');
315
        }
316
    }
317
318
    /**
319
     * @param Sort|null $sortAnnotation
320
     *
321
     * @return array
322
     */
323
    private static function extractSortInfo($sortAnnotation)
324
    {
325
        $sortInfo = ['direction' => null, 'column' => null];
326
        if ($sortAnnotation) {
327
            $direction = $sortAnnotation->direction;
328
            $sortInfo['direction'] = $direction;
329
            $column = $sortAnnotation->column;
330
            $sortInfo['column'] = $column;
331
        }
332
333
        return $sortInfo;
334
    }
335
336
    private function sortGridColumns(array &$columnDefs)
337
    {
338
        $unordered = [];
339
        $ordered = [];
340
        foreach ($columnDefs as $name => $columnDef) {
341
            $columnParts = $columnDef['arguments'];
342
            if (!isset($columnParts[5]) || null === $columnParts[5]) {
343
                $unordered[$name] = $columnDef;
344
                continue;
345
            }
346
            $ordered[$name] = $columnDef;
347
        }
348
349
        if (empty($ordered)) {
350
            return;
351
        }
352
353
        uasort($ordered, function ($columnDef1, $columnDef2) {
354
            $columnParts1 = $columnDef1['arguments'];
355
            $columnParts2 = $columnDef2['arguments'];
356
            $order1 = $columnParts1[5];
357
            $order2 = $columnParts2[5];
358
359
            return $order1 > $order2;
360
        });
361
362
        if ($unordered) {
363
            foreach ($unordered as $name => $columnDef) {
364
                $ordered[$name] = $columnDef;
365
            }
366
        }
367
        $columnDefs = $ordered;
368
    }
369
370
    /**
371
     * Generate Columns based on document's Metadata.
372
     *
373
     * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata|\Doctrine\ORM\Mapping\ClassMetadata $metadata
374
     */
375
    private static function getReflectionColumns($metadata)
376
    {
377
        $fields = $metadata->getFieldNames();
378
        $identifier = $metadata->getIdentifier();
379
        $identifier = isset($identifier[0]) ? $identifier[0] : null;
380
381
        if (!method_exists($metadata, 'getFieldMapping')) {
382
            return [];
383
        }
384
385
        $columns = [];
386
        foreach ($fields as $field) {
387
            $mapping = $metadata->getFieldMapping($field);
388
            if (isset($mapping['options']) && isset($mapping['options']['label'])) {
389
                $label = $mapping['options']['label'];
390
            } else {
391
                $label = CamelCase::fromCamelCase($field);
392
            }
393
394
            if ($identifier === $field) {
395
                if (isset($mapping['strategy']) && 'auto' == $mapping['strategy']) {
396
                    continue;
397
                }
398
            }
399
            $columns[$field] = new GridColumn($field, $label);
400
        }
401
402
        return $columns;
403
    }
404
}
405