|
4 | 4 |
|
5 | 5 | namespace Tempest\Core\Kernel; |
6 | 6 |
|
7 | | -use FilesystemIterator; |
8 | | -use RecursiveDirectoryIterator; |
9 | | -use RecursiveIteratorIterator; |
10 | | -use SplFileInfo; |
11 | 7 | use Tempest\Container\Container; |
12 | 8 | use Tempest\Core\DiscoveryCache; |
13 | 9 | use Tempest\Core\DiscoveryCacheStrategy; |
@@ -92,69 +88,85 @@ private function buildDiscovery(string $discoveryClass): Discovery |
92 | 88 | } |
93 | 89 |
|
94 | 90 | foreach ($this->kernel->discoveryLocations as $location) { |
95 | | - if ($this->shouldSkipLocation($location)) { |
96 | | - continue; |
97 | | - } |
| 91 | + $this->discoverPath($discovery, $location, $location->path); |
| 92 | + } |
98 | 93 |
|
99 | | - $directories = new RecursiveDirectoryIterator($location->path, FilesystemIterator::UNIX_PATHS | FilesystemIterator::SKIP_DOTS); |
100 | | - $files = new RecursiveIteratorIterator($directories); |
| 94 | + return $discovery; |
| 95 | + } |
101 | 96 |
|
102 | | - /** @var SplFileInfo $file */ |
103 | | - foreach ($files as $file) { |
104 | | - $fileName = $file->getFilename(); |
| 97 | + private function discoverPath(Discovery $discovery, DiscoveryLocation $location, string $path): void |
| 98 | + { |
| 99 | + if ($this->shouldSkipLocation($location)) { |
| 100 | + return; |
| 101 | + } |
105 | 102 |
|
106 | | - if ($fileName === '') { |
107 | | - continue; |
108 | | - } |
| 103 | + $input = realpath($path); |
109 | 104 |
|
110 | | - if ($fileName === '.') { |
111 | | - continue; |
112 | | - } |
| 105 | + if ($input === false) { |
| 106 | + return; |
| 107 | + } |
113 | 108 |
|
114 | | - if ($fileName === '..') { |
115 | | - continue; |
116 | | - } |
| 109 | + // Make sure the path is not marked for skipping |
| 110 | + if ($this->shouldSkipBasedOnConfig($input)) { |
| 111 | + return; |
| 112 | + } |
117 | 113 |
|
118 | | - $input = $file->getRealPath(); |
| 114 | + // Directories are scanned recursively |
| 115 | + if (is_dir($input)) { |
| 116 | + if ($this->shouldSkipDirectory($input)) { |
| 117 | + return; |
| 118 | + } |
119 | 119 |
|
120 | | - if ($this->shouldSkipBasedOnConfig($input)) { |
| 120 | + foreach (scandir($input) as $subPath) { |
| 121 | + if ($subPath === '.' || $subPath === '..') { |
121 | 122 | continue; |
122 | 123 | } |
123 | 124 |
|
124 | | - // We assume that any PHP file that starts with an uppercase letter will be a class |
125 | | - if ($file->getExtension() === 'php' && ucfirst($fileName) === $fileName) { |
126 | | - $className = $location->toClassName($file->getPathname()); |
127 | | - |
128 | | - // Discovery errors (syntax errors, missing imports, etc.) |
129 | | - // are ignored when they happen in vendor files, |
130 | | - // but they are allowed to be thrown in project code |
131 | | - if ($location->isVendor()) { |
132 | | - try { |
133 | | - $input = new ClassReflector($className); |
134 | | - } catch (Throwable) { // @mago-expect best-practices/no-empty-catch-clause |
135 | | - } |
136 | | - } elseif (class_exists($className)) { |
137 | | - $input = new ClassReflector($className); |
138 | | - } |
139 | | - } |
| 125 | + $this->discoverPath($discovery, $location, "{$input}/{$subPath}"); |
| 126 | + } |
140 | 127 |
|
141 | | - if ($this->shouldSkipBasedOnConfig($input)) { |
142 | | - continue; |
143 | | - } |
| 128 | + return; |
| 129 | + } |
144 | 130 |
|
145 | | - if ($input instanceof ClassReflector) { |
146 | | - // If the input is a class, we'll call `discover` |
147 | | - if (! $this->shouldSkipDiscoveryForClass($discovery, $input)) { |
148 | | - $discovery->discover($location, $input); |
149 | | - } |
150 | | - } elseif ($discovery instanceof DiscoversPath) { |
151 | | - // If the input is NOT a class, AND the discovery class can discover paths, we'll call `discoverPath` |
152 | | - $discovery->discoverPath($location, $input); |
| 131 | + $pathInfo = pathinfo($input); |
| 132 | + $extension = $pathInfo['extension'] ?? null; |
| 133 | + $fileName = $pathInfo['filename'] ?: null; |
| 134 | + |
| 135 | + // We assume that any PHP file that starts with an uppercase letter will be a class |
| 136 | + if ($extension === 'php' && ucfirst($fileName) === $fileName) { |
| 137 | + $className = $location->toClassName($input); |
| 138 | + |
| 139 | + // Discovery errors (syntax errors, missing imports, etc.) |
| 140 | + // are ignored when they happen in vendor files, |
| 141 | + // but they are allowed to be thrown in project code |
| 142 | + if ($location->isVendor()) { |
| 143 | + try { |
| 144 | + $input = new ClassReflector($className); |
| 145 | + } catch (Throwable $e) { // @mago-expect best-practices/no-empty-catch-clause |
153 | 146 | } |
| 147 | + } elseif (class_exists($className)) { |
| 148 | + $input = new ClassReflector($className); |
154 | 149 | } |
155 | 150 | } |
156 | 151 |
|
157 | | - return $discovery; |
| 152 | + // If the input is a class, we'll try to discover it |
| 153 | + if ($input instanceof ClassReflector) { |
| 154 | + // Check whether the class should be skipped |
| 155 | + if ($this->shouldSkipBasedOnConfig($input)) { |
| 156 | + return; |
| 157 | + } |
| 158 | + |
| 159 | + // Check whether this class is marked with `#[SkipDiscovery]` |
| 160 | + if ($this->shouldSkipDiscoveryForClass($discovery, $input)) { |
| 161 | + return; |
| 162 | + } |
| 163 | + |
| 164 | + $discovery->discover($location, $input); |
| 165 | + } elseif ($discovery instanceof DiscoversPath) { |
| 166 | + // If the input is NOT a class, AND the discovery class can discover paths, we'll call `discoverPath` |
| 167 | + // Note that we've already checked whether the path was marked for skipping earlier in this method |
| 168 | + $discovery->discoverPath($location, $input); |
| 169 | + } |
158 | 170 | } |
159 | 171 |
|
160 | 172 | /** |
@@ -212,4 +224,14 @@ private function shouldSkipLocation(DiscoveryLocation $location): bool |
212 | 224 | DiscoveryCacheStrategy::PARTIAL => $location->isVendor(), |
213 | 225 | }; |
214 | 226 | } |
| 227 | + |
| 228 | + /** |
| 229 | + * Check whether a given directory should be skipped |
| 230 | + */ |
| 231 | + private function shouldSkipDirectory(string $path): bool |
| 232 | + { |
| 233 | + $directory = pathinfo($path, PATHINFO_BASENAME); |
| 234 | + |
| 235 | + return $directory === 'node_modules'; |
| 236 | + } |
215 | 237 | } |
0 commit comments