飞行/配置

对象配置选项的实现

3.0.3 2017-11-08 18:22 UTC

This package is auto-updated.

Last update: 2024-09-21 02:23:54 UTC


README

Build Status Scrutinizer Code Quality Code Coverage

概述

本包提供了简单、快速且灵活的对象配置管理实现。

对于对象来说,有一些配置选项影响其行为是很常见的任务。通常,此功能是实现的

  • 作为一组可配置属性,每个属性都有对应的getter/setter对(示例
  • 包含配置选项和相应访问器的数组
  • 使用外部的配置类

或者以上方式的混合。

第一种方式是最常见的,但它有一些缺点

  • 难以在多个对象之间传递多个属性的更改
  • 难以实现对对象配置的临时更改以进行单个方法调用

本包实现了第二种对象配置方式——通过访问对象配置选项。

亮点

  • 支持由类定义的任意配置选项
  • 在使用之前始终验证配置值以确保配置始终有效
  • 完全支持给定部分配置的扩展
  • 完全支持配置继承以及向嵌套类扩展
  • 既实现了作为抽象基类继承,也实现了作为独立类单独使用
  • 提供钩子以在配置更改时实现自定义逻辑

基本用法

包提供了两种使用其功能的方式——基类继承您的类以及独立实现以在任意类中使用。

使用基类实现

使用基类实现的最小要求是定义类配置选项。

配置选项定义

配置选项定义是通过覆盖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()方法。