• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

orchestral / testbench-core / 17389855726

02 Sep 2025 12:33AM UTC coverage: 92.138% (-0.1%) from 92.242%
17389855726

push

github

web-flow
Ability to merge Framework configurations (#357)

* wip

Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com>

* wip

Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com>

* wip

Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com>

* wip

Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com>

* wip

Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com>

* wip

Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com>

* wip

Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com>

* wip

Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com>

---------

Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com>

31 of 34 new or added lines in 2 files covered. (91.18%)

1 existing line in 1 file now uncovered.

1547 of 1679 relevant lines covered (92.14%)

76.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.65
/src/Bootstrap/LoadConfiguration.php
1
<?php
2

3
namespace Orchestra\Testbench\Bootstrap;
4

5
use Generator;
6
use Illuminate\Config\Repository;
7
use Illuminate\Contracts\Config\Repository as RepositoryContract;
8
use Illuminate\Contracts\Foundation\Application;
9
use Illuminate\Support\Collection;
10
use Illuminate\Support\LazyCollection;
11
use Orchestra\Sidekick\Env;
12
use RuntimeException;
13
use SplFileInfo;
14
use Symfony\Component\Finder\Finder;
15

16
use function Orchestra\Testbench\default_skeleton_path;
17
use function Orchestra\Testbench\package_path;
18

19
/**
20
 * @internal
21
 *
22
 * @phpstan-type TLaravel \Illuminate\Contracts\Foundation\Application
23
 */
24
class LoadConfiguration
25
{
26
    /**
27
     * Cached Laravel Framework default configuration.
28
     *
29
     * @var \Illuminate\Contracts\Config\Repository|null
30
     */
31
    protected static ?RepositoryContract $cachedFrameworkConfigurations = null;
32

33
    /**
34
     * Bootstrap the given application.
35
     *
36
     * @param  TLaravel  $app
37
     * @return void
38
     */
39
    public function bootstrap(Application $app): void
40
    {
41
        $app->instance('config', $config = new Repository([]));
190✔
42

43
        $this->loadConfigurationFiles($app, $config);
190✔
44

45
        if (\is_null($config->get('database.connections.testing'))) {
190✔
46
            $config->set('database.connections.testing', [
190✔
47
                'driver' => 'sqlite',
190✔
48
                'database' => ':memory:',
190✔
49
                'foreign_key_constraints' => Env::get('DB_FOREIGN_KEYS', false),
190✔
50
            ]);
190✔
51
        }
52

53
        $this->configureDefaultDatabaseConnection($config);
190✔
54

55
        mb_internal_encoding('UTF-8');
190✔
56
    }
57

58
    /**
59
     * Load the configuration items from all of the files.
60
     *
61
     * @param  TLaravel  $app
62
     * @param  \Illuminate\Contracts\Config\Repository  $config
63
     * @return void
64
     */
65
    private function loadConfigurationFiles(Application $app, RepositoryContract $config): void
66
    {
67
        $shouldMerge = method_exists($app, 'shouldMergeFrameworkConfiguration')
190✔
68
            ? $app->shouldMergeFrameworkConfiguration()
190✔
NEW
69
            : true;
×
70

71
        static::$cachedFrameworkConfigurations ??= new Repository(
190✔
72
            (new Collection($this->getFrameworkDefaultConfigurations()))
190✔
73
                ->transform(fn ($path, $key) => require $path)
190✔
74
                ->all()
190✔
75
        );
190✔
76

77
        $this->extendsLoadedConfiguration(
190✔
78
            (new LazyCollection(function () use ($app) {
190✔
79
                $path = $this->getConfigurationPath($app);
190✔
80

81
                if (\is_string($path)) {
190✔
82
                    yield from $this->getConfigurationsFromPath($path);
190✔
83
                }
84
            }))
190✔
85
                ->collect()
190✔
86
                ->transform(fn ($path, $key) => $this->resolveConfigurationFile($path, $key))
190✔
87
        )->each(static function ($path, $key) use ($config) {
190✔
88
            $config->set($key, require $path);
190✔
89
        })->when($shouldMerge === true, static function ($configurations) use ($config) {
190✔
90
            /** @var \Illuminate\Contracts\Config\Repository $baseConfigurations */
91
            $baseConfigurations = static::$cachedFrameworkConfigurations;
190✔
92

93
            /** @var array<int, string> $excludes */
94
            $excludes = $configurations->keys()->all();
190✔
95

96
            (new Collection($baseConfigurations->all()))->reject(
190✔
97
                fn ($data, $key) => \in_array($key, $excludes)
190✔
98
            )->each(function ($data, $key) use ($config) {
190✔
NEW
99
                $config->set($key, $data);
×
100
            });
190✔
101

102
            return $configurations->each(static function ($data, $key) use ($config, $baseConfigurations) {
190✔
103
                foreach (static::mergeableOptions($key) as $option) {
190✔
104
                    $name = "{$key}.{$option}";
190✔
105

106
                    $config->set($name, array_merge(($baseConfigurations->get($name) ?? []), ($config->get($name) ?? [])));
190✔
107
                }
108
            });
190✔
109
        });
190✔
110
    }
111

112
    /**
113
     * Get the configuration file nesting path.
114
     *
115
     * @param  \SplFileInfo  $file
116
     * @param  string  $configPath
117
     * @return string
118
     */
119
    protected function getNestedDirectory(SplFileInfo $file, string $configPath): string
120
    {
121
        $directory = $file->getPath();
190✔
122

123
        if ($nested = trim(str_replace($configPath, '', $directory), DIRECTORY_SEPARATOR)) {
190✔
124
            $nested = str_replace(DIRECTORY_SEPARATOR, '.', $nested).'.';
61✔
125
        }
126

127
        return $nested;
190✔
128
    }
129

130
    /**
131
     * Resolve the configuration file.
132
     *
133
     * @param  string  $path
134
     * @param  string  $key
135
     * @return string
136
     */
137
    protected function resolveConfigurationFile(string $path, string $key): string
138
    {
139
        return $path;
140✔
140
    }
141

142
    /**
143
     * Extend the loaded configuration.
144
     *
145
     * @param  \Illuminate\Support\Collection  $configurations
146
     * @return \Illuminate\Support\Collection
147
     */
148
    protected function extendsLoadedConfiguration(Collection $configurations): Collection
149
    {
150
        return $configurations;
140✔
151
    }
152

153
    /**
154
     * Configure the default database connection.
155
     *
156
     * @param  \Illuminate\Contracts\Config\Repository  $config
157
     * @return void
158
     */
159
    protected function configureDefaultDatabaseConnection(RepositoryContract $config): void
160
    {
161
        if ($config->get('database.default') === 'sqlite' && ! is_file($config->get('database.connections.sqlite.database'))) {
190✔
162
            $config->set('database.default', 'testing');
90✔
163
        }
164
    }
165

166
    /**
167
     * Get the application configuration path.
168
     *
169
     * @param  TLaravel  $app
170
     * @return string
171
     *
172
     * @throws \RuntimeException
173
     */
174
    protected function getConfigurationPath(Application $app): string
175
    {
176
        $configurationPath = is_dir($app->basePath('config'))
190✔
177
            ? $app->basePath('config')
190✔
UNCOV
178
            : default_skeleton_path('config');
×
179

180
        if ($configurationPath === false) {
190✔
NEW
181
            throw new RuntimeException('Unable to locate configuration path');
×
182
        }
183

184
        return $configurationPath;
190✔
185
    }
186

187
    /**
188
     * Get the framework default configurations.
189
     *
190
     * @return array<string, string>
191
     */
192
    protected function getFrameworkDefaultConfigurations(): array
193
    {
194
        return (new LazyCollection(function () {
1✔
195
            yield from $this->getConfigurationsFromPath(package_path(['vendor', 'laravel', 'framework', 'config']));
1✔
196
        }))->all();
1✔
197
    }
198

199
    /**
200
     * Get the configurations from path.
201
     *
202
     * @param  string  $path
203
     * @return \Generator
204
     */
205
    protected function getConfigurationsFromPath(string $path): Generator
206
    {
207
        foreach (Finder::create()->files()->name('*.php')->in($path) as $file) {
190✔
208
            $directory = $this->getNestedDirectory($file, $path);
190✔
209

210
            yield $directory.basename($file->getRealPath(), '.php') => $file->getRealPath();
190✔
211
        }
212
    }
213

214
    /**
215
     * Get the options within the configuration file that should be merged again.
216
     *
217
     * @param  string  $name
218
     * @return array<int, string>
219
     */
220
    public static function mergeableOptions(string $name): array
221
    {
222
        return [
223
            'auth' => ['guards', 'providers', 'passwords'],
224
            'broadcasting' => ['connections'],
225
            'cache' => ['stores'],
226
            'database' => ['connections'],
227
            'filesystems' => ['disks'],
228
            'logging' => ['channels'],
229
            'mail' => ['mailers'],
230
            'queue' => ['connections'],
231
        ][$name] ?? [];
190✔
232
    }
233
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc