Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions src/Illuminate/Foundation/Bootstrap/LoadConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Illuminate\Foundation\Bootstrap;

use Closure;
use Illuminate\Config\Repository;
use Illuminate\Contracts\Config\Repository as RepositoryContract;
use Illuminate\Contracts\Foundation\Application;
Expand All @@ -11,6 +12,22 @@

class LoadConfiguration
{
/**
* @var (Closure(Application): array<array-key, mixed>)|null
*/
protected static ?Closure $alwaysUseConfig = null;

/**
* Set a callback to return the config values.
*
* @param (Closure(Application): array<array-key, mixed>)|null $alwaysUseConfig
* @return void
*/
public static function setAlwaysUseConfig(?Closure $alwaysUseConfig): void
{
static::$alwaysUseConfig = $alwaysUseConfig;
}

/**
* Bootstrap the given application.
*
Expand All @@ -24,18 +41,24 @@ public function bootstrap(Application $app)
// First we will see if we have a cache configuration file. If we do, we'll load
// the configuration items from that file so that it is very quick. Otherwise
// we will need to spin through every configuration file and load them all.
if (file_exists($cached = $app->getCachedConfigPath())) {
$loadedFromCache = false;
if (self::$alwaysUseConfig !== null) {
$items = $app->call(self::$alwaysUseConfig);

$loadedFromCache = true;
} elseif (file_exists($cached = $app->getCachedConfigPath())) {
$items = require $cached;

$app->instance('config_loaded_from_cache', $loadedFromCache = true);
$loadedFromCache = true;
}
$app->instance('config_loaded_from_cache', $loadedFromCache);

// Next we will spin through all of the configuration files in the configuration
// directory and load each one into the repository. This will make all of the
// options available to the developer for use in various parts of this app.
$app->instance('config', $config = new Repository($items));

if (! isset($loadedFromCache)) {
if (! $loadedFromCache) {
$this->loadConfigurationFiles($app, $config);
}

Expand Down
1 change: 1 addition & 0 deletions src/Illuminate/Foundation/Testing/CachedState.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
class CachedState
{
public static array $cachedRoutes;
public static array $cachedConfig;
}
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ protected function tearDownTheTestEnvironment(): void
*/
protected function setUpTraits()
{
$uses = array_flip(class_uses_recursive(static::class));
$uses = $this->testUsesTraits ?? array_flip(class_uses_recursive(static::class));

if (isset($uses[RefreshDatabase::class])) {
$this->refreshDatabase();
Expand Down
14 changes: 13 additions & 1 deletion src/Illuminate/Foundation/Testing/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ abstract class TestCase extends BaseTestCase
Concerns\InteractsWithTestCaseLifecycle,
Concerns\InteractsWithViews;

/**
* The list of trait that this test uses, fetched recursively.
*
* @var array<class-string, int>
*/
protected array $testUsesTraits;

/**
* Creates the application.
*
Expand All @@ -29,10 +36,15 @@ public function createApplication()
{
$app = require Application::inferBasePath().'/bootstrap/app.php';

$this->testUsesTraits = array_flip(class_uses_recursive(static::class));
if (isset(CachedState::$cachedRoutes) &&
in_array(WithCachedRoutes::class, class_uses_recursive(static::class))) {
isset($this->testUsesTraits[WithCachedRoutes::class])) {
$app->booting(fn () => $this->markRoutesCached($app));
}
if (isset(CachedState::$cachedConfig) &&
isset($this->testUsesTraits[WithCachedConfig::class])) {
$this->markConfigCached($app);
}

$app->make(Kernel::class)->bootstrap();

Expand Down
34 changes: 34 additions & 0 deletions src/Illuminate/Foundation/Testing/WithCachedConfig.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace Illuminate\Foundation\Testing;

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Bootstrap\LoadConfiguration;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider;

trait WithCachedConfig
{
protected function setUpWithCachedConfig(): void
{
if ((CachedState::$cachedConfig ?? null) === null) {
CachedState::$cachedConfig = $this->app->make('config')->all();
}

$this->markConfigCached($this->app);
}

protected function tearDownWithCachedConfig(): void
{
LoadConfiguration::setAlwaysUseConfig(null);
}

/**
* Inform the container to treat configuration as cached.
*/
protected function markConfigCached(Application $app): void
{
$app->instance('config_loaded_from_cache', true); // I'm not sure this is actually needed

LoadConfiguration::setAlwaysUseConfig(static fn () => CachedState::$cachedConfig);
Copy link
Contributor Author

@cosmastech cosmastech Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any concern here about someone storing an object in cache that can be modified between tests? We could serialize/unserialize it on each test. This would cost some CPU cycles for each test, but could insulate us from that problem. Not sure how often people are storing mutable objects in their configurations 🤔

}
}