Completed
Push — master ( 949f21...8711d2 )
by Iman
01:33
created

CheckPsr4::fixReferences()   B

Complexity

Conditions 6
Paths 12

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
nc 12
nop 3
dl 0
loc 20
rs 8.9777
c 0
b 0
f 0
1
<?php
2
3
namespace Imanghafoori\LaravelMicroscope\Commands;
4
5
use Illuminate\Console\Command;
6
use Illuminate\Support\Composer;
7
use Illuminate\Support\Facades\View;
8
use Illuminate\Support\Str;
9
use Imanghafoori\LaravelMicroscope\Analyzers\ComposerJson;
10
use Imanghafoori\LaravelMicroscope\CheckNamespaces;
11
use Imanghafoori\LaravelMicroscope\ErrorReporters\ErrorPrinter;
12
use Imanghafoori\LaravelMicroscope\FileReaders\Paths;
13
use Imanghafoori\LaravelMicroscope\LaravelPaths\FilePath;
14
use Imanghafoori\LaravelMicroscope\LaravelPaths\LaravelPaths;
15
use Imanghafoori\LaravelMicroscope\SpyClasses\RoutePaths;
16
use Symfony\Component\Finder\Finder;
17
18
class CheckPsr4 extends Command
19
{
20
    protected $signature = 'check:psr4 {--d|detailed : Show files being checked} {--f|force} {--s|nofix}';
21
22
    protected $description = 'Checks the validity of namespaces';
23
24
    public function handle(ErrorPrinter $errorPrinter)
25
    {
26
        $this->line('');
27
        $this->info('Start checking PSR-4 namespaces...');
28
        $time = microtime(true);
29
30
        $errorPrinter->printer = $this->output;
31
32
        $autoload = ComposerJson::readAutoload();
33
        $this->checkNamespaces($autoload);
34
        $olds = \array_keys(CheckNamespaces::$changedNamespaces);
35
        $news = \array_values(CheckNamespaces::$changedNamespaces);
36
37
        $this->option('nofix') && config(['microscope.no_fix' => true]);
38
39
        if (! config('microscope.no_fix')) {
40
            $this->fixReferences($autoload, $olds, $news);
41
        }
42
        if (Str::startsWith(request()->server('argv')[1] ?? '', 'check:psr4')) {
43
            $this->reportResult($autoload);
44
            $this->printErrorsCount($errorPrinter, $time);
45
        } else {
46
            $this->getOutput()->writeln(' - '.CheckNamespaces::$checkedNamespaces.' namespaces were checked.');
47
        }
48
49
        $this->composerDumpIfNeeded($errorPrinter);
50
    }
51
52
    private function composerDumpIfNeeded(ErrorPrinter $errorPrinter)
53
    {
54
        if ($c = $errorPrinter->getCount('badNamespace')) {
55
            $this->output->write('- '.$c.' Namespace'.($c > 1 ? 's' : '').' Fixed, Running: "composer dump"');
56
            app(Composer::class)->dumpAutoloads();
57
            $this->info("\n".'finished: "composer dump"');
58
        }
59
    }
60
61
    private function fixRefs($_path, $olds, $news)
62
    {
63
        $lines = file($_path);
64
        $changed = [];
65
        $olds = $this->deriveVariants($olds);
66
        $news = $this->deriveVariants($news);
67
        foreach ($lines as $i => $line) {
68
            $count = 0;
69
            $lines[$i] = \str_replace($olds, $news, $line, $count);
70
            $count && $changed[] = ($i + 1);
0 ignored issues
show
Bug Best Practice introduced by
The expression $count of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
71
        }
72
73
        $changed && \file_put_contents($_path, \implode('', $lines));
0 ignored issues
show
Bug Best Practice introduced by
The expression $changed 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...
74
75
        return $changed;
76
    }
77
78
    private function deriveVariants($olds)
79
    {
80
        $newOld = [];
81
        foreach ($olds as $old) {
82
            $newOld[] = $old.'(';
83
            $newOld[] = $old.'::';
84
            $newOld[] = $old.';';
85
            $newOld[] = $old."\n";
86
            $newOld[] = $old."\r";
87
        }
88
89
        return $newOld;
90
    }
91
92
    private function collectNonPsr4Paths()
93
    {
94
        $paths = [
95
            RoutePaths::get(),
96
            Paths::getAbsFilePaths(LaravelPaths::migrationDirs()),
97
            Paths::getAbsFilePaths(config_path()),
98
            Paths::getAbsFilePaths(LaravelPaths::factoryDirs()),
99
            Paths::getAbsFilePaths(LaravelPaths::seeders()),
100
            LaravelPaths::bladeFilePaths(),
101
        ];
102
103
        return $this->mergePaths($paths);
104
    }
105
106
    private function mergePaths($paths)
107
    {
108
        $all = [];
109
        foreach ($paths as $p) {
110
            $all = array_merge($all, $p);
111
        }
112
113
        return $all;
114
    }
115
116
    private function fixReferences($autoload, $olds, $news)
117
    {
118
        foreach ($autoload as $psr4Namespace => $psr4Path) {
119
            $files = FilePath::getAllPhpFiles($psr4Path);
120
            foreach ($files as $classFilePath) {
121
                $_path = $classFilePath->getRealPath();
122
                $lineNumbers = $this->fixRefs($_path, $olds, $news);
123
                foreach ($lineNumbers as $line) {
124
                    $this->report($_path, $line);
125
                }
126
            }
127
        }
128
129
        foreach ($this->collectNonPsr4Paths() as $_path) {
130
            $lineNumbers = $this->fixRefs($_path, $olds, $news);
131
            foreach ($lineNumbers as $line) {
132
                $this->report($_path, $line);
133
            }
134
        }
135
    }
136
137
    private function checkNamespaces(array $autoload)
138
    {
139
        foreach ($autoload as $psr4Namespace => $psr4Path) {
140
            $files = FilePath::getAllPhpFiles($psr4Path);
141
            CheckNamespaces::within($files, $psr4Path, $psr4Namespace, $this);
0 ignored issues
show
Documentation introduced by
$files is of type object<Symfony\Component\Finder\Finder>, but the function expects a object<Imanghafoori\LaravelMicroscope\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
142
        }
143
    }
144
145
    private function report($_path, $line)
146
    {
147
        app(ErrorPrinter::class)->simplePendError($_path, $line, '', 'ns_replacement', 'Namespace replacement:');
148
    }
149
150
    private function printErrorsCount($errorPrinter, $time)
151
    {
152
        if ($errorCount = $errorPrinter->errorsList['total']) {
153
            $this->warn(PHP_EOL.$errorCount.' error(s) found in namespaces');
154
        } else {
155
            $this->noErrorFound($time);
156
        }
157
    }
158
159
    private function reportResult($autoload)
160
    {
161
        $this->getOutput()->writeln('');
162
        $this->getOutput()->writeln('<fg=green>Finished!</fg=green>');
163
        $this->getOutput()->writeln('==============================');
164
        $this->getOutput()->writeln('<options=bold;fg=yellow>'.CheckNamespaces::$checkedNamespaces.' classes were checked under:</>');
165
        $this->getOutput()->writeln(' - '.implode("\n - ", array_keys($autoload)).'');
166
    }
167
168
    private function noErrorFound($time)
169
    {
170
        $time = microtime(true) - $time;
171
        $this->line(PHP_EOL.'<fg=green>All namespaces are correct!</><fg=blue> You rock  \(^_^)/ </>');
172
        $this->line('<fg=red;options=bold>'.round($time, 5).'(s)</>');
173
        $this->line('');
174
    }
175
}
176