Completed
Push — master ( 209945...41a4bc )
by Matthew
07:41 queued 03:34
created

ColumnExtractionTrait::autoDiscoverColumns()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 0
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace Dtc\GridBundle\Grid\Source;
4
5
use Doctrine\Common\Annotations\Reader;
6
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo;
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\Grid\Column\GridColumn;
12
use Dtc\GridBundle\Util\CamelCaseTrait;
13
14
trait ColumnExtractionTrait
15
{
16
    use CamelCaseTrait;
17
18
    /** @var Reader|null */
19
    protected $reader;
20
21
    /** @var string|null */
22
    protected $cacheDir;
23
24
    /** @var bool */
25
    protected $debug = false;
26
27
    /** @var string */
28
    protected $annotationCacheFilename;
29
30
    /** @var array|null */
31
    protected $annotationColumns;
32
33
    public function setDebug($flag)
34
    {
35
        $this->debug = $flag;
36
    }
37
38
    /**
39
     * @param Reader $reader
40
     */
41
    public function setAnnotationReader(Reader $reader)
42
    {
43
        $this->reader = $reader;
44
    }
45
46
    /**
47
     * @param string|null $cacheDir
48
     */
49
    public function setCacheDir($cacheDir)
50
    {
51
        $this->cacheDir = $cacheDir;
52
    }
53
54
    /**
55
     * @return ClassMetadataInfo|ClassMetadataInfo
56
     */
57
    abstract public function getClassMetadata();
58
59
    public function autoDiscoverColumns()
60
    {
61
        $annotationColumns = $this->getAnnotationColumns();
62
        if ($annotationColumns) {
63
            $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

63
            $this->/** @scrutinizer ignore-call */ setColumns($annotationColumns);
Loading history...
64
65
            return;
66
        }
67
68
        $this->setColumns($this->getReflectionColumns());
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

68
        $this->/** @scrutinizer ignore-call */ setColumns($this->getReflectionColumns());
Loading history...
69
    }
70
71
    /**
72
     * Populates the filename for the annotationCache.
73
     *
74
     * @return string
75
     *
76
     * @throws \Exception
77
     */
78
    protected function populateAnnotationCacheFilename()
79
    {
80
        if (isset($this->annotationCacheFilename)) {
81
            return $this->annotationCacheFilename;
82
        }
83
        $directory = $this->cacheDir.'/DtcGridBundle';
84
        $metadata = $this->getClassMetadata();
85
        $reflectionClass = $metadata->getReflectionClass();
86
        $name = $reflectionClass->getName();
87
        $namespace = $reflectionClass->getNamespaceName();
88
        $namespace = str_replace('\\', DIRECTORY_SEPARATOR, $namespace);
89
        $namespaceDir = $directory.DIRECTORY_SEPARATOR.$namespace;
90
91
        $umask = decoct(umask());
92
        $umask = str_pad($umask, 4, '0', STR_PAD_LEFT);
93
94
        // Is there a better way to do this?
95
        $permissions = '0777';
96
        $permissions[1] = intval($permissions[1]) - intval($umask[1]);
97
        $permissions[2] = intval($permissions[2]) - intval($umask[2]);
98
        $permissions[3] = intval($permissions[3]) - intval($umask[3]);
99
100
        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

100
        if (!is_dir($namespaceDir) && !mkdir($namespaceDir, /** @scrutinizer ignore-type */ octdec($permissions), true)) {
Loading history...
101
            throw new \Exception("Can't create: ".$namespaceDir);
102
        }
103
104
        $name = str_replace('\\', DIRECTORY_SEPARATOR, $name);
105
        $this->annotationCacheFilename = $directory.DIRECTORY_SEPARATOR.$name.'.php';
106
107
        return $this->annotationCacheFilename;
108
    }
109
110
    /**
111
     * Attempt to discover columns using the GridColumn annotation.
112
     *
113
     * @throws \Exception
114
     */
115
    protected function getAnnotationColumns()
116
    {
117
        if (!isset($this->reader)) {
118
            return null;
119
        }
120
121
        if (!isset($this->cacheDir)) {
122
            return null;
123
        }
124
125
        if (!isset($this->annotationCacheFilename)) {
126
            $this->populateAnnotationCacheFilename();
127
        }
128
129
        if (!$this->debug && null !== $this->annotationColumns) {
130
            return $this->annotationColumns ?: null;
131
        }
132
133
        // Check mtime of class
134
        if (is_file($this->annotationCacheFilename)) {
135
            $result = $this->getCachedAnnotationColumns();
136
            if (null !== $result) {
137
                return $result;
138
            }
139
        }
140
141
        // cache annotation
142
        $this->populateAndCacheAnnotationColumns();
143
144
        return $this->annotationColumns ?: null;
145
    }
146
147
    /**
148
     * Cached annotation columns from the file, if the mtime of the file has not changed (or if not in debug).
149
     *
150
     * @return mixed|null
151
     */
152
    protected function getCachedAnnotationColumns()
153
    {
154
        if (!$this->debug) {
155
            return $this->annotationColumns = include $this->annotationCacheFilename;
156
        }
157
158
        $metadata = $this->getClassMetadata();
159
        $reflectionClass = $metadata->getReflectionClass();
160
        $filename = $reflectionClass->getFileName();
161
        if ($filename && is_file($filename)) {
162
            $mtime = filemtime($filename);
163
            if (($currentfileMtime = filemtime(__FILE__)) > $mtime) {
164
                $mtime = $currentfileMtime;
165
            }
166
            $mtimeAnnotation = filemtime($this->annotationCacheFilename);
167
            if ($mtime && $mtimeAnnotation && $mtime <= $mtimeAnnotation) {
168
                return $this->annotationColumns = include $this->annotationCacheFilename;
169
            }
170
        }
171
172
        return null;
173
    }
174
175
    /**
176
     * Caches the annotation columns result into a file.
177
     */
178
    protected function populateAndCacheAnnotationColumns()
179
    {
180
        $annotationColumns = $this->generateAnnotationColumns();
181
        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...
182
            $output = "<?php\nreturn array(\n";
183
            foreach ($annotationColumns as $field => $info) {
184
                $class = $info['class'];
185
                $output .= "'$field' => new $class(";
186
                $first = true;
187
                foreach ($info['arguments'] as $argument) {
188
                    if ($first) {
189
                        $first = false;
190
                    } else {
191
                        $output .= ',';
192
                    }
193
                    $output .= var_export($argument, true);
194
                }
195
                $output .= '),';
196
            }
197
            $output .= ");\n";
198
        } else {
199
            $output = "<?php\nreturn false;\n";
200
        }
201
        file_put_contents($this->annotationCacheFilename, $output);
202
        $this->annotationColumns = include $this->annotationCacheFilename;
203
    }
204
205
    /**
206
     * Generates a list of proeprty name and labels based on finding the GridColumn annotation.
207
     *
208
     * @return array
209
     */
210
    protected function generateAnnotationColumns()
211
    {
212
        $metadata = $this->getClassMetadata();
213
        $reflectionClass = $metadata->getReflectionClass();
214
        $properties = $reflectionClass->getProperties();
215
216
        /** @var Grid $gridAnnotation */
217
        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

217
        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...
218
            $actions = $gridAnnotation->actions;
219
        }
220
221
        $gridColumns = [];
222
        foreach ($properties as $property) {
223
            /** @var \Dtc\GridBundle\Annotation\Column $annotation */
224
            $annotation = $this->reader->getPropertyAnnotation($property, 'Dtc\GridBundle\Annotation\Column');
225
            if ($annotation) {
226
                $name = $property->getName();
227
                $label = $annotation->label ?: $this->fromCamelCase($name);
228
                $gridColumns[$name] = ['class' => '\Dtc\GridBundle\Grid\Column\GridColumn', 'arguments' => [$name, $label]];
229
                $gridColumns[$name]['arguments'][] = null;
230
                if ($annotation->sortable) {
231
                    $gridColumns[$name]['arguments'][] = ['sortable' => true];
232
                } else {
233
                    $gridColumns[$name]['arguments'][] = [];
234
                }
235
                $gridColumns[$name]['arguments'][] = $annotation->searchable;
236
            }
237
        }
238
239
        /* @var Action $action */
240
        if (isset($actions)) {
241
            $field = '\$-action';
242
            $actionArgs = [$field];
243
            $actionDefs = [];
244
            foreach ($actions as $action) {
245
                $actionDef = ['label' => $action->label, 'route' => $action->route];
246
                if ($action instanceof ShowAction) {
247
                    $actionDef['action'] = 'show';
248
                }
249
                if ($action instanceof DeleteAction) {
250
                    $actionDef['action'] = 'delete';
251
                }
252
                $actionDefs[] = $actionDef;
253
            }
254
            $actionArgs[] = $actionDefs;
255
256
            $gridColumns[$field] = ['class' => '\Dtc\GridBundle\Grid\Column\ActionGridColumn',
257
                'arguments' => $actionArgs, ];
258
        }
259
260
        return $gridColumns;
261
    }
262
263
    /**
264
     * Generate Columns based on document's Metadata.
265
     */
266
    protected function getReflectionColumns()
267
    {
268
        $metadata = $this->getClassMetadata();
269
        $fields = $metadata->getFieldNames();
270
        $identifier = $metadata->getIdentifier();
271
        $identifier = isset($identifier[0]) ? $identifier[0] : null;
272
273
        $columns = array();
274
        foreach ($fields as $field) {
275
            $mapping = $metadata->getFieldMapping($field);
276
            if (isset($mapping['options']) && isset($mapping['options']['label'])) {
277
                $label = $mapping['options']['label'];
278
            } else {
279
                $label = $this->fromCamelCase($field);
280
            }
281
282
            if ($identifier === $field) {
283
                if (isset($mapping['strategy']) && 'auto' == $mapping['strategy']) {
284
                    continue;
285
                }
286
            }
287
            $columns[$field] = new GridColumn($field, $label);
288
        }
289
290
        return $columns;
291
    }
292
293
    /**
294
     * @return string|null
295
     */
296
    protected function getIdColumn()
297
    {
298
        static $identifier = false;
299
        if (false !== $identifier) {
300
            return $identifier;
301
        }
302
303
        $metadata = $this->getClassMetadata();
304
        $identifier = $metadata->getIdentifier();
305
        $identifier = isset($identifier[0]) ? $identifier[0] : null;
306
307
        return $identifier;
308
    }
309
310
    public function hasIdColumn()
311
    {
312
        return $this->getIdColumn() ? true : false;
313
    }
314
}
315