Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
4 changes: 3 additions & 1 deletion src/Config/Repository.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,12 @@ class Repository extends BaseRepository implements ArrayAccess, RepositoryContra
* @param string $environment
* @return void
*/
public function __construct(LoaderInterface $loader, $environment)
public function __construct(LoaderInterface $loader, $environment, array $items = [])
{
$this->loader = $loader;
$this->environment = $environment;

parent::__construct($items);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/Foundation/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,7 @@ public function registerCoreContainerAliases()
*/
public function getCachedConfigPath()
{
return PathResolver::join($this->storagePath(), '/framework/config.php');
return PathResolver::join($this->storagePath(), '/framework/' . ($this['env'] ?? 'production') . '.config.php');
}

/**
Expand Down
12 changes: 11 additions & 1 deletion src/Foundation/Bootstrap/LoadConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,17 @@ public function bootstrap(Application $app): void
return $this->getEnvironmentFromHost();
});

$app->instance('config', $config = new Repository($fileLoader, $app['env']));
$items = [];

// 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 (empty($app['disableConfigCacheLoading']) && file_exists($cached = $app->getCachedConfigPath())) {
$items = require $cached;
$items['loadedFromCache'] = true;
}

$app->instance('config', $config = new Repository($fileLoader, $app['env'], $items));

date_default_timezone_set($config['app.timezone']);

Expand Down
119 changes: 118 additions & 1 deletion src/Foundation/Console/ConfigCacheCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,129 @@

namespace Winter\Storm\Foundation\Console;

use Exception;
use Illuminate\Contracts\Console\Kernel as ConsoleKernelContract;
use Illuminate\Foundation\Console\ConfigCacheCommand as BaseCommand;
use LogicException;
use Throwable;

class ConfigCacheCommand extends BaseCommand
{
/**
* @var string The console command signature.
*/
protected $signature = 'config:cache
{env? : Which environment should be cached?}
';

/**
* Execute the console command.
*
* @return void
*
* @throws \LogicException
*/
public function handle()
{
$this->components->warn('Caching configuration files is not supported in Winter CMS. See https://github.com/wintercms/winter/issues/1297#issuecomment-2624578966');
$args = [];
if ($this->argument('env')) {
$args['env'] = $this->argument('env');
}

// This is the only change to the parent, it allows us to only clear the requested config
$this->callSilent('config:clear', $args);

$config = $this->getFreshConfiguration();

$configPath = $this->laravel->getCachedConfigPath();

$this->files->put(
$configPath,
'<?php return ' . var_export($config, true) . ';' . PHP_EOL
);

try {
require $configPath;
} catch (Throwable $e) {
$this->files->delete($configPath);

throw new LogicException('Your configuration files are not serializable.', 0, $e);
}

$this->components->info('Configuration cached successfully.');
}

/**
* Boot a fresh copy of the application configuration.
*
* @return array
*/
protected function getFreshConfiguration()
{
// This allows us to detect and override the "env by subdomain" feature of Winter
if ($this->argument('env') && ($environment = $this->getEnvironmentConfiguration())) {
// Grab hosts as env => domain
$hosts = isset($environment['hosts'])
? array_flip($environment['hosts'])
: [];

// if we have env, set the host to the domain to "trick" the system into registering the correct config
if (isset($hosts[$this->argument('env')])) {
$_SERVER['HTTP_HOST'] = $hosts[$this->argument('env')];
}
}

$app = require $this->laravel->bootstrapPath() . '/app.php';

// This allows us to inform the LoadConfiguration class to not load from cache on fresh load
$app['disableConfigCacheLoading'] = true;

// This overrides the new app and existing app's env
if ($this->argument('env')) {
$this->laravel->detectEnvironment(fn() => $this->argument('env') ?? $app['env']);

Check failure on line 84 in src/Foundation/Console/ConfigCacheCommand.php

View workflow job for this annotation

GitHub Actions / Code Analysis

Expression on left side of ?? is not nullable.
$app->detectEnvironment(fn() => $this->argument('env') ?? $app['env']);

Check failure on line 85 in src/Foundation/Console/ConfigCacheCommand.php

View workflow job for this annotation

GitHub Actions / Code Analysis

Expression on left side of ?? is not nullable.
}

// Stolen stuff from the Laravel command
$app->useStoragePath($this->laravel->storagePath());
$app->make(ConsoleKernelContract::class)->bootstrap();

// Force preload all registered configs
foreach ($app['config']->getNamespaces() as $namespace => $path) {
foreach (glob($path . DIRECTORY_SEPARATOR . '*.php') as $file) {
$app['config']->get($namespace . '::' . pathinfo($file, PATHINFO_FILENAME));
}
}

return $app['config']->all();
}

/**
* Load the environment configuration.
* @TODO: This is copied from LoadConfiguration, should be exposed somewhere...
* @see storm/src/Foundation/Bootstrap/LoadConfiguration.php
*/
protected function getEnvironmentConfiguration(): array
{
$config = [];
$environment = env('APP_ENV');
if ($environment && file_exists($configPath = base_path() . '/config/' . $environment . '/environment.php')) {
try {
$config = require $configPath;
}
catch (Exception $ex) {
//
}
Comment on lines +115 to +117
Copy link

Copilot AI Sep 3, 2025

Choose a reason for hiding this comment

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

Empty catch blocks make debugging difficult. Consider logging the exception or adding a comment explaining why the exception is being silently ignored.

Copilot uses AI. Check for mistakes.
}
elseif (file_exists($configPath = base_path() . '/config/environment.php')) {
try {
$config = require $configPath;
}
catch (Exception $ex) {
//
}
Comment on lines +123 to +125
Copy link

Copilot AI Sep 3, 2025

Choose a reason for hiding this comment

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

Empty catch blocks make debugging difficult. Consider logging the exception or adding a comment explaining why the exception is being silently ignored.

Copilot uses AI. Check for mistakes.
}

return $config;
}
}
28 changes: 26 additions & 2 deletions src/Foundation/Console/ConfigClearCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,32 @@

class ConfigClearCommand extends BaseCommand
{
public function handle()
/**
* @var string The console command signature.
*/
protected $signature = 'config:clear
{env? : Which environment should be cleared?}
';

/**
* Execute the console command.
*
* @return void
*/
public function handle(): void
{
$this->components->warn('Caching configuration files is not supported in Winter CMS. See https://github.com/wintercms/winter/issues/1297#issuecomment-2624578966');
$configPath = $this->laravel->getCachedConfigPath();

if ($this->argument('env')) {
$configPath = realpath(
dirname($this->laravel->getCachedConfigPath())
. DIRECTORY_SEPARATOR
. $this->argument('env')
. '.config.php'
);
Comment on lines +26 to +31
Copy link

Copilot AI Sep 3, 2025

Choose a reason for hiding this comment

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

The realpath() function will return false if the file doesn't exist, which could cause issues when trying to delete a non-existent config file. Consider using dirname($this->laravel->getCachedConfigPath()) . DIRECTORY_SEPARATOR . $this->argument('env') . '.config.php' directly without realpath().

Suggested change
$configPath = realpath(
dirname($this->laravel->getCachedConfigPath())
. DIRECTORY_SEPARATOR
. $this->argument('env')
. '.config.php'
);
$configPath = dirname($this->laravel->getCachedConfigPath())
. DIRECTORY_SEPARATOR
. $this->argument('env')
. '.config.php';

Copilot uses AI. Check for mistakes.
}

$this->files->delete($configPath);
$this->components->info('Configuration cache cleared successfully.');
}
}
82 changes: 68 additions & 14 deletions tests/Foundation/ApplicationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,85 @@

class ApplicationTest extends TestCase
{
protected Application $fakeApp;
protected string $basePath;

protected function setUp(): void
{
// Mock application
$this->basePath = '/tmp/custom-path';
$this->app = new Application($this->basePath);
$this->fakeApp = new Application($this->basePath);
}

public function testPathMethods()
{
$this->assertEquals(PathResolver::join($this->basePath, '/plugins'), $this->app->pluginsPath());
$this->assertEquals(PathResolver::join($this->basePath, '/themes'), $this->app->themesPath());
$this->assertEquals(PathResolver::join($this->basePath, '/storage/temp'), $this->app->tempPath());
$this->assertEquals(PathResolver::join($this->basePath, '/storage/app/uploads'), $this->app->uploadsPath());
$this->assertEquals(PathResolver::join($this->basePath, '/storage/app/media'), $this->app->mediaPath());
$this->assertEquals(
PathResolver::join($this->basePath, '/plugins'),
$this->fakeApp->pluginsPath()
);
$this->assertEquals(
PathResolver::join($this->basePath, '/themes'),
$this->fakeApp->themesPath()
);
$this->assertEquals(
PathResolver::join($this->basePath, '/storage/temp'),
$this->fakeApp->tempPath()
);
$this->assertEquals(
PathResolver::join($this->basePath, '/storage/app/uploads'),
$this->fakeApp->uploadsPath()
);
$this->assertEquals(
PathResolver::join($this->basePath, '/storage/app/media'),
$this->fakeApp->mediaPath()
);

$storagePath = $this->basePath . '/storage';

$this->assertEquals(PathResolver::join($storagePath, '/framework/config.php'), $this->app->getCachedConfigPath());
$this->assertEquals(PathResolver::join($storagePath, '/framework/routes.php'), $this->app->getCachedRoutesPath());
$this->assertEquals(PathResolver::join($storagePath, '/framework/compiled.php'), $this->app->getCachedCompilePath());
$this->assertEquals(PathResolver::join($storagePath, '/framework/services.php'), $this->app->getCachedServicesPath());
$this->assertEquals(PathResolver::join($storagePath, '/framework/packages.php'), $this->app->getCachedPackagesPath());
$this->assertEquals(PathResolver::join($storagePath, '/framework/classes.php'), $this->app->getCachedClassesPath());
$this->assertEquals(
PathResolver::join($storagePath, '/framework/production.config.php'),
$this->fakeApp->getCachedConfigPath()
);
$this->assertEquals(
PathResolver::join($storagePath, '/framework/routes.php'),
$this->fakeApp->getCachedRoutesPath()
);
$this->assertEquals(
PathResolver::join($storagePath, '/framework/compiled.php'),
$this->fakeApp->getCachedCompilePath()
);
$this->assertEquals(
PathResolver::join($storagePath, '/framework/services.php'),
$this->fakeApp->getCachedServicesPath()
);
$this->assertEquals(
PathResolver::join($storagePath, '/framework/packages.php'),
$this->fakeApp->getCachedPackagesPath()
);
$this->assertEquals(
PathResolver::join($storagePath, '/framework/classes.php'),
$this->fakeApp->getCachedClassesPath()
);
}

public function testCachedConfigPath()
{
$storagePath = $this->basePath . '/storage';

// No env set
$this->assertEquals(
PathResolver::join($storagePath, '/framework/production.config.php'),
$this->fakeApp->getCachedConfigPath()
);

// Test that setting the app env to each value results in the correct config file being returned
foreach (['test', 'prod', 'local', 'dev'] as $env) {
$this->fakeApp->detectEnvironment(fn() => $env);
$this->assertEquals(
PathResolver::join($storagePath, '/framework/' . $env . '.config.php'),
$this->fakeApp->getCachedConfigPath()
);
}
}

public function testSetPathMethods()
Expand All @@ -40,9 +94,9 @@ public function testSetPathMethods()

$path = PathResolver::join('/my'.ucfirst($type), '/custom/path');
$expected = PathResolver::standardize($path);
$this->app->{$setter}($path);
$this->fakeApp->{$setter}($path);

$this->assertEquals($expected, $this->app->{$getter}());
$this->assertEquals($expected, $this->fakeApp->{$getter}());
}
}
}
Loading