Issues (1181)

src/base/ObjectParserAutocomplete.php (73 issues)

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
The tag in position 1 should be the @copyright tag
Loading history...
8
 * @copyright Copyright (c) 2022 nystudio107
0 ignored issues
show
@copyright tag must contain a year and the name of the copyright holder
Loading history...
9
 */
0 ignored issues
show
PHP version not specified
Loading history...
Missing @category tag in file comment
Loading history...
Missing @package tag in file comment
Loading history...
Missing @author tag in file comment
Loading history...
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
Missing short description in doc comment
Loading history...
26
 * @author    nystudio107
0 ignored issues
show
The tag in position 1 should be the @package tag
Loading history...
Content of the @author tag must be in the form "Display Name <[email protected]>"
Loading history...
Tag value for @author tag indented incorrectly; expected 2 spaces but found 4
Loading history...
27
 * @package   twigfield
0 ignored issues
show
Package name "twigfield" is not valid; consider "Twigfield" instead
Loading history...
Tag value for @package tag indented incorrectly; expected 1 spaces but found 3
Loading history...
28
 * @since     1.0.12
0 ignored issues
show
The tag in position 3 should be the @author tag
Loading history...
Tag value for @since tag indented incorrectly; expected 3 spaces but found 5
Loading history...
29
 */
0 ignored issues
show
Missing @category tag in class comment
Loading history...
Missing @license tag in class comment
Loading history...
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
    // Public Properties
56
    // =========================================================================
57
58
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
59
     * @var bool If the class itself should be parsed for complete items
60
     */
61
    public $parseClass = true;
62
63
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
64
     * @var bool If any ServiceLocator components should be parsed for complete items
65
     */
66
    public $parseComponents = true;
67
68
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
69
     * @var bool If the class properties should be parsed for complete items
70
     */
71
    public $parseProperties = true;
72
73
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
74
     * @var bool If the class methods should be parsed for complete items
75
     */
76
    public $parseMethods = true;
77
78
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
79
     * @var bool If the class behaviors should be parsed for complete items
80
     */
81
    public $parseBehaviors = true;
82
83
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
84
     * @var string Prefix for custom (behavior) properties, for the complete items sort
85
     */
86
    public $customPropertySortPrefix = '~';
87
88
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
89
     * @var string Prefix for properties, for the complete items sort
90
     */
91
    public $propertySortPrefix = '~~';
92
93
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
94
     * @var string Prefix for methods, for the complete items sort
95
     */
96
    public $methodSortPrefix = '~~~';
97
98
    // Public Methods
99
    // =========================================================================
100
101
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
Parameter $name should have a doc-comment as per coding-style.
Loading history...
Parameter $object should have a doc-comment as per coding-style.
Loading history...
Parameter $recursionDepth should have a doc-comment as per coding-style.
Loading history...
Parameter $path should have a doc-comment as per coding-style.
Loading history...
102
     * @inerhitdoc
103
     */
0 ignored issues
show
Missing @return tag in function comment
Loading history...
104
    public function parseObject(string $name, $object, int $recursionDepth, string $path = ''): void
105
    {
106
        // Only recurse `RECURSION_DEPTH_LIMIT` deep
107
        if ($recursionDepth > self::RECURSION_DEPTH_LIMIT) {
108
            return;
109
        }
110
        $recursionDepth++;
111
        // Create the docblock factory
112
        $factory = DocBlockFactory::createInstance();
113
114
        $path = trim(implode('.', [$path, $name]), '.');
115
        // The class itself
116
        if ($this->parseClass) {
117
            $this->getClassCompletion($object, $factory, $name, $path);
118
        }
119
        // ServiceLocator Components
120
        if ($this->parseComponents) {
121
            $this->getComponentCompletion($object, $recursionDepth, $path);
122
        }
123
        // Class properties
124
        if ($this->parseProperties) {
125
            $this->getPropertyCompletion($object, $factory, $recursionDepth, $path);
126
        }
127
        // Class methods
128
        if ($this->parseMethods) {
129
            $this->getMethodCompletion($object, $factory, $path);
130
        }
131
        // Behavior properties
132
        if ($this->parseBehaviors) {
133
            $this->getBehaviorCompletion($object, $factory, $recursionDepth, $path);
134
        }
135
    }
136
137
    // Protected Methods
138
    // =========================================================================
139
140
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
141
     * @param $object
0 ignored issues
show
Missing parameter comment
Loading history...
142
     * @param DocBlockFactory $factory
0 ignored issues
show
Missing parameter comment
Loading history...
143
     * @param string $name
0 ignored issues
show
Expected 10 spaces after parameter type; 1 found
Loading history...
Missing parameter comment
Loading history...
144
     * @param $path
0 ignored issues
show
Missing parameter comment
Loading history...
145
     */
0 ignored issues
show
Missing @return tag in function comment
Loading history...
146
    protected function getClassCompletion($object, DocBlockFactory $factory, string $name, $path): void
147
    {
148
        try {
149
            $reflectionClass = new ReflectionClass($object);
150
        } catch (ReflectionException $e) {
151
            return;
152
        }
153
        // Information on the class itself
154
        $className = $reflectionClass->getName();
155
        $docs = $this->getDocs($reflectionClass, $factory);
156
        CompleteItem::create()
157
            ->detail((string)$className)
158
            ->documentation((string)$docs)
159
            ->kind(CompleteItemKind::ClassKind)
160
            ->label((string)$name)
161
            ->insertText((string)$name)
162
            ->add($this, $path);
163
    }
164
165
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
166
     * @param $object
0 ignored issues
show
Missing parameter comment
Loading history...
167
     * @param $recursionDepth
0 ignored issues
show
Missing parameter comment
Loading history...
168
     * @param $path
0 ignored issues
show
Missing parameter comment
Loading history...
169
     */
0 ignored issues
show
Missing @return tag in function comment
Loading history...
170
    protected function getComponentCompletion($object, $recursionDepth, $path): void
171
    {
172
        if ($object instanceof ServiceLocator) {
173
            foreach ($object->getComponents() as $key => $value) {
174
                $componentObject = null;
0 ignored issues
show
The assignment to $componentObject is dead and can be removed.
Loading history...
175
                try {
176
                    $componentObject = $object->get($key);
177
                } catch (InvalidConfigException $e) {
178
                    // That's okay
179
                }
180
                if ($componentObject) {
181
                    $this->parseObject($key, $componentObject, $recursionDepth, $path);
182
                }
183
            }
184
        }
185
    }
186
187
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
188
     * @param $object
0 ignored issues
show
Missing parameter comment
Loading history...
189
     * @param DocBlockFactory $factory
0 ignored issues
show
Missing parameter comment
Loading history...
190
     * @param $recursionDepth
0 ignored issues
show
Missing parameter comment
Loading history...
191
     * @param string $path
0 ignored issues
show
Expected 10 spaces after parameter type; 1 found
Loading history...
Missing parameter comment
Loading history...
192
     */
0 ignored issues
show
Missing @return tag in function comment
Loading history...
193
    protected function getPropertyCompletion($object, DocBlockFactory $factory, $recursionDepth, string $path): void
194
    {
195
        try {
196
            $reflectionClass = new ReflectionClass($object);
197
        } catch (ReflectionException $e) {
198
            return;
199
        }
200
        $reflectionProperties = $reflectionClass->getProperties();
201
        $customField = false;
202
        if ($object instanceof Behavior) {
203
            $customField = true;
204
        }
205
        $sortPrefix = $customField ? $this->customPropertySortPrefix : $this->propertySortPrefix;
206
        foreach ($reflectionProperties as $reflectionProperty) {
207
            $propertyName = $reflectionProperty->getName();
208
            // Exclude some properties
209
            $propertyAllowed = true;
210
            foreach (self::EXCLUDED_PROPERTY_REGEXES as $excludePattern) {
211
                $pattern = '`' . $excludePattern . '`i';
212
                if (preg_match($pattern, $propertyName) === 1) {
213
                    $propertyAllowed = false;
214
                }
215
            }
216
            if (in_array($propertyName, self::EXCLUDED_PROPERTY_NAMES, true)) {
217
                $propertyAllowed = false;
218
            }
219
            if ($customField && in_array($propertyName, self::EXCLUDED_BEHAVIOR_NAMES, true)) {
220
                $propertyAllowed = false;
221
            }
222
            // Process the property
223
            if ($propertyAllowed && $reflectionProperty->isPublic()) {
224
                $detail = "Property";
225
                $docblock = null;
226
                $docs = $reflectionProperty->getDocComment();
227
                if ($docs) {
228
                    $docblock = $factory->create($docs);
229
                    $docs = '';
230
                    $summary = $docblock->getSummary();
231
                    if (!empty($summary)) {
232
                        $docs = $summary;
233
                    }
234
                    $description = $docblock->getDescription()->render();
235
                    if (!empty($description)) {
236
                        $docs = $description;
237
                    }
238
                }
239
                // Figure out the type
240
                if ($docblock) {
241
                    $tag = $docblock->getTagsByName('var');
242
                    if ($tag && isset($tag[0])) {
243
                        $docs = $tag[0];
244
                    }
245
                }
246
                if (preg_match('/@var\s+([^\s]+)/', $docs, $matches)) {
247
                    list(, $type) = $matches;
248
                    $detail = $type;
249
                }
250
                if ($detail === "Property") {
251
                    if ((PHP_MAJOR_VERSION >= 7 && PHP_MINOR_VERSION >= 4) || (PHP_MAJOR_VERSION >= 8)) {
252
                        if ($reflectionProperty->hasType()) {
253
                            $reflectionType = $reflectionProperty->getType();
254
                            if ($reflectionType instanceof ReflectionNamedType) {
0 ignored issues
show
The type nystudio107\twigfield\base\ReflectionNamedType was not found. Did you mean ReflectionNamedType? If so, make sure to prefix the type with \.
Loading history...
255
                                $type = $reflectionType->getName();
0 ignored issues
show
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

255
                                /** @scrutinizer ignore-call */ 
256
                                $type = $reflectionType->getName();
Loading history...
256
                                $detail = $type;
257
                            }
258
                        }
259
                        if ((PHP_MAJOR_VERSION >= 8) && $reflectionProperty->hasDefaultValue()) {
260
                            $value = $reflectionProperty->getDefaultValue();
261
                            if (is_array($value)) {
262
                                $value = json_encode($value);
263
                            }
264
                            if (!empty($value)) {
265
                                $detail = (string)$value;
266
                            }
267
                        }
268
                    }
269
                }
270
                $thisPath = trim(implode('.', [$path, $propertyName]), '.');
271
                $label = $propertyName;
272
                CompleteItem::create()
273
                    ->detail((string)$detail)
274
                    ->documentation((string)$docs)
275
                    ->kind($customField ? CompleteItemKind::FieldKind : CompleteItemKind::PropertyKind)
276
                    ->label((string)$label)
277
                    ->insertText((string)$label)
278
                    ->sortText((string)$sortPrefix . (string)$label)
279
                    ->add($this, $thisPath);
280
                // Recurse through if this is an object
281
                if (isset($object->$propertyName) && is_object($object->$propertyName)) {
282
                    if (!$customField && !in_array($propertyName, self::EXCLUDED_PROPERTY_NAMES, true)) {
283
                        $this->parseObject($propertyName, $object->$propertyName, $recursionDepth, $path);
284
                    }
285
                }
286
            }
287
        }
288
    }
289
290
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
291
     * @param $object
0 ignored issues
show
Missing parameter comment
Loading history...
292
     * @param DocBlockFactory $factory
0 ignored issues
show
Missing parameter comment
Loading history...
293
     * @param string $path
0 ignored issues
show
Missing parameter comment
Loading history...
Expected 10 spaces after parameter type; 1 found
Loading history...
294
     */
0 ignored issues
show
Missing @return tag in function comment
Loading history...
295
    protected function getMethodCompletion($object, DocBlockFactory $factory, string $path): void
296
    {
297
        try {
298
            $reflectionClass = new ReflectionClass($object);
299
        } catch (ReflectionException $e) {
300
            return;
301
        }
302
        $reflectionMethods = $reflectionClass->getMethods();
303
        foreach ($reflectionMethods as $reflectionMethod) {
304
            $methodName = $reflectionMethod->getName();
305
            // Exclude some properties
306
            $methodAllowed = true;
307
            foreach (self::EXCLUDED_METHOD_REGEXES as $excludePattern) {
308
                $pattern = '`' . $excludePattern . '`i';
309
                if (preg_match($pattern, $methodName) === 1) {
310
                    $methodAllowed = false;
311
                }
312
            }
313
            // Process the method
314
            if ($methodAllowed && $reflectionMethod->isPublic()) {
315
                $docblock = null;
316
                $docs = $this->getDocs($reflectionMethod, $factory);
317
                $detail = $methodName . '(';
318
                $params = $reflectionMethod->getParameters();
319
                $paramList = [];
320
                foreach ($params as $param) {
321
                    if ($param->hasType()) {
322
                        $reflectionType = $param->getType();
323
                        if ($reflectionType instanceof ReflectionUnionType) {
324
                            $unionTypes = $reflectionType->getTypes();
325
                            $typeName = '';
326
                            foreach ($unionTypes as $unionType) {
327
                                $typeName .= '|' . $unionType->getName();
328
                            }
329
                            $typeName = trim($typeName, '|');
330
                            $paramList[] = $typeName . ': ' . '$' . $param->getName();
331
                        } else {
332
                            $paramList[] = $param->getType()->getName() . ': ' . '$' . $param->getName();
333
                        }
334
                    } else {
335
                        $paramList[] = '$' . $param->getName();
336
                    }
337
                }
338
                $detail .= implode(', ', $paramList) . ')';
339
                $thisPath = trim(implode('.', [$path, $methodName]), '.');
340
                $label = $methodName . '()';
341
                $docsPreamble = '';
342
                // Figure out the type
343
                if ($docblock) {
344
                    $tags = $docblock->getTagsByName('param');
345
                    if ($tags) {
346
                        $docsPreamble = "Parameters:\n\n";
347
                        foreach ($tags as $tag) {
348
                            $docsPreamble .= $tag . "\n";
349
                        }
350
                        $docsPreamble .= "\n";
351
                    }
352
                }
353
                CompleteItem::create()
354
                    ->detail((string)$detail)
355
                    ->documentation((string)$docsPreamble . (string)$docs)
356
                    ->kind(CompleteItemKind::MethodKind)
357
                    ->label((string)$label)
358
                    ->insertText((string)$label)
359
                    ->sortText($this->methodSortPrefix . (string)$label)
360
                    ->add($this, $thisPath);
361
            }
362
        }
363
    }
364
365
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
366
     * @param $object
0 ignored issues
show
Missing parameter comment
Loading history...
367
     * @param DocBlockFactory $factory
0 ignored issues
show
Missing parameter comment
Loading history...
368
     * @param $recursionDepth
0 ignored issues
show
Missing parameter comment
Loading history...
369
     * @param string $path
0 ignored issues
show
Missing parameter comment
Loading history...
Expected 10 spaces after parameter type; 1 found
Loading history...
370
     */
0 ignored issues
show
Missing @return tag in function comment
Loading history...
371
    protected function getBehaviorCompletion($object, DocBlockFactory $factory, $recursionDepth, string $path): void
372
    {
373
        if ($object instanceof Element) {
374
            $behaviorClass = $object->getBehavior('customFields');
375
            if ($behaviorClass) {
376
                $this->getPropertyCompletion($behaviorClass, $factory, $recursionDepth, $path);
377
            }
378
        }
379
    }
380
381
    /**
382
     * Try to get the best documentation block we can
383
     *
384
     * @param ReflectionClass|ReflectionMethod $reflection
0 ignored issues
show
Missing parameter comment
Loading history...
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
385
     * @param DocBlockFactory $factory
0 ignored issues
show
Missing parameter comment
Loading history...
Expected 18 spaces after parameter type; 1 found
Loading history...
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
386
     * @return string
0 ignored issues
show
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
387
     */
388
    protected function getDocs($reflection, DocBlockFactory $factory): string
389
    {
390
        $docs = $reflection->getDocComment();
391
        if ($docs) {
392
            $docblock = $factory->create($docs);
393
            $summary = $docblock->getSummary();
394
            if (!empty($summary)) {
395
                $docs = $summary;
396
            }
397
            $description = $docblock->getDescription()->render();
398
            if (!empty($description)) {
399
                $docs = $description;
400
            }
401
        }
402
403
        return $docs ?: '';
404
    }
405
}
406