Passed
Push — develop ( 53ebdb...515625 )
by Andrew
16:45
created

ObjectParserAutocomplete   F

Complexity

Total Complexity 65

Size/Duplication

Total Lines 325
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 181
c 1
b 0
f 0
dl 0
loc 325
rs 3.2
wmc 65

7 Methods

Rating   Name   Duplication   Size   Complexity  
C getMethodCompletion() 0 66 14
A parseObject() 0 21 2
A getClassCompletion() 0 17 2
A getComponentCompletion() 0 12 5
A getBehaviorCompletion() 0 6 3
F getPropertyCompletion() 0 91 34
A getDocs() 0 16 5

How to fix   Complexity   

Complex Class

Complex classes like ObjectParserAutocomplete 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 ObjectParserAutocomplete, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Twigfield for Craft CMS
4
 *
5
 * Provides a twig editor field with Twig & Craft API autocomplete
6
 *
7
 * @link      https://nystudio107.com
0 ignored issues
show
Coding Style introduced by
The tag in position 1 should be the @copyright tag
Loading history...
8
 * @copyright Copyright (c) 2022 nystudio107
0 ignored issues
show
Coding Style introduced by
@copyright tag must contain a year and the name of the copyright holder
Loading history...
9
 */
0 ignored issues
show
Coding Style introduced by
PHP version not specified
Loading history...
Coding Style introduced by
Missing @category tag in file comment
Loading history...
Coding Style introduced by
Missing @package tag in file comment
Loading history...
Coding Style introduced by
Missing @author tag in file comment
Loading history...
Coding Style introduced by
Missing @license tag in file comment
Loading history...
10
11
namespace nystudio107\twigfield\base;
12
13
use craft\base\Element;
14
use nystudio107\twigfield\models\CompleteItem;
15
use nystudio107\twigfield\types\CompleteItemKind;
16
use phpDocumentor\Reflection\DocBlockFactory;
17
use ReflectionClass;
18
use ReflectionException;
19
use ReflectionMethod;
20
use ReflectionUnionType;
21
use yii\base\Behavior;
22
use yii\base\InvalidConfigException;
23
use yii\di\ServiceLocator;
24
25
/**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
26
 * @author    nystudio107
0 ignored issues
show
Coding Style introduced by
The tag in position 1 should be the @package tag
Loading history...
Coding Style introduced by
Content of the @author tag must be in the form "Display Name <[email protected]>"
Loading history...
Coding Style introduced by
Tag value for @author tag indented incorrectly; expected 2 spaces but found 4
Loading history...
27
 * @package   twigfield
0 ignored issues
show
Coding Style introduced by
Package name "twigfield" is not valid; consider "Twigfield" instead
Loading history...
Coding Style introduced by
Tag value for @package tag indented incorrectly; expected 1 spaces but found 3
Loading history...
28
 * @since     1.0.12
0 ignored issues
show
Coding Style introduced by
The tag in position 3 should be the @author tag
Loading history...
Coding Style introduced by
Tag value for @since tag indented incorrectly; expected 3 spaces but found 5
Loading history...
29
 */
0 ignored issues
show
Coding Style introduced by
Missing @category tag in class comment
Loading history...
Coding Style introduced by
Missing @license tag in class comment
Loading history...
Coding Style introduced by
Missing @link tag in class comment
Loading history...
30
abstract class ObjectParserAutocomplete extends Autocomplete implements ObjectParserInterface
31
{
32
    // Constants
33
    // =========================================================================
34
35
    const EXCLUDED_PROPERTY_NAMES = [
36
        'controller',
37
        'Controller',
38
        'CraftEdition',
39
        'CraftSolo',
40
        'CraftPro',
41
    ];
42
    const EXCLUDED_BEHAVIOR_NAMES = [
43
        'fieldHandles',
44
        'hasMethods',
45
        'owner',
46
    ];
47
    const EXCLUDED_PROPERTY_REGEXES = [
48
        '^_',
49
    ];
50
    const EXCLUDED_METHOD_REGEXES = [
51
        '^_',
52
    ];
53
    const RECURSION_DEPTH_LIMIT = 10;
54
55
    const CUSTOM_PROPERTY_SORT_PREFIX = '~';
56
    const PROPERTY_SORT_PREFIX = '~~';
57
    const METHOD_SORT_PREFIX = '~~~';
58
59
    // Public Methods
60
    // =========================================================================
61
62
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
Parameter $name should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $object should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $recursionDepth should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $path should have a doc-comment as per coding-style.
Loading history...
63
     * @inerhitdoc
64
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
65
    public function parseObject(string $name, $object, int $recursionDepth, string $path = ''): void
66
    {
67
        // Only recurse `RECURSION_DEPTH_LIMIT` deep
68
        if ($recursionDepth > self::RECURSION_DEPTH_LIMIT) {
69
            return;
70
        }
71
        $recursionDepth++;
72
        // Create the docblock factory
73
        $factory = DocBlockFactory::createInstance();
74
75
        $path = trim(implode('.', [$path, $name]), '.');
76
        // The class itself
77
        $this->getClassCompletion($object, $factory, $name, $path);
78
        // ServiceLocator Components
79
        $this->getComponentCompletion($object, $recursionDepth, $path);
80
        // Class properties
81
        $this->getPropertyCompletion($object, $factory, $recursionDepth, $path);
82
        // Class methods
83
        $this->getMethodCompletion($object, $factory, $path);
84
        // Behavior properties
85
        $this->getBehaviorCompletion($object, $factory, $recursionDepth, $path);
86
    }
87
88
    // Protected Methods
89
    // =========================================================================
90
91
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
92
     * @param $object
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
93
     * @param DocBlockFactory $factory
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
94
     * @param string $name
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 10 spaces after parameter type; 1 found
Loading history...
95
     * @param $path
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
96
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
97
    protected function getClassCompletion($object, DocBlockFactory $factory, string $name, $path): void
98
    {
99
        try {
100
            $reflectionClass = new ReflectionClass($object);
101
        } catch (ReflectionException $e) {
102
            return;
103
        }
104
        // Information on the class itself
105
        $className = $reflectionClass->getName();
106
        $docs = $this->getDocs($reflectionClass, $factory);
107
        CompleteItem::create()
108
            ->detail((string)$className)
109
            ->documentation((string)$docs)
110
            ->kind(CompleteItemKind::ClassKind)
111
            ->label((string)$name)
112
            ->insertText((string)$name)
113
            ->add($this, $path);
114
    }
115
116
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
117
     * @param $object
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
118
     * @param $recursionDepth
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
119
     * @param $path
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
120
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
121
    protected function getComponentCompletion($object, $recursionDepth, $path): void
122
    {
123
        if ($object instanceof ServiceLocator) {
124
            foreach ($object->getComponents() as $key => $value) {
125
                $componentObject = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $componentObject is dead and can be removed.
Loading history...
126
                try {
127
                    $componentObject = $object->get($key);
128
                } catch (InvalidConfigException $e) {
129
                    // That's okay
130
                }
131
                if ($componentObject) {
132
                    $this->parseObject($key, $componentObject, $recursionDepth, $path);
133
                }
134
            }
135
        }
136
    }
137
138
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
139
     * @param $object
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
140
     * @param DocBlockFactory $factory
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
141
     * @param $recursionDepth
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
142
     * @param string $path
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 10 spaces after parameter type; 1 found
Loading history...
143
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
144
    protected function getPropertyCompletion($object, DocBlockFactory $factory, $recursionDepth, string $path): void
145
    {
146
        try {
147
            $reflectionClass = new ReflectionClass($object);
148
        } catch (ReflectionException $e) {
149
            return;
150
        }
151
        $reflectionProperties = $reflectionClass->getProperties();
152
        $customField = false;
153
        if ($object instanceof Behavior) {
154
            $customField = true;
155
        }
156
        $sortPrefix = $customField ? self::CUSTOM_PROPERTY_SORT_PREFIX : self::PROPERTY_SORT_PREFIX;
157
        foreach ($reflectionProperties as $reflectionProperty) {
158
            $propertyName = $reflectionProperty->getName();
159
            // Exclude some properties
160
            $propertyAllowed = true;
161
            foreach (self::EXCLUDED_PROPERTY_REGEXES as $excludePattern) {
162
                $pattern = '`' . $excludePattern . '`i';
163
                if (preg_match($pattern, $propertyName) === 1) {
164
                    $propertyAllowed = false;
165
                }
166
            }
167
            if (in_array($propertyName, self::EXCLUDED_PROPERTY_NAMES, true)) {
168
                $propertyAllowed = false;
169
            }
170
            if ($customField && in_array($propertyName, self::EXCLUDED_BEHAVIOR_NAMES, true)) {
171
                $propertyAllowed = false;
172
            }
173
            // Process the property
174
            if ($propertyAllowed && $reflectionProperty->isPublic()) {
175
                $detail = "Property";
176
                $docblock = null;
177
                $docs = $reflectionProperty->getDocComment();
178
                if ($docs) {
179
                    $docblock = $factory->create($docs);
180
                    $docs = '';
181
                    $summary = $docblock->getSummary();
182
                    if (!empty($summary)) {
183
                        $docs = $summary;
184
                    }
185
                    $description = $docblock->getDescription()->render();
186
                    if (!empty($description)) {
187
                        $docs = $description;
188
                    }
189
                }
190
                // Figure out the type
191
                if ($docblock) {
192
                    $tag = $docblock->getTagsByName('var');
193
                    if ($tag && isset($tag[0])) {
194
                        $docs = $tag[0];
195
                    }
196
                }
197
                if (preg_match('/@var\s+([^\s]+)/', $docs, $matches)) {
198
                    list(, $type) = $matches;
199
                    $detail = $type;
200
                }
201
                if ($detail === "Property") {
202
                    if ((PHP_MAJOR_VERSION >= 7 && PHP_MINOR_VERSION >= 4) || (PHP_MAJOR_VERSION >= 8)) {
203
                        if ($reflectionProperty->hasType()) {
204
                            $reflectionType = $reflectionProperty->getType();
205
                            if ($reflectionType instanceof ReflectionNamedType) {
0 ignored issues
show
Bug introduced by
The type nystudio107\twigfield\base\ReflectionNamedType was not found. Did you mean ReflectionNamedType? If so, make sure to prefix the type with \.
Loading history...
206
                                $type = $reflectionType->getName();
0 ignored issues
show
Bug introduced by
The method getName() does not exist on ReflectionType. It seems like you code against a sub-type of ReflectionType such as ReflectionNamedType. ( Ignorable by Annotation )

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

206
                                /** @scrutinizer ignore-call */ 
207
                                $type = $reflectionType->getName();
Loading history...
207
                                $detail = $type;
208
                            }
209
                        }
210
                        if ((PHP_MAJOR_VERSION >= 8) && $reflectionProperty->hasDefaultValue()) {
211
                            $value = $reflectionProperty->getDefaultValue();
212
                            if (is_array($value)) {
213
                                $value = json_encode($value);
214
                            }
215
                            if (!empty($value)) {
216
                                $detail = (string)$value;
217
                            }
218
                        }
219
                    }
220
                }
221
                $thisPath = trim(implode('.', [$path, $propertyName]), '.');
222
                $label = $propertyName;
223
                CompleteItem::create()
224
                    ->detail((string)$detail)
225
                    ->documentation((string)$docs)
226
                    ->kind($customField ? CompleteItemKind::FieldKind : CompleteItemKind::PropertyKind)
227
                    ->label((string)$label)
228
                    ->insertText((string)$label)
229
                    ->sortText((string)$sortPrefix . (string)$label)
230
                    ->add($this, $thisPath);
231
                // Recurse through if this is an object
232
                if (isset($object->$propertyName) && is_object($object->$propertyName)) {
233
                    if (!$customField && !in_array($propertyName, self::EXCLUDED_PROPERTY_NAMES, true)) {
234
                        $this->parseObject($propertyName, $object->$propertyName, $recursionDepth, $path);
235
                    }
236
                }
237
            }
238
        }
239
    }
240
241
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
242
     * @param $object
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
243
     * @param DocBlockFactory $factory
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
244
     * @param string $path
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 10 spaces after parameter type; 1 found
Loading history...
245
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
246
    protected function getMethodCompletion($object, DocBlockFactory $factory, string $path): void
247
    {
248
        try {
249
            $reflectionClass = new ReflectionClass($object);
250
        } catch (ReflectionException $e) {
251
            return;
252
        }
253
        $reflectionMethods = $reflectionClass->getMethods();
254
        foreach ($reflectionMethods as $reflectionMethod) {
255
            $methodName = $reflectionMethod->getName();
256
            // Exclude some properties
257
            $methodAllowed = true;
258
            foreach (self::EXCLUDED_METHOD_REGEXES as $excludePattern) {
259
                $pattern = '`' . $excludePattern . '`i';
260
                if (preg_match($pattern, $methodName) === 1) {
261
                    $methodAllowed = false;
262
                }
263
            }
264
            // Process the method
265
            if ($methodAllowed && $reflectionMethod->isPublic()) {
266
                $docblock = null;
267
                $docs = $this->getDocs($reflectionMethod, $factory);
268
                $detail = $methodName . '(';
269
                $params = $reflectionMethod->getParameters();
270
                $paramList = [];
271
                foreach ($params as $param) {
272
                    if ($param->hasType()) {
273
                        $reflectionType = $param->getType();
274
                        if ($reflectionType instanceof ReflectionUnionType) {
275
                            $unionTypes = $reflectionType->getTypes();
276
                            $typeName = '';
277
                            foreach ($unionTypes as $unionType) {
278
                                $typeName .= '|' . $unionType->getName();
279
                            }
280
                            $typeName = trim($typeName, '|');
281
                            $paramList[] = $typeName . ': ' . '$' . $param->getName();
282
                        } else {
283
                            $paramList[] = $param->getType()->getName() . ': ' . '$' . $param->getName();
284
                        }
285
                    } else {
286
                        $paramList[] = '$' . $param->getName();
287
                    }
288
                }
289
                $detail .= implode(', ', $paramList) . ')';
290
                $thisPath = trim(implode('.', [$path, $methodName]), '.');
291
                $label = $methodName . '()';
292
                $docsPreamble = '';
293
                // Figure out the type
294
                if ($docblock) {
295
                    $tags = $docblock->getTagsByName('param');
296
                    if ($tags) {
297
                        $docsPreamble = "Parameters:\n\n";
298
                        foreach ($tags as $tag) {
299
                            $docsPreamble .= $tag . "\n";
300
                        }
301
                        $docsPreamble .= "\n";
302
                    }
303
                }
304
                CompleteItem::create()
305
                    ->detail((string)$detail)
306
                    ->documentation((string)$docsPreamble . (string)$docs)
307
                    ->kind(CompleteItemKind::MethodKind)
308
                    ->label((string)$label)
309
                    ->insertText((string)$label)
310
                    ->sortText(self::METHOD_SORT_PREFIX . (string)$label)
311
                    ->add($this, $thisPath);
312
            }
313
        }
314
    }
315
316
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
317
     * @param $object
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
318
     * @param DocBlockFactory $factory
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
319
     * @param $recursionDepth
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
320
     * @param string $path
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 10 spaces after parameter type; 1 found
Loading history...
321
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
322
    protected function getBehaviorCompletion($object, DocBlockFactory $factory, $recursionDepth, string $path): void
323
    {
324
        if ($object instanceof Element) {
325
            $behaviorClass = $object->getBehavior('customFields');
326
            if ($behaviorClass) {
327
                $this->getPropertyCompletion($behaviorClass, $factory, $recursionDepth, $path);
328
            }
329
        }
330
    }
331
332
    /**
333
     * Try to get the best documentation block we can
334
     *
335
     * @param ReflectionClass|ReflectionMethod $reflection
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
336
     * @param DocBlockFactory $factory
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 18 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
337
     * @return string
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
338
     */
339
    protected function getDocs($reflection, DocBlockFactory $factory): string
340
    {
341
        $docs = $reflection->getDocComment();
342
        if ($docs) {
343
            $docblock = $factory->create($docs);
344
            $summary = $docblock->getSummary();
345
            if (!empty($summary)) {
346
                $docs = $summary;
347
            }
348
            $description = $docblock->getDescription()->render();
349
            if (!empty($description)) {
350
                $docs = $description;
351
            }
352
        }
353
354
        return $docs ?: '';
355
    }
356
}
357