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 池对象和一个 LayeredLoader 实例(或任何 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 而不是使用问题跟踪器。

鸣谢

许可

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