crell/config

任何 PHP 项目的快速、类型驱动的配置加载系统。

资助包维护!
Crell

0.2.0 2024-07-26 16:25 UTC

This package is auto-updated.

Last update: 2024-08-27 17:11:47 UTC


README

Latest Version on Packagist Software License Total Downloads

Config Loader 就是它所说的那样:一个简单、快速但强大的配置加载系统,适用于任何框架。

它的工作原理

Config Loader 基于 "配置对象"。配置对象只是一个普通的 PHP 类。每个配置对象完全由一个 PHP 类定义,每个属性都是一个配置值。这意味着每个配置值的名称、类型和默认值都由普通的 PHP 代码定义和认可。如果某个值是必需的,则没有设置默认值。如果是可选的,默认值直接在代码中指定。虽然系统中没有强制要求,但强烈建议将配置类设置为 readonly

配置对象可以从不同的来源以 "层" 的形式进行填充。通常,这可以是磁盘上的文件,格式随意(YAML、PHP 等)。多层允许覆盖单个配置属性,例如,有一个基础配置然后为 devprod 环境进行修改。

由于每个配置对象都是自己的类,因此它可以无缝地与依赖注入容器和测试集成。(请参阅下面的 DI 部分。)

让我们看看一个例子。

YAML 配置示例

注意:要使用 YAML 配置文件,您需要安装 symfony/yaml

use Crell\Config\LayeredLoader;
use Crell\Config\YamlFileSource;

class EditorSettings
{
    public function __construct(
        public readonly string $color,
        public readonly string $bgcolor,
        public readonly int $fontSize = 14,
    ) {}
}

$loader = new LayeredLoader([
  new YamlFileSource('./config/common'),
  new YamlFileSource('./config/' . APP_ENV),
]);

$editorConfig = $loader->load(EditorSettings::class);

给定磁盘上的这些文件

# config/common/editorsettings.yaml
color: "#ccddee"
bgcolor: "#ffffff"
# config/dev/editorsettings.yaml
bgcolor: '#eeff00'
JSON 配置示例
use Crell\Config\LayeredLoader;
use Crell\Config\JsonFileSource;

class EditorSettings
{
    public function __construct(
        public readonly string $color,
        public readonly string $bgcolor,
        public readonly int $fontSize = 14,
    ) {}
}

$loader = new LayeredLoader([
  new JsonFileSource('./config/common'),
  new JsonFileSource('./config/' . APP_ENV),
]);

$editorConfig = $loader->load(EditorSettings::class);

给定磁盘上的这些文件

config/common/editorsettings.json:

{
    "color": "#ccddee",
    "bgcolor": "#ffffff"
}

config/dev/editorsettings.json:

{
  "bgcolor": "#eeff00"
}

现在,当运行此代码时,$editorConfig 将具有颜色 #ccddee、背景颜色 #ffffff 和字体大小 14(因为提供了默认值)。然而,如果在 dev 环境中运行(APP_ENVdev),则背景颜色将是 #eeff00

结果是快速轻松地加载应用程序的配置,并完全支持按环境覆盖。当然,您也可以以任何其他方式使用层。(例如,根据系统语言变化。)

您可以定义尽可能多的不同配置对象。它们都单独加载。有关如何使用依赖注入来帮助的说明,请参阅下面的部分。

源类型

默认情况下支持几种文件格式,包括 JsonFileSourceYamlFileSourcePhpFileSource,甚至 IniFileSource(为什么不呢?)。
注意:要使用 YamlFileSource,您需要安装 symfony/yaml 包。

编写其他源很简单,因为 ConfigSource 接口只有一个方法。

如果您想支持多种文件类型,甚至可以将同一目录中的多个文件类型堆叠到单个列表中读取。

自定义文件键

默认情况下,每个配置对象的标识符和文件名是其类的全名,小写,并将 \ 替换为 _。因此,My\App\Config\EditorSettings 将成为 my_app_config_editorsettings.yaml(或任何文件格式)。

这通常不是一个好的文件名。但是,您可以通过属性来自定义键,如下所示

use Crell\Config\Config;

#[Config(key: 'editor_settings')]
class EditorSettings
{
    public function __construct(
        public readonly string $color,
        public readonly string $bgcolor,
        public readonly int $fontSize = 14,
    ) {}
}

现在,这个类的文件名将是 editor_settings.yaml (以及其他类似文件)。

复杂对象

配置加载器使用功能强大且灵活的 Crell/Serde 库来填充配置对象。这意味着 Serde 的所有灵活性和功能都可以通过属性来访问。请参阅 Serde 的文档以获取所有可能选项,但尤其关注其将属性收集到子对象中、添加或删除前缀、折叠 camelCase 和 snake_case 之间的不同属性,以及更多功能。您还可以使用后加载回调方法进行验证,以实施类型系统无法处理的规则。

如果您没有将 Serde 实例传递给 LayeredLoader,则会自动创建一个。对于简单使用来说,这很好,但如果要将配置加载器集成到依赖注入容器中,则最好注入一个托管的 Serde 实例。

缓存

虽然 Serde 速度相当快,但仍然存在成本。如果您正在加载许多配置对象,那么时间可能会累积。

配置加载器附带两个缓存包装器,可以轻松地包装在 LayeredLoader 旁边。

  • Psr6CacheConfigLoader - 向其提供配置好的 PSR-6 池对象和一个 LayerdLoader 实例(或任何 ConfigLoader 对象),并且它将在加载每个配置对象时自动透明地缓存。
  • SerializedFiesystemCache - PSR-6 包装器的限制是您需要已经启动了您的缓存后端,而配置通常在请求的早期加载。相反,您可以使用文件系统缓存,将每个对象作为 PHP 序列化值保存到磁盘。它只需要一个路径。这是可能的最快选项,并且对于大多数配置都推荐使用。

注意:请确保存储文件缓存的目录不是公开可访问且安全性很高。反序列化 PHP 对象速度快,但也可能是一个潜在的安全漏洞。绝对不要允许除了缓存包装器之外的其他任何内容写入该目录。

示例

YAML 配置示例

注意:要使用 YAML 配置文件,您需要安装 symfony/yaml

use Crell\Config\LayeredLoader;
use Crell\Config\YamlFileSource;
use Crell\Config\SerializedFilesystemCache;

$loader = new LayeredLoader([
  new YamlFileSource('./config/common'),
  new YamlFileSource('./config/' . APP_ENV),
]);

$cachedLoader = new SerializedFilesytemCache($loader, '/path/to/cache/dir');

$cachedLoader->load(EditorSettings::class);
JSON 配置示例
use Crell\Config\LayeredLoader;
use Crell\Config\JsonFileSource;
use Crell\Config\SerializedFilesystemCache;

$loader = new LayeredLoader([
  new JsonFileSource('./config/common'),
  new JsonFileSource('./config/' . APP_ENV),
]);

$cachedLoader = new SerializedFilesytemCache($loader, '/path/to/cache/dir');

$cachedLoader->load(EditorSettings::class);

依赖注入

配置加载器优化的目标是集成到依赖注入容器中。具体来说,它可以作为一个工厂来生产每种配置类型的对象,然后可以将这些对象暴露给容器的自动装配功能。

考虑以下服务类

class EditorForm
{
    public function __construct(
        private EditorSettings $settings,
    ) {}
    
    public function renderForm(): string
    {
        // Do stuff here.
        $this->settings->color;
        
        ...
    }
}

它依赖于一个 EditorSettings 实例。它如何获取它,没有人关心。但它可以依赖于 PHP 提供的所有关于类型安全、值已定义、IDE 中的自动完成等的保证。

您现在可以在测试中轻松测试该服务,通过创建自己的 EditorSettings 实例并将其传递进去

class EditorFormTest extends TestCase
{
    #[Test]
    public function some_test(): void
    {
        $settings = new EditorSettings(color: '#fff', bgcolor: '#000');
        
        $subject = new EditorForm($settings);
        
        // Make various assertions.
    }
}

对于运行中的应用程序,您可以将每个配置对象注册为服务,按其类名键入,并定义它为对配置加载器服务的工厂调用进行加载。现在,容器将自动创建、缓存每个配置对象,并在需要时将其注入到需要它的服务中,就像任何其他服务一样。

例如,在 Laravel 中,您可以这样做

namespace App\Providers;
 
use Crell\Config\ConfigLoader;
use Crell\Config\LayeredLoader;
use Crell\Config\PhpFileSource;
use Crell\Config\SerializedFilesystemCache;
use Crell\Serde\Serde;
use Crell\Serde\SerdeCommon;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\ServiceProvider;
 
class ConfigServiceProvider extends ServiceProvider
{
    public $singletons = [
        // Wire up Serde first.  See its documentation for more
        // robust ways to configure it.
        Serde::class => SerdeCommon::class,
    ];
    
    public function register(): void
    {
        // Set up some sources.
        $this->app->singleton('base_config', fn(Application $app) 
            => new PhpFileSource('config/base')
        );
        $this->app->singleton('env_config', fn(Application $app) 
            => new PhpFileSource('config/'. APP_ENV)
        );
        
        // Register the loader, and wrap it in a cache.
        $this->app->singleton(LayeredLoader::class, fn(Application $app)
            => new LayeredLoader(
                [$app['base_config'], $app['env_config']],
                $app[Serde::class],
            )
        );
        $this->app->singleton(ConfigLoader::class, fn(Application $app)
            => new SerializedFilesystemCache($app[LayeredLoader::class], 'cache/config')
        );
        
        // Now register the config objects.
        // You could also use a compiler pass to discover these from disk and
        // auto-register them, if your framework has that ability.
        $this->app->singleton(EditorSettings::class, fn(Application $app)
            => $app[ConfigLoader::class]->load(EditorSettings::class);
    }
}

现在,当第一次加载需要 EditorSettings 的服务(如 EditorForm)时,将创建、填充、缓存在磁盘上,并在容器中作为单例缓存 EditorSettings 配置对象服务。一切都是透明的!任何需要 EditorSettings 的服务都可以简单地声明一个构造函数依赖项,然后完成。在后续加载中,将从磁盘加载缓存的版本,以获得更快的速度。

贡献

请参阅 CONTRIBUTINGCODE_OF_CONDUCT 了解详细信息。

安全

如果您发现任何与安全相关的问题,请通过电子邮件larry at garfieldtech dot com联系,而不是使用问题跟踪器。

致谢

许可证

较宽松的GPL版本3或更高版本。请参阅许可证文件获取更多信息。