Merge pull request #2271 from flarum/fl/laravel-updates-config

This extracts another real class for dealing with the configuration options stored in `config.php`. The idea is to reduce the scope of the `Application` class and make it easier to inject exactly what's needed (rather than an array, which is complicated, or the bloated `Application` class).
This commit is contained in:
Alexander Skvortsov 2020-09-25 11:22:53 -04:00 committed by GitHub
commit 50cbb7be5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 310 additions and 87 deletions

View File

@ -63,7 +63,7 @@ class AdminServiceProvider extends AbstractServiceProvider
$this->app->bind('flarum.admin.error_handler', function () { $this->app->bind('flarum.admin.error_handler', function () {
return new HttpMiddleware\HandleErrors( return new HttpMiddleware\HandleErrors(
$this->app->make(Registry::class), $this->app->make(Registry::class),
$this->app['flarum']->inDebugMode() ? $this->app->make(WhoopsFormatter::class) : $this->app->make(ViewFormatter::class), $this->app['flarum.config']->inDebugMode() ? $this->app->make(WhoopsFormatter::class) : $this->app->make(ViewFormatter::class),
$this->app->tagged(Reporter::class) $this->app->tagged(Reporter::class)
); );
}); });

View File

@ -59,7 +59,7 @@ class ApiServiceProvider extends AbstractServiceProvider
$this->app->bind('flarum.api.error_handler', function () { $this->app->bind('flarum.api.error_handler', function () {
return new HttpMiddleware\HandleErrors( return new HttpMiddleware\HandleErrors(
$this->app->make(Registry::class), $this->app->make(Registry::class),
new JsonApiFormatter($this->app['flarum']->inDebugMode()), new JsonApiFormatter($this->app['flarum.config']->inDebugMode()),
$this->app->tagged(Reporter::class) $this->app->tagged(Reporter::class)
); );
}); });

View File

@ -10,6 +10,7 @@
namespace Flarum\Api\Serializer; namespace Flarum\Api\Serializer;
use Flarum\Foundation\Application; use Flarum\Foundation\Application;
use Flarum\Foundation\Config;
use Flarum\Http\UrlGenerator; use Flarum\Http\UrlGenerator;
use Flarum\Settings\SettingsRepositoryInterface; use Flarum\Settings\SettingsRepositoryInterface;
@ -21,9 +22,9 @@ class ForumSerializer extends AbstractSerializer
protected $type = 'forums'; protected $type = 'forums';
/** /**
* @var Application * @var Config
*/ */
protected $app; protected $config;
/** /**
* @var SettingsRepositoryInterface * @var SettingsRepositoryInterface
@ -36,13 +37,13 @@ class ForumSerializer extends AbstractSerializer
protected $url; protected $url;
/** /**
* @param Application $app * @param Config $config
* @param SettingsRepositoryInterface $settings * @param SettingsRepositoryInterface $settings
* @param UrlGenerator $url * @param UrlGenerator $url
*/ */
public function __construct(Application $app, SettingsRepositoryInterface $settings, UrlGenerator $url) public function __construct(Config $config, SettingsRepositoryInterface $settings, UrlGenerator $url)
{ {
$this->app = $app; $this->config = $config;
$this->settings = $settings; $this->settings = $settings;
$this->url = $url; $this->url = $url;
} }
@ -66,7 +67,7 @@ class ForumSerializer extends AbstractSerializer
'showLanguageSelector' => (bool) $this->settings->get('show_language_selector', true), 'showLanguageSelector' => (bool) $this->settings->get('show_language_selector', true),
'baseUrl' => $url = $this->url->to('forum')->base(), 'baseUrl' => $url = $this->url->to('forum')->base(),
'basePath' => parse_url($url, PHP_URL_PATH) ?: '', 'basePath' => parse_url($url, PHP_URL_PATH) ?: '',
'debug' => $this->app->inDebugMode(), 'debug' => $this->config->inDebugMode(),
'apiUrl' => $this->url->to('api')->base(), 'apiUrl' => $this->url->to('api')->base(),
'welcomeTitle' => $this->settings->get('welcome_title'), 'welcomeTitle' => $this->settings->get('welcome_title'),
'welcomeMessage' => $this->settings->get('welcome_message'), 'welcomeMessage' => $this->settings->get('welcome_message'),

View File

@ -14,6 +14,7 @@ use Flarum\Database\Console\MigrateCommand;
use Flarum\Database\Console\ResetCommand; use Flarum\Database\Console\ResetCommand;
use Flarum\Foundation\AbstractServiceProvider; use Flarum\Foundation\AbstractServiceProvider;
use Flarum\Foundation\Console\CacheClearCommand; use Flarum\Foundation\Console\CacheClearCommand;
use Flarum\Foundation\Console\InfoCommand;
class ConsoleServiceProvider extends AbstractServiceProvider class ConsoleServiceProvider extends AbstractServiceProvider
{ {
@ -26,6 +27,7 @@ class ConsoleServiceProvider extends AbstractServiceProvider
return [ return [
CacheClearCommand::class, CacheClearCommand::class,
GenerateMigrationCommand::class, GenerateMigrationCommand::class,
InfoCommand::class,
MigrateCommand::class, MigrateCommand::class,
ResetCommand::class, ResetCommand::class,
]; ];

View File

@ -73,7 +73,7 @@ class ForumServiceProvider extends AbstractServiceProvider
$this->app->bind('flarum.forum.error_handler', function () { $this->app->bind('flarum.forum.error_handler', function () {
return new HttpMiddleware\HandleErrors( return new HttpMiddleware\HandleErrors(
$this->app->make(Registry::class), $this->app->make(Registry::class),
$this->app['flarum']->inDebugMode() ? $this->app->make(WhoopsFormatter::class) : $this->app->make(ViewFormatter::class), $this->app['flarum.config']->inDebugMode() ? $this->app->make(WhoopsFormatter::class) : $this->app->make(ViewFormatter::class),
$this->app->tagged(Reporter::class) $this->app->tagged(Reporter::class)
); );
}); });

View File

@ -95,7 +95,9 @@ class Application
*/ */
public function config($key, $default = null) public function config($key, $default = null)
{ {
return Arr::get($this->container->make('flarum.config'), $key, $default); $config = $this->container->make('flarum.config');
return $config[$key] ?? $default;
} }
/** /**
@ -117,18 +119,10 @@ class Application
public function url($path = null) public function url($path = null)
{ {
$config = $this->container->make('flarum.config'); $config = $this->container->make('flarum.config');
$url = Arr::get($config, 'url', Arr::get($_SERVER, 'REQUEST_URI')); $url = (string) $config->url();
if (is_array($url)) {
if (isset($url[$path])) {
return $url[$path];
}
$url = $url['base'];
}
if ($path) { if ($path) {
$url .= '/'.Arr::get($config, "paths.$path", $path); $url .= '/'.($config["paths.$path"] ?? $path);
} }
return $url; return $url;

75
src/Foundation/Config.php Normal file
View File

@ -0,0 +1,75 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Foundation;
use ArrayAccess;
use Illuminate\Support\Arr;
use InvalidArgumentException;
use Laminas\Diactoros\Uri;
use Psr\Http\Message\UriInterface;
use RuntimeException;
class Config implements ArrayAccess
{
private $data;
public function __construct(array $data)
{
$this->data = $data;
$this->requireKeys('url');
}
public function url(): UriInterface
{
return new Uri(rtrim($this->data['url'], '/'));
}
public function inDebugMode(): bool
{
return $this->data['debug'] ?? false;
}
public function inMaintenanceMode(): bool
{
return $this->data['offline'] ?? false;
}
private function requireKeys(...$keys)
{
foreach ($keys as $key) {
if (! array_key_exists($key, $this->data)) {
throw new InvalidArgumentException(
"Configuration is invalid without a $key key"
);
}
}
}
public function offsetGet($offset)
{
return Arr::get($this->data, $offset);
}
public function offsetExists($offset)
{
return Arr::has($this->data, $offset);
}
public function offsetSet($offset, $value)
{
throw new RuntimeException('The Config is immutable');
}
public function offsetUnset($offset)
{
throw new RuntimeException('The Config is immutable');
}
}

View File

@ -12,6 +12,7 @@ namespace Flarum\Foundation\Console;
use Flarum\Console\AbstractCommand; use Flarum\Console\AbstractCommand;
use Flarum\Extension\ExtensionManager; use Flarum\Extension\ExtensionManager;
use Flarum\Foundation\Application; use Flarum\Foundation\Application;
use Flarum\Foundation\Config;
use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableStyle; use Symfony\Component\Console\Helper\TableStyle;
@ -23,15 +24,15 @@ class InfoCommand extends AbstractCommand
protected $extensions; protected $extensions;
/** /**
* @var array * @var Config
*/ */
protected $config; protected $config;
/** /**
* @param ExtensionManager $extensions * @param ExtensionManager $extensions
* @param array $config * @param Config config
*/ */
public function __construct(ExtensionManager $extensions, array $config) public function __construct(ExtensionManager $extensions, Config $config)
{ {
$this->extensions = $extensions; $this->extensions = $extensions;
$this->config = $config; $this->config = $config;
@ -64,11 +65,11 @@ class InfoCommand extends AbstractCommand
$this->getExtensionTable()->render(); $this->getExtensionTable()->render();
$this->output->writeln('<info>Base URL:</info> '.$this->config['url']); $this->output->writeln('<info>Base URL:</info> '.$this->config->url());
$this->output->writeln('<info>Installation path:</info> '.getcwd()); $this->output->writeln('<info>Installation path:</info> '.getcwd());
$this->output->writeln('<info>Debug mode:</info> '.($this->config['debug'] ? 'ON' : 'off')); $this->output->writeln('<info>Debug mode:</info> '.($this->config->inDebugMode() ? 'ON' : 'off'));
if ($this->config['debug']) { if ($this->config->inDebugMode()) {
$this->error( $this->error(
"Don't forget to turn off debug mode! It should never be turned on in a production system." "Don't forget to turn off debug mode! It should never be turned on in a production system."
); );

View File

@ -9,7 +9,6 @@
namespace Flarum\Foundation; namespace Flarum\Foundation;
use Flarum\Foundation\Console\InfoCommand;
use Flarum\Http\Middleware\DispatchRoute; use Flarum\Http\Middleware\DispatchRoute;
use Flarum\Settings\SettingsRepositoryInterface; use Flarum\Settings\SettingsRepositoryInterface;
use Illuminate\Console\Command; use Illuminate\Console\Command;
@ -28,11 +27,11 @@ class InstalledApp implements AppInterface
protected $container; protected $container;
/** /**
* @var array * @var Config
*/ */
protected $config; protected $config;
public function __construct(Container $container, array $config) public function __construct(Container $container, Config $config)
{ {
$this->container = $container; $this->container = $container;
$this->config = $config; $this->config = $config;
@ -48,7 +47,7 @@ class InstalledApp implements AppInterface
*/ */
public function getRequestHandler() public function getRequestHandler()
{ {
if ($this->inMaintenanceMode()) { if ($this->config->inMaintenanceMode()) {
return new MaintenanceModeHandler(); return new MaintenanceModeHandler();
} elseif ($this->needsUpdate()) { } elseif ($this->needsUpdate()) {
return $this->getUpdaterHandler(); return $this->getUpdaterHandler();
@ -70,11 +69,6 @@ class InstalledApp implements AppInterface
return $pipe; return $pipe;
} }
protected function inMaintenanceMode(): bool
{
return $this->config['offline'] ?? false;
}
protected function needsUpdate(): bool protected function needsUpdate(): bool
{ {
$settings = $this->container->make(SettingsRepositoryInterface::class); $settings = $this->container->make(SettingsRepositoryInterface::class);
@ -99,7 +93,7 @@ class InstalledApp implements AppInterface
protected function basePath(): string protected function basePath(): string
{ {
return parse_url($this->config['url'], PHP_URL_PATH) ?: '/'; return $this->config->url()->getPath() ?: '/';
} }
protected function subPath($pathName): string protected function subPath($pathName): string
@ -112,21 +106,14 @@ class InstalledApp implements AppInterface
*/ */
public function getConsoleCommands() public function getConsoleCommands()
{ {
$commands = []; return array_map(function ($command) {
$command = $this->container->make($command);
// The info command is a special case, as it requires a config parameter that's only available here. if ($command instanceof Command) {
$commands[] = $this->container->make(InfoCommand::class, ['config' => $this->config]); $command->setLaravel($this->container);
foreach ($this->container->make('flarum.console.commands') as $command) {
$newCommand = $this->container->make($command);
if ($newCommand instanceof Command) {
$newCommand->setLaravel($this->container);
} }
$commands[] = $newCommand; return $command;
} }, $this->container->make('flarum.console.commands'));
return $commands;
} }
} }

View File

@ -55,7 +55,7 @@ class InstalledSite implements SiteInterface
protected $paths; protected $paths;
/** /**
* @var array * @var Config
*/ */
protected $config; protected $config;
@ -64,7 +64,7 @@ class InstalledSite implements SiteInterface
*/ */
protected $extenders = []; protected $extenders = [];
public function __construct(Paths $paths, array $config) public function __construct(Paths $paths, Config $config)
{ {
$this->paths = $paths; $this->paths = $paths;
$this->config = $config; $this->config = $config;
@ -101,7 +101,8 @@ class InstalledSite implements SiteInterface
$container->instance('env', 'production'); $container->instance('env', 'production');
$container->instance('flarum.config', $this->config); $container->instance('flarum.config', $this->config);
$container->instance('flarum.debug', $laravel->inDebugMode()); $container->alias('flarum.config', Config::class);
$container->instance('flarum.debug', $this->config->inDebugMode());
$container->instance('config', $config = $this->getIlluminateConfig($laravel)); $container->instance('config', $config = $this->getIlluminateConfig($laravel));
$this->registerLogger($container); $this->registerLogger($container);

View File

@ -24,13 +24,13 @@ class Site
date_default_timezone_set('UTC'); date_default_timezone_set('UTC');
if (static::hasConfigFile($paths->base)) { if (! static::hasConfigFile($paths->base)) {
return ( return new UninstalledSite($paths, $_SERVER['REQUEST_URI']);
new InstalledSite($paths, static::loadConfig($paths->base))
)->extendWith(static::loadExtenders($paths->base));
} else {
return new UninstalledSite($paths);
} }
return (
new InstalledSite($paths, static::loadConfig($paths->base))
)->extendWith(static::loadExtenders($paths->base));
} }
protected static function hasConfigFile($basePath) protected static function hasConfigFile($basePath)
@ -38,7 +38,7 @@ class Site
return file_exists("$basePath/config.php"); return file_exists("$basePath/config.php");
} }
protected static function loadConfig($basePath): array protected static function loadConfig($basePath): Config
{ {
$config = include "$basePath/config.php"; $config = include "$basePath/config.php";
@ -46,7 +46,7 @@ class Site
throw new RuntimeException('config.php should return an array'); throw new RuntimeException('config.php should return an array');
} }
return $config; return new Config($config);
} }
protected static function loadExtenders($basePath): array protected static function loadExtenders($basePath): array

View File

@ -35,9 +35,15 @@ class UninstalledSite implements SiteInterface
*/ */
protected $paths; protected $paths;
public function __construct(Paths $paths) /**
* @var string
*/
private $baseUrl;
public function __construct(Paths $paths, string $baseUrl)
{ {
$this->paths = $paths; $this->paths = $paths;
$this->baseUrl = $baseUrl;
} }
/** /**
@ -58,8 +64,9 @@ class UninstalledSite implements SiteInterface
$laravel = new Application($container, $this->paths); $laravel = new Application($container, $this->paths);
$container->instance('env', 'production'); $container->instance('env', 'production');
$container->instance('flarum.config', []); $container->instance('flarum.config', new Config(['url' => $this->baseUrl]));
$container->instance('flarum.debug', $laravel->inDebugMode()); $container->alias('flarum.config', Config::class);
$container->instance('flarum.debug', true);
$container->instance('config', $config = $this->getIlluminateConfig()); $container->instance('config', $config = $this->getIlluminateConfig());
$this->registerLogger($container); $this->registerLogger($container);

View File

@ -9,7 +9,7 @@
namespace Flarum\Frontend\Content; namespace Flarum\Frontend\Content;
use Flarum\Foundation\Application; use Flarum\Foundation\Config;
use Flarum\Frontend\Compiler\CompilerInterface; use Flarum\Frontend\Compiler\CompilerInterface;
use Flarum\Frontend\Document; use Flarum\Frontend\Document;
use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Container\Container;
@ -19,17 +19,17 @@ use Psr\Http\Message\ServerRequestInterface as Request;
class Assets class Assets
{ {
protected $container; protected $container;
protected $app; protected $config;
/** /**
* @var \Flarum\Frontend\Assets * @var \Flarum\Frontend\Assets
*/ */
protected $assets; protected $assets;
public function __construct(Container $container, Application $app) public function __construct(Container $container, Config $config)
{ {
$this->container = $container; $this->container = $container;
$this->app = $app; $this->config = $config;
} }
public function forFrontend(string $name) public function forFrontend(string $name)
@ -48,7 +48,7 @@ class Assets
'css' => [$this->assets->makeCss(), $this->assets->makeLocaleCss($locale)] 'css' => [$this->assets->makeCss(), $this->assets->makeLocaleCss($locale)]
]; ];
if ($this->app->inDebugMode()) { if ($this->config->inDebugMode()) {
$this->commit(Arr::flatten($compilers)); $this->commit(Arr::flatten($compilers));
} }

View File

@ -11,8 +11,7 @@ namespace Flarum\Http;
use Dflydev\FigCookies\Modifier\SameSite; use Dflydev\FigCookies\Modifier\SameSite;
use Dflydev\FigCookies\SetCookie; use Dflydev\FigCookies\SetCookie;
use Flarum\Foundation\Application; use Flarum\Foundation\Config;
use Illuminate\Support\Arr;
class CookieFactory class CookieFactory
{ {
@ -52,19 +51,19 @@ class CookieFactory
protected $samesite; protected $samesite;
/** /**
* @param Application $app * @param Config $config
*/ */
public function __construct(Application $app) public function __construct(Config $config)
{ {
// Parse the forum's base URL so that we can determine the optimal cookie settings // If necessary, we will use the forum's base URL to determine smart defaults for cookie settings
$url = parse_url(rtrim($app->url(), '/')); $url = $config->url();
// Get the cookie settings from the config or use the default values // Get the cookie settings from the config or use the default values
$this->prefix = $app->config('cookie.name', 'flarum'); $this->prefix = $config['cookie.name'] ?? 'flarum';
$this->path = $app->config('cookie.path', Arr::get($url, 'path') ?: '/'); $this->path = $config['cookie.path'] ?? $url->getPath() ?: '/';
$this->domain = $app->config('cookie.domain'); $this->domain = $config['cookie.domain'];
$this->secure = $app->config('cookie.secure', Arr::get($url, 'scheme') === 'https'); $this->secure = $config['cookie.secure'] ?? $url->getScheme() === 'https';
$this->samesite = $app->config('cookie.samesite'); $this->samesite = $config['cookie.samesite'];
} }
/** /**

View File

@ -11,7 +11,7 @@ namespace Flarum\Update\Controller;
use Exception; use Exception;
use Flarum\Database\Console\MigrateCommand; use Flarum\Database\Console\MigrateCommand;
use Flarum\Foundation\Application; use Flarum\Foundation\Config;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Laminas\Diactoros\Response; use Laminas\Diactoros\Response;
use Laminas\Diactoros\Response\HtmlResponse; use Laminas\Diactoros\Response\HtmlResponse;
@ -26,18 +26,18 @@ class UpdateController implements RequestHandlerInterface
protected $command; protected $command;
/** /**
* @var Application * @var Config
*/ */
protected $app; protected $config;
/** /**
* @param MigrateCommand $command * @param MigrateCommand $command
* @param Application $app * @param Config $config
*/ */
public function __construct(MigrateCommand $command, Application $app) public function __construct(MigrateCommand $command, Config $config)
{ {
$this->command = $command; $this->command = $command;
$this->app = $app; $this->config = $config;
} }
/** /**
@ -48,7 +48,7 @@ class UpdateController implements RequestHandlerInterface
{ {
$input = $request->getParsedBody(); $input = $request->getParsedBody();
if (Arr::get($input, 'databasePassword') !== $this->app->config('database.password')) { if (Arr::get($input, 'databasePassword') !== $this->config['database.password']) {
return new HtmlResponse('Incorrect database password.', 500); return new HtmlResponse('Incorrect database password.', 500);
} }

View File

@ -10,6 +10,7 @@
namespace Flarum\Tests\integration; namespace Flarum\Tests\integration;
use Flarum\Extend\ExtenderInterface; use Flarum\Extend\ExtenderInterface;
use Flarum\Foundation\Config;
use Flarum\Foundation\InstalledSite; use Flarum\Foundation\InstalledSite;
use Flarum\Foundation\Paths; use Flarum\Foundation\Paths;
use Illuminate\Database\ConnectionInterface; use Illuminate\Database\ConnectionInterface;
@ -40,7 +41,7 @@ abstract class TestCase extends \PHPUnit\Framework\TestCase
'public' => __DIR__.'/tmp/public', 'public' => __DIR__.'/tmp/public',
'storage' => __DIR__.'/tmp/storage', 'storage' => __DIR__.'/tmp/storage',
]), ]),
include __DIR__.'/tmp/config.php' new Config(include __DIR__.'/tmp/config.php')
); );
$site->extendWith($this->extenders); $site->extendWith($this->extenders);

View File

@ -0,0 +1,155 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Tests\unit\Foundation;
use Flarum\Foundation\Config;
use Flarum\Tests\unit\TestCase;
use InvalidArgumentException;
use RuntimeException;
class ConfigTest extends TestCase
{
/** @test */
public function it_complains_when_base_url_is_missing()
{
$this->expectException(InvalidArgumentException::class);
new Config([]);
}
/** @test */
public function it_wraps_base_url_in_value_object()
{
$config = new Config([
'url' => 'https://flarum.local/myforum/',
]);
$url = $config->url();
$this->assertEquals('https', $url->getScheme());
$this->assertEquals('/myforum', $url->getPath()); // Note that trailing slashes are removed
$this->assertEquals('https://flarum.local/myforum', (string) $url);
}
/** @test */
public function it_has_a_helper_for_debug_mode()
{
$config = new Config([
'url' => 'https://flarum.local',
'debug' => false,
]);
$this->assertFalse($config->inDebugMode());
$config = new Config([
'url' => 'https://flarum.local',
'debug' => true,
]);
$this->assertTrue($config->inDebugMode());
}
/** @test */
public function it_turns_off_debug_mode_by_default()
{
$config = new Config([
'url' => 'https://flarum.local',
]);
$this->assertFalse($config->inDebugMode());
}
/** @test */
public function it_has_a_helper_for_maintenance_mode()
{
$config = new Config([
'url' => 'https://flarum.local',
'offline' => false,
]);
$this->assertFalse($config->inMaintenanceMode());
$config = new Config([
'url' => 'https://flarum.local',
'offline' => true,
]);
$this->assertTrue($config->inMaintenanceMode());
}
/** @test */
public function it_turns_off_maintenance_mode_by_default()
{
$config = new Config([
'url' => 'https://flarum.local',
]);
$this->assertFalse($config->inMaintenanceMode());
}
/** @test */
public function it_exposes_additional_keys_via_array_access()
{
$config = new Config([
'url' => 'https://flarum.local',
'custom_a' => 'b',
]);
$this->assertEquals('b', $config['custom_a']);
}
/** @test */
public function it_exposes_nested_keys_via_dot_syntax()
{
$config = new Config([
'url' => 'https://flarum.local',
'nested' => [
'first' => '1',
'second' => '2',
],
]);
$this->assertEquals('1', $config['nested.first']);
$this->assertEquals('2', $config['nested.second']);
}
/** @test */
public function it_does_not_allow_mutation_via_array_access()
{
$config = new Config([
'url' => 'https://flarum.local',
'custom_a' => 'b',
]);
try {
$config['custom_a'] = 'c';
} catch (RuntimeException $_) {
}
// Ensure the value was not changed
$this->assertEquals('b', $config['custom_a']);
}
/** @test */
public function it_does_not_allow_removal_via_array_access()
{
$config = new Config([
'url' => 'https://flarum.local',
'custom_a' => 'b',
]);
try {
unset($config['custom_a']);
} catch (RuntimeException $_) {
}
// Ensure the value was not changed
$this->assertEquals('b', $config['custom_a']);
}
}