Skip to content

Commit d28e896

Browse files
authored
perf(core): improve config loading performance (#1341)
1 parent 34e0d2d commit d28e896

File tree

1 file changed

+54
-19
lines changed

1 file changed

+54
-19
lines changed

packages/core/src/Kernel/LoadConfig.php

Lines changed: 54 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,12 @@
44

55
namespace Tempest\Core\Kernel;
66

7-
use FilesystemIterator;
8-
use RecursiveDirectoryIterator;
9-
use RecursiveIteratorIterator;
10-
use SplFileInfo;
117
use Tempest\Core\AppConfig;
128
use Tempest\Core\ConfigCache;
139
use Tempest\Core\Kernel;
10+
use Tempest\Support\Arr\MutableArray;
1411
use Tempest\Support\Str;
1512

16-
use function Tempest\Support\arr;
17-
1813
/** @internal */
1914
final readonly class LoadConfig
2015
{
@@ -40,21 +35,11 @@ public function __invoke(): void
4035
*/
4136
public function find(): array
4237
{
43-
$configPaths = [];
38+
$configPaths = new MutableArray();
4439

4540
// Scan for config files in all discovery locations
4641
foreach ($this->kernel->discoveryLocations as $discoveryLocation) {
47-
$directories = new RecursiveDirectoryIterator($discoveryLocation->path, FilesystemIterator::UNIX_PATHS | FilesystemIterator::SKIP_DOTS);
48-
$files = new RecursiveIteratorIterator($directories);
49-
50-
/** @var SplFileInfo $file */
51-
foreach ($files as $file) {
52-
if (! str_ends_with($file->getPathname(), '.config.php')) {
53-
continue;
54-
}
55-
56-
$configPaths[] = $file->getPathname();
57-
}
42+
$this->scan($discoveryLocation->path, $configPaths);
5843
}
5944

6045
$suffixes = [
@@ -64,7 +49,7 @@ public function find(): array
6449
'development' => ['.dev.config.php', '.local.config.php'],
6550
];
6651

67-
return arr($configPaths)
52+
return $configPaths
6853
->filter(fn (string $path) => match (true) {
6954
$this->appConfig->environment->isLocal() => ! Str\contains($path, [...$suffixes['production'], ...$suffixes['staging'], ...$suffixes['testing']]),
7055
$this->appConfig->environment->isProduction() => ! Str\contains($path, [...$suffixes['staging'], ...$suffixes['testing'], ...$suffixes['development']]),
@@ -94,4 +79,54 @@ public function find(): array
9479
->values()
9580
->toArray();
9681
}
82+
83+
/**
84+
* Recursively scan a directory and apply a given set of discovery classes to all files
85+
*/
86+
private function scan(string $path, MutableArray $configPaths): void
87+
{
88+
$input = realpath($path);
89+
90+
// Make sure the path is valid
91+
if ($input === false) {
92+
return;
93+
}
94+
95+
// Directories are scanned recursively
96+
if (is_dir($input)) {
97+
// Make sure the current directory is not marked for skipping
98+
if ($this->shouldSkipDirectory($input)) {
99+
return;
100+
}
101+
102+
foreach (scandir($input, SCANDIR_SORT_NONE) as $subPath) {
103+
// `.` and `..` are skipped
104+
if ($subPath === '.' || $subPath === '..') {
105+
continue;
106+
}
107+
108+
// Scan all files and folders within this directory
109+
$this->scan("{$input}/{$subPath}", $configPaths);
110+
}
111+
112+
return;
113+
}
114+
115+
// Only include config files
116+
if (! str_ends_with($input, '.config.php')) {
117+
return;
118+
}
119+
120+
$configPaths[] = $input;
121+
}
122+
123+
/**
124+
* Check whether a given directory should be skipped
125+
*/
126+
private function shouldSkipDirectory(string $path): bool
127+
{
128+
$directory = pathinfo($path, PATHINFO_BASENAME);
129+
130+
return $directory === 'node_modules' || $directory === 'vendor';
131+
}
97132
}

0 commit comments

Comments
 (0)