Passed
Push — main ( f07e9b...aa17d9 )
by Yaroslav
03:18
created

getPropertiesFromMethods()   B

Complexity

Conditions 10
Paths 4

Size

Total Lines 41
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 110

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 26
c 1
b 0
f 0
nc 4
nop 1
dl 0
loc 41
ccs 0
cts 26
cp 0
crap 110
rs 7.6666

How to fix   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 NovaFlexibleContent\Console\Commands;
4
5
use Illuminate\Console\Command;
6
use Illuminate\Database\Eloquent\Casts\Attribute;
7
use Illuminate\Filesystem\Filesystem;
8
use Illuminate\Support\Str;
9
use Laravel\Nova\Fields\Field;
10
use NovaFlexibleContent\Flexible;
11
use NovaFlexibleContent\Layouts\Collections\LayoutsCollection;
12
use NovaFlexibleContent\Layouts\Layout;
13
use ReflectionClass;
14
use ReflectionNamedType;
15
use ReflectionType;
16
use Symfony\Component\Finder\Finder;
17
18
class GenerateIdeHelperLayoutsCommand extends Command
19
{
20
    protected $signature = 'nova-flexible-content:ide-helper:layouts
21
     {--filename= : File name}
22
     ';
23
24
    protected $description = '';
25
26
    protected Filesystem $files;
27
28 38
    public function __construct(Filesystem $files)
29
    {
30 38
        parent::__construct();
31 38
        $this->files = $files;
32
    }
33
34
    public function handle()
35
    {
36
        $filename = $this->option('filename') ?? '_ide_helper_flexible_layouts.php';
37
38
39
        $content = $this->generateDocs();
40
41
        $written = $this->files->put($filename, $content);
42
        if ($written !== false) {
43
            $this->info("Layout(s) information was written to $filename");
44
        } else {
45
            $this->error("Failed to write model information to $filename");
46
        }
47
48
        return 0;
49
    }
50
51
    protected function generateDocs(): string
52
    {
53
        $formatterOff = '@formatter:off';
54
        $output       = "<?php
55
// {$formatterOff}
56
/**
57
 * A helper file for your Flexible Layouts
58
 *
59
 * @author Think Dev Team <[email protected]>
60
 */
61
\n\n";
62
63
        $namespace = app()->getNamespace();
0 ignored issues
show
introduced by
The method getNamespace() does not exist on Illuminate\Container\Container. Are you sure you never get this type here, but always one of the subclasses? ( Ignorable by Annotation )

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

63
        $namespace = app()->/** @scrutinizer ignore-call */ getNamespace();
Loading history...
64
65
        $layouts = [];
66
67
        foreach ((new Finder())->in(app_path('Nova'))->files() as $resource) {
68
            $resource = $namespace . str_replace(
69
                ['/', '.php'],
70
                ['\\', ''],
71
                Str::after($resource->getPathname(), app_path() . DIRECTORY_SEPARATOR)
72
            );
73
74
            if (
75
                is_subclass_of($resource, Layout::class) &&
76
                !(new ReflectionClass($resource))->isAbstract()
77
            ) {
78
                $layouts[] = $resource;
79
            }
80
        }
81
82
        foreach ($layouts as $layout) {
83
            $output .= $this->createLayoutPhpDocs($layout);
84
        }
85
86
        return $output;
87
    }
88
89
    /**
90
     * @param class-string<Layout> $layout
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<Layout> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<Layout>.
Loading history...
91
     * @return string
92
     */
93
    protected function createLayoutPhpDocs(string $layout): string
94
    {
95
        $output                     = '';
96
        $parentClassName            = Layout::class;
97
        $layoutsCollectionClassName = LayoutsCollection::class;
98
        $className                  = class_basename($layout);
99
        $namespace                  = trim(Str::beforeLast($layout, $className), '\\');
100
101
        $output .= "namespace {$namespace} {\n\n";
102
103
        $output .= "/**\n";
104
        $output .= "* {$layout}\n";
105
        $output .= "*\n";
106
        /** @var Field $field */
107
        foreach ($layout::make()->fieldsCollection() as $field) {
108
            $output .= "* @property-read mixed \${$field->attribute}\n";
109
            if ($field instanceof Flexible) {
110
                $fieldName = 'flexible' . Str::ucfirst(Str::camel($field->attribute));
111
112
                $output .= "* @property-read \\{$layoutsCollectionClassName} \${$fieldName}\n";
113
            }
114
        }
115
        $output .= $this->getPropertiesFromMethods($layout);
116
        $output .= "*/\n";
117
        $output .= "class {$className} extends {$parentClassName} {}\n\n";
118
119
        $output .= "}\n\n";
120
121
        return $output;
122
    }
123
124
    public function getPropertiesFromMethods(string $layout): string
125
    {
126
127
        $properties = [];
128
129
        $methods = get_class_methods($layout);
130
        if ($methods) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $methods of type string[] 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...
131
            sort($methods);
132
            foreach ($methods as $method) {
133
                $reflection  = new \ReflectionMethod($layout, $method);
134
                $type        = $this->getReturnTypeFromReflection($reflection);
135
                $isAttribute = is_a($type, Attribute::class, true);
136
                if (
137
                    Str::startsWith($method, 'get') && Str::endsWith(
138
                        $method,
139
                        'Attribute'
140
                    ) && $method !== 'getAttribute'
141
                ) {
142
                    //Magic get<name>Attribute
143
                    $name = Str::snake(substr($method, 3, -9));
144
                    if (!empty($name)) {
145
                        $type              = $this->getReturnTypeFromReflection($reflection);
146
                        $properties[$name] = $type;
147
                    }
148
                } elseif ($isAttribute) {
149
                    $name  = Str::snake($method);
150
                    $types = $this->getAttributeReturnType($layout, $method);
0 ignored issues
show
Bug introduced by
The method getAttributeReturnType() does not exist on NovaFlexibleContent\Cons...IdeHelperLayoutsCommand. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

150
                    /** @scrutinizer ignore-call */ 
151
                    $types = $this->getAttributeReturnType($layout, $method);
Loading history...
151
152
                    if ($types->has('get')) {
153
                        $properties[$name] = 'mixed';
154
                    }
155
                }
156
            }
157
        }
158
159
        $output = '';
160
        foreach ($properties as $name => $type) {
161
            $output .= "* @property-read $type \${$name}\n";
162
        }
163
164
        return $output;
165
    }
166
167
    protected function getReturnTypeFromReflection(\ReflectionMethod $reflection): ?string
168
    {
169
        $returnType = $reflection->getReturnType();
170
        if (!$returnType) {
171
            return null;
172
        }
173
174
        $types = $this->extractReflectionTypes($returnType);
175
176
        $type = implode('|', $types);
177
178
        if ($returnType->allowsNull()) {
179
            $type .= '|null';
180
        }
181
182
        return $type;
183
    }
184
185
    protected function extractReflectionTypes(ReflectionType $reflection_type): array
186
    {
187
        if ($reflection_type instanceof ReflectionNamedType) {
188
            $types[] = $this->getReflectionNamedType($reflection_type);
0 ignored issues
show
Comprehensibility Best Practice introduced by
$types was never initialized. Although not strictly required by PHP, it is generally a good practice to add $types = array(); before regardless.
Loading history...
189
        } else {
190
            $types = [];
191
            foreach ($reflection_type->getTypes() as $named_type) {
0 ignored issues
show
Bug introduced by
The method getTypes() does not exist on ReflectionType. It seems like you code against a sub-type of ReflectionType such as ReflectionUnionType. ( Ignorable by Annotation )

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

191
            foreach ($reflection_type->/** @scrutinizer ignore-call */ getTypes() as $named_type) {
Loading history...
192
                if ($named_type->getName() === 'null') {
193
                    continue;
194
                }
195
196
                $types[] = $this->getReflectionNamedType($named_type);
197
            }
198
        }
199
200
        return $types;
201
    }
202
203
    protected function getReflectionNamedType(ReflectionNamedType $paramType): string
204
    {
205
        $parameterName = $paramType->getName();
206
        if (!$paramType->isBuiltin()) {
207
            $parameterName = '\\' . $parameterName;
208
        }
209
210
        return $parameterName;
211
    }
212
}
213