From ba493a90c1b4e82ca6a1a6affc55cb13b732a255 Mon Sep 17 00:00:00 2001 From: Sami Mazouz Date: Sun, 31 Oct 2021 21:09:06 +0100 Subject: [PATCH] feat: Default Settings Extender (#3127) Co-authored-by: Alexander Skvortsov <38059171+askvortsov1@users.noreply.github.com> --- framework/core/src/Database/Migration.php | 4 ++ framework/core/src/Extend/Settings.php | 32 ++++++++++- .../Settings/DefaultSettingsRepository.php | 47 +++++++++++++++ .../Settings/SettingsRepositoryInterface.php | 7 +++ .../src/Settings/SettingsServiceProvider.php | 16 ++++-- .../integration/extenders/SettingsTest.php | 57 +++++++++++++++++++ 6 files changed, 158 insertions(+), 5 deletions(-) create mode 100644 framework/core/src/Settings/DefaultSettingsRepository.php diff --git a/framework/core/src/Database/Migration.php b/framework/core/src/Database/Migration.php index 410e05671..1fb0bcb2f 100644 --- a/framework/core/src/Database/Migration.php +++ b/framework/core/src/Database/Migration.php @@ -9,6 +9,7 @@ namespace Flarum\Database; +use Flarum\Extend\Settings; use Flarum\Settings\DatabaseSettingsRepository; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Builder; @@ -120,6 +121,9 @@ abstract class Migration /** * Add default values for config values. + * + * @deprecated Use the Settings extender's `default` method instead to register settings. + * @see Settings::default() */ public static function addSettings(array $defaults) { diff --git a/framework/core/src/Extend/Settings.php b/framework/core/src/Extend/Settings.php index e100e94b0..24e1689a1 100644 --- a/framework/core/src/Extend/Settings.php +++ b/framework/core/src/Extend/Settings.php @@ -15,10 +15,12 @@ use Flarum\Extension\Extension; use Flarum\Foundation\ContainerUtil; use Flarum\Settings\SettingsRepositoryInterface; use Illuminate\Contracts\Container\Container; +use Illuminate\Support\Collection; class Settings implements ExtenderInterface { private $settings = []; + private $defaults = []; /** * Serialize a setting value to the ForumSerializer attributes. @@ -33,7 +35,8 @@ class Settings implements ExtenderInterface * The callable should return: * - mixed $value: The modified value. * - * @param mixed $default: Optional default serialized value. Will be run through the optional callback. + * @todo remove $default in 2.0 + * @param mixed $default: Deprecated optional default serialized value. Will be run through the optional callback. * @return self */ public function serializeToForum(string $attributeName, string $key, $callback = null, $default = null): self @@ -43,8 +46,35 @@ class Settings implements ExtenderInterface return $this; } + /** + * Set a default value for a setting. + * Replaces inserting the default value with a migration. + * + * @param string $key: The setting key, must be unique. Namespace it with the extension ID (example: 'my-extension-id.setting_key'). + * @param mixed $value: The setting value. + * @return self + */ + public function default(string $key, $value): self + { + $this->defaults[$key] = $value; + + return $this; + } + public function extend(Container $container, Extension $extension = null) { + if (! empty($this->defaults)) { + $container->extend('flarum.settings.default', function (Collection $defaults) { + foreach ($this->defaults as $key => $value) { + if ($defaults->has($key)) { + throw new \RuntimeException("Cannot modify immutable default setting $key."); + } + + $defaults->put($key, $value); + } + }); + } + if (! empty($this->settings)) { AbstractSerializer::addAttributeMutator( ForumSerializer::class, diff --git a/framework/core/src/Settings/DefaultSettingsRepository.php b/framework/core/src/Settings/DefaultSettingsRepository.php new file mode 100644 index 000000000..ad5300cf0 --- /dev/null +++ b/framework/core/src/Settings/DefaultSettingsRepository.php @@ -0,0 +1,47 @@ +inner = $inner; + $this->defaults = $defaults; + } + + public function get($key, $default = null) + { + // Global default overrules local default because local default is deprecated, + // and will be removed in 2.0 + return $this->inner->get($key, $this->defaults->get($key, $default)); + } + + public function set($key, $value) + { + $this->inner->set($key, $value); + } + + public function delete($keyLike) + { + $this->inner->delete($keyLike); + } + + public function all(): array + { + return array_merge($this->defaults->toArray(), $this->inner->all()); + } +} diff --git a/framework/core/src/Settings/SettingsRepositoryInterface.php b/framework/core/src/Settings/SettingsRepositoryInterface.php index 8bb3d7ab2..b96a95cde 100644 --- a/framework/core/src/Settings/SettingsRepositoryInterface.php +++ b/framework/core/src/Settings/SettingsRepositoryInterface.php @@ -13,6 +13,13 @@ interface SettingsRepositoryInterface { public function all(): array; + /** + * @todo remove $default in 2.0 + * + * @param $key + * @param mixed $default: Deprecated + * @return mixed + */ public function get($key, $default = null); public function set($key, $value); diff --git a/framework/core/src/Settings/SettingsServiceProvider.php b/framework/core/src/Settings/SettingsServiceProvider.php index 4bfb8f466..bb2090b03 100644 --- a/framework/core/src/Settings/SettingsServiceProvider.php +++ b/framework/core/src/Settings/SettingsServiceProvider.php @@ -12,6 +12,7 @@ namespace Flarum\Settings; use Flarum\Foundation\AbstractServiceProvider; use Illuminate\Contracts\Container\Container; use Illuminate\Database\ConnectionInterface; +use Illuminate\Support\Collection; class SettingsServiceProvider extends AbstractServiceProvider { @@ -20,11 +21,18 @@ class SettingsServiceProvider extends AbstractServiceProvider */ public function register() { + $this->container->singleton('flarum.settings.default', function () { + return new Collection(); + }); + $this->container->singleton(SettingsRepositoryInterface::class, function (Container $container) { - return new MemoryCacheSettingsRepository( - new DatabaseSettingsRepository( - $container->make(ConnectionInterface::class) - ) + return new DefaultSettingsRepository( + new MemoryCacheSettingsRepository( + new DatabaseSettingsRepository( + $container->make(ConnectionInterface::class) + ) + ), + $container->make('flarum.settings.default') ); }); diff --git a/framework/core/tests/integration/extenders/SettingsTest.php b/framework/core/tests/integration/extenders/SettingsTest.php index 437707f9f..454fd370f 100644 --- a/framework/core/tests/integration/extenders/SettingsTest.php +++ b/framework/core/tests/integration/extenders/SettingsTest.php @@ -163,6 +163,63 @@ class SettingsTest extends TestCase $this->assertArrayHasKey('customPrefix.noCustomSetting', $payload['data']['attributes']); $this->assertEquals('customDefaultModified2', $payload['data']['attributes']['customPrefix.noCustomSetting']); } + + /** + * @test + */ + public function custom_setting_default_prioritizes_extender() + { + $this->extend( + (new Extend\Settings()) + ->serializeToForum('customPrefix.unavailableCustomSetting3', 'custom-prefix.unavailable_custom_setting3') + ->default('custom-prefix.unavailable_custom_setting3', 'extenderDefault') + ); + + $value = $this->app() + ->getContainer() + ->make('flarum.settings') + ->get('custom-prefix.unavailable_custom_setting3', 'defaultParameterValue'); + + $this->assertEquals('extenderDefault', $value); + } + + /** + * @test + */ + public function custom_setting_default_falls_back_to_parameter() + { + $this->extend( + (new Extend\Settings()) + ->serializeToForum('customPrefix.unavailableCustomSetting4', 'custom-prefix.unavailable_custom_setting4') + ); + + $value = $this->app() + ->getContainer() + ->make('flarum.settings') + ->get('custom-prefix.unavailable_custom_setting4', 'defaultParameterValue'); + + $this->assertEquals('defaultParameterValue', $value); + } + + /** + * @test + */ + public function null_custom_setting_returns_null() + { + $this->setting('custom-prefix.custom_null_setting', null); + + $this->extend( + (new Extend\Settings()) + ->default('custom-prefix.custom_null_setting', 'extenderDefault') + ); + + $value = $this->app() + ->getContainer() + ->make('flarum.settings') + ->get('custom-prefix.custom_null_setting'); + + $this->assertEquals(null, $value); + } } class CustomInvokableClass