飞行 / 配置
对象配置选项的实现
Requires (Dev)
- phpunit/phpunit: ^5.7
README
概述
本包提供了简单、快速且灵活的对象配置管理实现。
对于对象来说,有一些配置选项影响其行为是很常见的任务。通常,此功能是实现的
或者以上方式的混合。
第一种方式是最常见的,但它有一些缺点
- 难以在多个对象之间传递多个属性的更改
- 难以实现对对象配置的临时更改以进行单个方法调用
本包实现了第二种对象配置方式——通过访问对象配置选项。
亮点
- 支持由类定义的任意配置选项
- 在使用之前始终验证配置值以确保配置始终有效
- 完全支持给定部分配置的扩展
- 完全支持配置继承以及向嵌套类扩展
- 既实现了作为抽象基类继承,也实现了作为独立类单独使用
- 提供钩子以在配置更改时实现自定义逻辑
基本用法
包提供了两种使用其功能的方式——基类继承您的类以及独立实现以在任意类中使用。
使用基类实现
使用基类实现的最小要求是定义类配置选项。
配置选项定义
配置选项定义是通过覆盖initConfig()
方法实现的。覆盖应按照方法本身描述的方式进行,以确保配置继承可以正常工作。
假设我们正在配置某种基于文件的缓存提供程序
use Flying\Config\AbstractConfig; class MyCache extends AbstractConfig { protected function initConfig() { parent::initConfig(); $this->mergeConfig([ 'enabled' => true, // Boolean value 'path' => null, // Path to directory where to store files, no path is configured by default 'prefix' => null, // Prefix for cache entries (string or null) 'hash_method' => 'md5', // Hash method to use for cache keys generation, accepted values are 'md5' and 'sha1' 'directory_depth' => 1, // Depth of hashed directories structure (integer greater then 0) 'lifetime' => 3600, // Cache entry lifetime in seconds ]); } }
到此为止,我们就可以使用定义的配置选项了。我们可以获取一些配置值
if ($this->getConfig('enabled')) { // Cache is enabled, calculate hash of cache key $method = $this->getConfig('hash_method'); $hash = $method($key); // Proceed with cache entry ... }
或逐个设置它们
$cache->setConfig('enabled', true);
或同时设置多个值
$cache->setConfig([ 'path' => realpath(__DIR__ . '/../../cache'), 'prefix' => 'my_', ]);
但在此阶段,我们无法确定我们的配置在任何时候都是有效的。为了实现这一点,有必要实现配置验证器。
重要:从2.0.0版本开始,只能将标量和数组配置选项传递给initConfig()
,因为配置初始化结果现在被缓存以提高性能。将任何类型的动态值(例如对象数组)传递给initConfig()
可能会引起不受欢迎的副作用,但出于性能原因并未控制。
配置验证
要使配置有效,我们需要实现另一个方法:validateConfig()
。此方法接收配置选项的名称和值,并应决定是否可以通过这种方式更改配置选项,以及可选地规范化给定值。例如,我们的示例配置的验证器可能如下所示
protected function validateConfig($name, &$value) { switch ($name) { case 'enabled': $value = (boolean)$value; break; case 'path': if ($value !== null) { // Check if path is available if (!is_dir($value)) { throw new \InvalidArgumentException('Unavailable path to cache directory: ' . $value); } } break; case 'prefix': if (strlen($value)) { $value = rtrim($value, '_') . '_'; } else { $value = null; } break; case 'hash_method': if (is_string($value)) { if (!in_array($value, ['md5', 'sha1'])) { trigger_error('Invalid hash method: ' . $value, E_USER_WARNING); return false; } } else { trigger_error('Hash method must be a string', E_USER_WARNING); return false; } break; case 'directory_depth': $value = max(min((int)$value, 3), 1); break; case 'lifetime': if ($value!==null) { $value = max(min((int)$value, 86400), 1); } break; default: return parent::validateConfig($name, $value); break; } return true; }
实施此方法后,我们可以确信我们的配置在任何时候都是有效的。
懒加载配置初始化
自v1.1.0版本起,可以进行懒加载(按需)初始化配置选项。这在配置选项包含一些默认情况下难以初始化的信息(例如对象实例)时特别有帮助。要懒加载初始化某些配置选项,需要将其默认值设置为null
,并在lazyConfigInit()
方法中实现选项初始化。例如
protected function initConfig() { parent::initConfig(); $this->mergeConfig([ 'cache' => null, // Cache object instance will be here ]); } protected function lazyConfigInit($name) { switch($name) { case 'cache': return new My\Cache(); break; default: return parent::lazyConfigInit($name); break; } }
代码后面的部分
// Create instance of object with 'cache' configuration option $object = new My\Object(); // 'cache' option now remains null internally $cache = $object->getConfig('cache'); // $cache contains instance of My\Cache initialized by request $cache->save();
如果您的对象配置计划以懒加载的方式完全初始化,则可以简化配置初始化
protected function initConfig() { parent::initConfig(); $this->mergeConfig([ 'cache', // No values are required 'loader', 'some_service', ]); }
部分配置扩展
有时可能需要以不同的配置运行某些对象的方法。这可以通过将额外的配置选项作为方法本身的附加参数传递来实现。但在一般情况下,我们无法确定给定的配置选项集是否完整(而不仅仅是1-2个选项),并且我们对其有效性一无所知。这个包提供了很好的支持来处理这种情况。让我们看看一个例子
/** * Save cache entry * * @param string $key Cache entry key * @param mixed $contents Cache entry contents * @param array $config OPTIONAL Additional configuration options * @return boolean */ public function save($key, $contents, $config = null) { // At this moment we can't tell anything about contents of $config argument $config = $this->getConfig($config); // And now we can be sure that $config stores complete set // of object's configuration options with valid values! // We can safely keep going with logic of this method ... }
您可以看到这多么简单。仅用一行代码,我们就得到了
- 对象配置的完整选项集
- 确保此变量中所有配置选项都是有效的
- 确保对配置选项的所有修改都已验证并应用
并且实际的配置对象没有被修改,因此我们可以在保持实际配置原始的同时,使用我们的本地可能已修改的配置副本。
配置修改
有时可能需要修改对象配置的本地副本,并以安全的方式进行,以确保我们的配置仍然是完整和有效的。这可以通过使用modifyConfig()
方法来实现。它接受配置选项数组,将其应用于给定的修改,并返回结果配置。
各种配置来源
有时可能需要从另一个对象导入对象配置。这可以通过将此类对象(例如Zend\Config\Config
或类似对象)直接传递给setConfig()
方法来实现。
配置继承和扩展
通常需要将配置选项的列表扩展到子类中。只要以这些方法(或本文档)中描述的方式实现initConfig()
和validateConfig()
方法,此功能将自动实现。
配置更改跟踪
在对象配置更改时执行一些额外任务通常很有必要。这可以通过重写onConfigChange()
方法来实现。每当配置选项更改时都会调用此方法。配置选项的名称和新的值作为此方法的参数提供。实现此方法的良好做法是遵循与validateConfig()
方法相同的结构,以确保在继承类的情况下应用程序逻辑的正确工作。
独立实现的用法
独立实现由Flying\Config\ObjectConfig
类提供,并提供相同的API和基本实现。完整的用法示例可以在相应的测试类中看到。
从图中可以看出,对于此类对象,实现Flying\Config\ConfigurableInterface
通常是好主意,并将所有方法代理到内部配置对象。在这种情况下,您的具有独立配置功能的对象在功能上将与继承自Flying\Config\AbstractConfig
的对象相同。
实现细节
出于灵活性和性能的考虑,配置选项以数组的形式存储和传递。为了区分对象的配置和常规数组,它们具有额外的__config__
条目(在Flying\Config\ConfigurableInterface
中定义)。
在例如迭代配置选项列表时,应考虑此条目的存在。
$config = $this->getConfig(); foreach($config as $key => $value) { if (\Flying\Config\ConfigurableInterface::CLASS_ID_KEY === $key) { continue; } // ... }
如果您需要避免获取此额外条目,可以将false
作为getConfig()
方法的第二个参数传递(仅在2.x版本中)。然而,如果您计划将此配置选项传递到其他地方,通常不建议删除此条目,因为这将在下次访问时导致配置重新验证,可能造成轻微的性能损失。出于性能考虑的另一个决定是:包含配置ID键的数组自动被视为有效且不会重新验证。您有责任不要手动修改配置数组,您需要使用modifyConfig()
方法。