FileFinder::findPhpFiles()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
c 0
b 0
f 0
dl 0
loc 8
rs 10
cc 4
nc 3
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Churn\File;
6
7
use Generator;
8
use RecursiveDirectoryIterator;
9
use RecursiveIteratorIterator;
10
use SplFileInfo;
11
12
/**
13
 * @internal
14
 */
15
final class FileFinder
16
{
17
    /**
18
     * List of file extensions to look for.
19
     *
20
     * @var array<string>
21
     */
22
    private $fileExtensions;
23
24
    /**
25
     * List of regular expressions used to filter files to ignore.
26
     *
27
     * @var array<string>
28
     */
29
    private $filters;
30
31
    /**
32
     * The base path.
33
     *
34
     * @var string
35
     */
36
    private $basePath;
37
38
    /**
39
     * Class constructor.
40
     *
41
     * @param array<string> $fileExtensions List of file extensions to look for.
42
     * @param array<string> $filesToIgnore List of files to ignore.
43
     * @param string $basePath The base path.
44
     */
45
    public function __construct(array $fileExtensions, array $filesToIgnore, string $basePath)
46
    {
47
        $this->fileExtensions = $fileExtensions;
48
        $this->filters = \array_map(function (string $fileToIgnore): string {
49
            return $this->patternToRegex($fileToIgnore);
50
        }, $filesToIgnore);
51
        $this->basePath = $basePath;
52
    }
53
54
    /**
55
     * Recursively finds all files with the .php extension in the provided
56
     * $paths and returns list as array.
57
     *
58
     * @param array<string> $paths Paths in which to look for .php files.
59
     * @return Generator<int, File>
60
     */
61
    public function getPhpFiles(array $paths): Generator
62
    {
63
        foreach ($paths as $path) {
64
            $absolutePath = FileHelper::toAbsolutePath($path, $this->basePath);
65
66
            yield from $this->getPhpFilesFromPath($absolutePath);
67
        }
68
    }
69
70
    /**
71
     * Recursively finds all files with the .php extension in the provided
72
     * $path adds them to $this->files.
73
     *
74
     * @param string $path Path in which to look for .php files.
75
     * @return Generator<int, File>
76
     */
77
    private function getPhpFilesFromPath(string $path): Generator
78
    {
79
        if (\is_file($path)) {
80
            $file = new SplFileInfo($path);
81
82
            yield new File((string) $file->getRealPath(), $this->getDisplayPath($file));
83
84
            return;
85
        }
86
87
        if (!\is_dir($path)) {
88
            // invalid path
89
            return;
90
        }
91
92
        foreach ($this->findPhpFiles($path) as $file) {
93
            yield new File((string) $file->getRealPath(), $this->getDisplayPath($file));
94
        }
95
    }
96
97
    /**
98
     * @param SplFileInfo $file The file object.
99
     * @return string The file path to display.
100
     */
101
    private function getDisplayPath(SplFileInfo $file): string
102
    {
103
        return FileHelper::toRelativePath($file->getPathname(), $this->basePath);
104
    }
105
106
    /**
107
     * Recursively finds all PHP files in a given directory.
108
     *
109
     * @param string $path Path in which to look for .php files.
110
     * @return Generator<int, SplFileInfo>
111
     */
112
    private function findPhpFiles(string $path): Generator
113
    {
114
        foreach ($this->findFiles($path) as $file) {
115
            if (!\in_array($file->getExtension(), $this->fileExtensions, true) || $this->fileShouldBeIgnored($file)) {
116
                continue;
117
            }
118
119
            yield $file;
120
        }
121
    }
122
123
    /**
124
     * Recursively finds all files in a given directory.
125
     *
126
     * @param string $path Path in which to look for .php files.
127
     * @return Generator<int, SplFileInfo>
128
     */
129
    private function findFiles(string $path): Generator
130
    {
131
        $directoryIterator = new RecursiveDirectoryIterator($path);
132
133
        foreach (new RecursiveIteratorIterator($directoryIterator) as $item) {
134
            if (!$item instanceof SplFileInfo || $item->isDir()) {
135
                continue;
136
            }
137
138
            yield $item;
139
        }
140
    }
141
142
    /**
143
     * Determines if a file should be ignored.
144
     *
145
     * @param SplFileInfo $file File.
146
     */
147
    private function fileShouldBeIgnored(SplFileInfo $file): bool
148
    {
149
        foreach ($this->filters as $regex) {
150
            $realPath = $file->getRealPath();
151
            if (false === $realPath || (bool) \preg_match("#{$regex}#", $realPath)) {
152
                return true;
153
            }
154
        }
155
156
        return false;
157
    }
158
159
    /**
160
     * Translate file path pattern to regex string.
161
     *
162
     * @param string $filePattern File pattern to be ignored.
163
     */
164
    private function patternToRegex(string $filePattern): string
165
    {
166
        $regex = (string) \preg_replace("#(.*)\*([\w.]*)$#", "$1.+$2$", $filePattern);
167
168
        if ('\\' === \DIRECTORY_SEPARATOR) {
169
            $regex = \str_replace('/', '\\\\', $regex);
170
        }
171
172
        return $regex;
173
    }
174
}
175