Passed
Push — master ( 1316c5...63993c )
by Matthew
05:18
created

ColumnExtractionTrait::hasIdColumn()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 1
nc 2
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
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
    /**
48
     * @param Reader $reader
49
     */
50
    public function setAnnotationReader(Reader $reader)
51
    {
52
        $this->reader = $reader;
53
    }
54
55
    /**
56
     * @param string|null $cacheDir
57
     */
58
    public function setCacheDir($cacheDir)
59
    {
60
        $this->cacheDir = $cacheDir;
61
    }
62
63
    /**
64
     * @return ClassMetadataInfo|ClassMetadataInfo
65
     */
66
    abstract public function getClassMetadata();
67
68
    public function autoDiscoverColumns()
69
    {
70
        $annotationColumns = $this->getAnnotationColumns();
71
        if ($annotationColumns) {
72
            $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

72
            $this->/** @scrutinizer ignore-call */ 
73
                   setColumns($annotationColumns);
Loading history...
73
74
            return;
75
        }
76
77
        $this->setColumns($this->getReflectionColumns());
78
    }
79
80
    /**
81
     * @return array|null
82
     */
83
    public function getDefaultSort()
84
    {
85
        if (null !== $this->getAnnotationColumns()) {
86
            return $this->annotationSort;
87
        }
88
89
        return null;
90
    }
91
92
    /**
93
     * Populates the filename for the annotationCache.
94
     *
95
     * @return string
96
     *
97
     * @throws \Exception
98
     */
99
    protected function populateAnnotationCacheFilename()
100
    {
101
        if (isset($this->annotationCacheFilename)) {
102
            return $this->annotationCacheFilename;
103
        }
104
        $directory = $this->cacheDir.'/DtcGridBundle';
105
        $metadata = $this->getClassMetadata();
106
        $reflectionClass = $metadata->getReflectionClass();
107
        $name = $reflectionClass->getName();
108
        $namespace = $reflectionClass->getNamespaceName();
109
        $namespace = str_replace('\\', DIRECTORY_SEPARATOR, $namespace);
110
        $namespaceDir = $directory.DIRECTORY_SEPARATOR.$namespace;
111
112
        $umask = decoct(umask());
113
        $umask = str_pad($umask, 4, '0', STR_PAD_LEFT);
114
115
        // Is there a better way to do this?
116
        $permissions = '0777';
117
        $permissions[1] = intval($permissions[1]) - intval($umask[1]);
118
        $permissions[2] = intval($permissions[2]) - intval($umask[2]);
119
        $permissions[3] = intval($permissions[3]) - intval($umask[3]);
120
121
        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 $mode 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

121
        if (!is_dir($namespaceDir) && !mkdir($namespaceDir, /** @scrutinizer ignore-type */ octdec($permissions), true)) {
Loading history...
122
            throw new \Exception("Can't create: ".$namespaceDir);
123
        }
124
125
        $name = str_replace('\\', DIRECTORY_SEPARATOR, $name);
126
        $this->annotationCacheFilename = $directory.DIRECTORY_SEPARATOR.$name.'.php';
127
128
        return $this->annotationCacheFilename;
129
    }
130
131
    /**
132
     * Attempt to discover columns using the GridColumn annotation.
133
     *
134
     * @throws \Exception
135
     */
136
    protected function getAnnotationColumns()
137
    {
138
        if (!isset($this->reader)) {
139
            return null;
140
        }
141
142
        if (!isset($this->cacheDir)) {
143
            return null;
144
        }
145
146
        if (!isset($this->annotationCacheFilename)) {
147
            $this->populateAnnotationCacheFilename();
148
        }
149
150
        if (!$this->debug && null !== $this->annotationColumns) {
151
            return $this->annotationColumns ?: null;
152
        }
153
154
        // Check mtime of class
155
        if (is_file($this->annotationCacheFilename)) {
156
            $result = $this->tryIncludeAnnotationCache();
157
            if ($result) {
158
                return $this->annotationColumns;
159
            }
160
        }
161
162
        // cache annotation
163
        $this->populateAndCacheAnnotationColumns();
164
165
        return $this->annotationColumns ?: null;
166
    }
167
168
    /**
169
     * Cached annotation info from the file, if the mtime of the file has not changed (or if not in debug).
170
     *
171
     * @return bool
172
     */
173
    protected function tryIncludeAnnotationCache()
174
    {
175
        if (!$this->debug) {
176
            $this->includeAnnotationCache();
177
178
            return true;
179
        }
180
181
        $metadata = $this->getClassMetadata();
182
        $reflectionClass = $metadata->getReflectionClass();
183
        $filename = $reflectionClass->getFileName();
184
        if ($filename && is_file($filename)) {
185
            $mtime = filemtime($filename);
186
            if (($currentfileMtime = filemtime(__FILE__)) > $mtime) {
187
                $mtime = $currentfileMtime;
188
            }
189
            $mtimeAnnotation = filemtime($this->annotationCacheFilename);
190
            if ($mtime && $mtimeAnnotation && $mtime <= $mtimeAnnotation) {
191
                $this->includeAnnotationCache();
192
193
                return true;
194
            }
195
        }
196
197
        return false;
198
    }
199
200
    /**
201
     * Retrieves the cached annotations from the cache file.
202
     */
203
    protected function includeAnnotationCache()
204
    {
205
        $annotationInfo = include $this->annotationCacheFilename;
206
        $this->annotationColumns = $annotationInfo['columns'];
207
        $this->annotationSort = $annotationInfo['sort'];
208
        if ($this->annotationSort) {
209
            $this->validateSortInfo($this->annotationSort, $this->annotationColumns);
210
        }
211
    }
212
213
    /**
214
     * Caches the annotation columns result into a file.
215
     */
216
    protected function populateAndCacheAnnotationColumns()
217
    {
218
        $gridAnnotations = $this->readGridAnnotations();
219
        $annotationColumns = $gridAnnotations['columns'];
220
221
        $sort = $gridAnnotations['sort'];
222
        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...
223
            $output = "<?php\nreturn array('columns' => array(\n";
224
            foreach ($annotationColumns as $field => $info) {
225
                $class = $info['class'];
226
                $output .= "'$field' => new $class(";
227
                $first = true;
228
                foreach ($info['arguments'] as $argument) {
229
                    if ($first) {
230
                        $first = false;
231
                    } else {
232
                        $output .= ',';
233
                    }
234
                    $output .= var_export($argument, true);
235
                }
236
                $output .= '),';
237
            }
238
            $output .= "), 'sort' => array(";
239
            foreach ($sort as $key => $value) {
240
                $output .= "'$key'".' => ';
241
                if (null === $value) {
242
                    $output .= 'null,';
243
                } else {
244
                    $output .= "'$value',";
245
                }
246
            }
247
            $output .= "));\n";
248
        } else {
249
            $output = "<?php\nreturn false;\n";
250
        }
251
        file_put_contents($this->annotationCacheFilename, $output);
252
        $this->includeAnnotationCache();
253
    }
254
255
    /**
256
     * Generates a list of property name and labels based on finding the GridColumn annotation.
257
     *
258
     * @return array Hash of grid annotation results: ['columns' => array, 'sort' => string]
259
     */
260
    protected function readGridAnnotations()
261
    {
262
        $metadata = $this->getClassMetadata();
263
        $reflectionClass = $metadata->getReflectionClass();
264
        $properties = $reflectionClass->getProperties();
265
266
        /** @var Grid $gridAnnotation */
267
        $sort = null;
268
        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

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