caseyamcl / settings-manager
设置管理器
Requires
- php: ^7.3|^8.0
Requires (Dev)
- phpunit/phpunit: ^8.0|^9.5
- squizlabs/php_codesniffer: ^3.5
README
这是一个框架无关的库,为管理和存储用户可修改的设置提供抽象。设置可以存储在配置文件、数据库、外部API或其他任何地方。这个库的确定特性是围绕在运行时修改设置的假设设计的,这使得它特别适用于越来越流行的架构,如Swoole、React等。
它提供了以下功能
- 基于类的设置定义
- 能够定义多个设置提供者并按级联方式加载它们
- 能够验证和准备/转换设置值
- PSR-4和PSR-12合规;100%单元测试覆盖率
什么是设置?
这个库对那些明确区分配置值(由管理员设置)和设置(在应用中可用,用户可以在运行时更改)的项目很有用。
概念
设置定义只是一个实现SettingDefinition
接口的PHP类。设置定义具有以下属性
- 一个名称(例如机器名称/别名)
- 显示名称
- 内部备注
- 可选的默认值
- 可选的验证/转换逻辑,用于处理传入的值
- 此值是否敏感(例如,应安全处理)
设置定义被添加到SettingDefinitionRegistry
的实例中。
设置提供者是一个服务类,它从源加载设置值。来源可以是配置文件、数据库或任何其他东西。以下面的使用部分提供了一个捆绑提供者的列表。
可以将多个提供者链接在一起,以便以级联方式加载设置值。已捆绑了几个提供者(见下文),但您可以自由地通过实现SettingProvider
接口添加自己的提供者。提供者具有与定义相似的属性
- 一个名称(例如机器名称/别名)
- 显示名称
设置值是一个对象,它存储设置值以及一些附加信息
- 设置名称
- 定义此设置的提供者名称
- 可变度 - 此设置是否可以在该提供者之后被覆盖(例如,管理员可能希望在一个配置文件中锁定设置并不允许下游提供者更改它)
安装
通过Composer
$ composer require caseyamcl/settings-manager
使用
基本使用
此库的基本使用包括两个步骤
- 定义设置定义
- 从提供者加载设置值
定义设置定义
创建设置的推荐方法是使每个设置定义成为其自己的类。虽然这不是强制性的(您可以使用任何实现SettingDefinition
的类),但这可以使事情保持简洁和简单。
为了方便,此库包括AbstractSettingDefinition
类,其中包含常用属性的常量。请参阅以下示例
use SettingsManager\Model\AbstractSettingDefinition; use SettingsManager\Exception\InvalidSettingValueException; use SettingsManager\Registry\SettingDefinitionRegistry; // 1. Create setting definition: /** * Settings must implement the SettingDefinition interface. * * For convenience, an AbstractSettingDefinition class is bundled with the library. */ class MySetting extends AbstractSettingDefinition { // Required; This is the machine name, and it is recommended that you stick to machine-friendly names (alpha-dash, underscore) public const NAME = 'my_setting'; // Required; This is the "human friendly" name for the setting public const DISPLAY_NAME = 'My Setting'; // Internal notes (optional) public const NOTES = "These are notes that are either available to all users or just admins (implementor's choice)"; // Set an optional default (may need to override the getDefault() method if complex logic is required) public const DEFAULT = null; // Indicate whether this value is sensitive or not. By default, this is set to TRUE // This is relevant if the application wants to expose some setting values to all users, while hiding other ones public const SENSITIVE = true; /** * If there is any validation for this setting, you can override the processValue() method * * Throw an InvalidSettingValueException in the case of an invalid value * * @param string $value * @return string */ public function processValue($value) { if (! is_string($value)) { $errors[] = "value must be a string"; } if ($value !== 'test') { $errors[] = "value must be equal to 'test'"; } if (! empty($errors)) { // InvalidSettingValueException allows for multiple error messages throw new InvalidSettingValueException($errors); } return $value; } } // 2. Add it to the registry: $registry = new SettingDefinitionRegistry(); $registry->add(new MySetting()); // etc. add more values...
从提供者加载设置值
设置值是从设置提供者加载的。此库中包含几个捆绑的提供者,您可以通过实现SettingsManager\Contract\SettingProvider
接口来创建自己的提供者。
在此示例中,我们使用CascadingSettingProvider
将DefaultValuesProvider
和ArrayValuesProvider
的功能结合起来
use SettingsManager\Provider\CascadingSettingProvider; use SettingsManager\Provider\DefaultValuesProvider; use SettingsManager\Provider\ArrayValuesProvider; use SettingsManager\Registry\SettingDefinitionRegistry; // Setup a registry and add settings to it... $registry = new SettingDefinitionRegistry(); $registry->add(new MySetting()); // An array of setting values $settingValues = [ 'my_setting' => 'test' ]; // Setup the provider $provider = new CascadingSettingProvider([ new DefaultValuesProvider($registry), // loads default values new ArrayValuesProvider($settingValues, $registry), // loads values from an array ]); // Get values from the provider.. $provider->findValue('my_setting'); // returns 'test' $provider->getValue('my_setting'); // returns 'test' (would throw an exception if value isn't defined) // If you want to get the `SettingValue` instance (with metadata), use // `findValueInstance` or `getValueInstance` $provider->getValueInstance('my_setting')->getValue(); $provider->findValueInstance('my_setting')->getValue(); // `getValue` throws an exception if the requested setting isn't defined $provider->getValue('non_existent_value'); // Throws UndefinedSettingException // `findValue()` returns NULL if the requested setting isn't defined $provider->findValue('non_existent_value'); // returns NULL
捆绑的提供者
基本的设置提供者捆绑在此库的SettingsManager\Provider
命名空间中
设置可变度
有时您可能希望某些设置由特定提供者“锁定”。例如,如果您希望某个设置在某个提供者加载后(例如,配置文件)不可更改,可以使用以下语法
use SettingsManager\Provider\ArrayValuesProvider; use SettingsManager\Provider\DefaultValuesProvider; use SettingsManager\Provider\SettingRepositoryProvider; use SettingsManager\Provider\CascadingSettingProvider; use SettingsManager\Registry\SettingDefinitionRegistry; use MyApp\MySettingRepository; use MyApp\SensitivePasswordSetting; // Setup a registry and add settings to it... $registry = new SettingDefinitionRegistry(); $registry->add(new SensitivePasswordSetting()); // Method #1 - Key/value pairs $values = [ 'sensitive_password' => '11111', 'another_setting' => 123, // etc.. ]; // Method #2 $values = [ 'sensitive_password' => [ 'value' => '11111', 'mutable' => false // downstream providers won't be able to override this setting ], 'another_setting' => [ 'value' => 123, 'mutable' => true // downstream providers WILL be able to override this setting ] ]; // Mix and match methods #1 and #2 $values = [ 'sensitive_password' => '11111', 'another_setting' => [ 'value' => 123, 'mutable' => true ] ]; $provider = CascadingSettingProvider::build( new DefaultValuesProvider($registry), new ArrayValuesProvider($values, $registry, 'config_file'), new SettingRepositoryProvider(new MySettingRepository()) ); $provider->getValueInstance('sensitive_password')->getProviderName(); // will always be 'config_file'
使用 SettingRepositoryProvider
创建自己的提供者实现
很可能,您希望将值存储在数据库中。为了方便,本包已将 SettingRepository
接口及其提供者 SettingRepositoryProvider
一起打包。
use SettingsManager\Contract\SettingRepository; use SettingsManager\Exception\SettingValueNotFoundException; use SettingsManager\Provider\SettingRepositoryProvider; class MySettingRepository implements SettingRepository { /** * @var MyDatabaseProvider */ private $dbConnection; /** * MySettingRepository constructor. * @param MyDatabaseProvider $dbConnection */ public function __construct(MyDatabaseProvider $dbConnection) { $this->dbConnection = $dbConnection; } /** * Find a setting value by its name or NULL if it is not found * * @param string $settingName * @return mixed|null */ public function findValue(string $settingName) { return $this->dbConnection->findValue($settingName); } /** * Get a setting value by its name or throw an exception if not found * * @param string $settingName * @return mixed * @throws SettingValueNotFoundException */ public function getValue(string $settingName) { if ($this->dbConnection->hasValue($settingName)) { return $this->findValue($settingName); } else { throw SettingValueNotFoundException::fromName($settingName); } } /** * List values * * @return iterable|mixed[] */ public function listValues(): iterable { return $this->dbConnection->listValues(); } } // Then, use the `SettingRepositoryProvider` $repository = new MySettingRepository($dbConn); $provider = new SettingRepositoryProvider($repository);
异常处理
所有异常都实现了 SettingException
接口
关于运行时环境的考虑
此库简化了Swoole或React等提供的环境,在这些环境中设置值在运行时更新。
如果您想启用此功能,请确保始终在您的服务类中注入您正在使用的任何设置提供者,并在 运行时 查找设置。
use SettingsManager\Contract\SettingProvider; class MyServiceClass { /** * @var SettingProvider */ private $settings; /** * MyServiceClass constructor. * @param SettingProvider $provider */ public function __construct(SettingProvider $provider) { $this->settings = $provider; } public function doSomethingThatRequiresLookingUpASetting(): void { // Always lookup the setting value during runtime $settingValue = $this->settings->getValue('some_setting'); // do stuff here.. } }
变更日志
有关最近更改的更多信息,请参阅变更日志。
测试
$ composer test
贡献
安全
如果您发现任何安全相关的问题,请通过电子邮件caseyamcl@gmail.com报告,而不是使用问题跟踪器。
致谢
许可证
MIT许可证(MIT)。有关更多信息,请参阅许可证文件。