crell/envmapper

从环境变量到类对象的简单、快速映射器。

资助包维护!
Crell

1.0.3 2023-08-25 22:19 UTC

This package is auto-updated.

Last update: 2024-08-30 01:39:43 UTC


README

Latest Version on Packagist Software License Total Downloads

读取环境变量是大多数应用程序的常见部分。然而,这通常是以一种临时和不安全的方式完成的,通过调用 getenv() 或从代码中的任意位置读取 $_ENV。这意味着错误处理、缺失值处理、默认值等散布在代码库中。

这个库改变了这一点。它允许您以极快的速度将环境变量映射到任意类对象,这使得可以使用类定义本身进行默认处理、类型安全等。然后可以将该类注册到依赖注入容器中,使其对任何服务自动可用。

用法

EnvMapper 几乎没有配置。一切只是类。

// Define the class.
class DbSettings
{
    public function __construct(
        // Loads the DB_USER env var.
        public readonly string $dbUser,
        // Loads the DB_PASS env var.
        public readonly string $dbPass,
        // Loads the DB_HOST env var.
        public readonly string $dbHost,
        // Loads the DB_PORT env var.
        public readonly int $dbPort,
        // Loads the DB_NAME env var.
        public readonly string $dbName,
    ) {}
}

$mapper = new Crell\EnvMapper\EnvMapper();

$db = $mapper->map(DbSettings::class);

$db 现在是一个 DbSettings 实例,所有五个属性都从环境变量中填充(如果已定义)。该对象现在可以用于任何地方,传递给构造函数,或 whatever。因为它的属性都是 readonly,所以您可以放心,使用此对象的服务无法修改它。

您可以使用任何想要的类。唯一重要的是定义的属性(通过构造函数提升定义或未定义)。属性可以是您喜欢的任何可见性,并且您可以包括任何您想要的方法。

名称映射和默认值

EnvMapper 将属性名称转换为 UPPER_CASE 风格,这是环境变量通常使用的风格。然后它会寻找具有该名称的环境变量并将其分配给该属性。这意味着您可以使用 lowerCamelsnake_case 为对象属性命名。两者都可以正常工作。

如果找不到环境变量,但属性在类定义中设置了默认值,则将使用该默认值。如果没有默认值,则将其保留为未初始化。

或者,您可以在 map() 调用中设置 requireValues: true。如果设置了 requireValues,则缺失的属性将抛出 MissingEnvValue 异常。

class MissingStuff
{
    public function __construct(
        public readonly string $notSet,
    ) {}
}

// This will throw a MissingEnvValue exception unless there is a NOT_SET env var defined.
$mapper->map(MissingStuff::class, requireValues: true);

类型强制

环境变量始终是字符串,但您可能知道它们应该是 intfloat。EnvMapper 将自动将类似于整数的值(如 "5" 或 "42")转换为整数,并将类似于浮点数的值(如 "3.14")转换为浮点数,以便它们可以安全地分配到对象的类型属性上。

如果属性的类型为 bool,则值 "1"、"true"、"yes" 和 "on"(任何大小写)将评估为 true。任何其他内容都将评估为 false

如果无法分配值(例如,如果将 DB_PORT 环境变量设置为 "any"),则将抛出 TypeMismatch 异常。

dot-env 兼容性

EnvMapper 默认从 $_ENV 读取值。如果您使用的是将 .env 文件读取到环境的库,只要它填充了 $_ENV,它应该与 EnvMapper 工作得很好。EnvMapper 不使用 getenv(),因为它要慢得多。

注意,在 PHP 中,您需要将 variables_order 设置为包括 E(环境),以便填充 $_ENV。如果不这样做,则 $_ENV 将为空。如果您无法配置您的服务器以填充 $_ENV,并且您无法切换到非损坏的服务器,则作为备用方案,您可以像这样将 getenv() 的返回值传递给 source 参数:

$mapper->map(Environment::class, source: getenv());

常见模式

使用DI容器进行注册

使用 EnvMapper 的推荐方法是将其连接到依赖注入容器中,最好是支持自动连接的容器。例如,在Laravel Service Provider中,您可以这样做

namespace App\Providers;
 
use App\Environment;
use Crell\EnvMapper\EnvMapper;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\ServiceProvider;
 
class EnvMapperServiceProvider extends ServiceProvider
{
    // The EnvMapper has no constructor arguments, so registering it is simple.
    public $singletons = [
        EnvMapper::class => EnvMapper::class,
    ];
    
    public function register(): void
    {
        // When the Environment class is requested, it will be loaded lazily out of the env vars by the mapper.
        // Because it's a singleton, the object will be automatically cached.
        $this->app->singleton(Environment::class, fn(Application $app) => $app[EnvMapper::class]->map(Environment::class));
    }
}

在Symfony中,您可以在 services.yaml 中实现相同的配置

services:
    Crell\EnvMapper\EnvMapper: ~

    App\Environment:
      factory:   ['@Crell\EnvMapper\EnvMapper', 'map']
      arguments: ['App\Environment']

现在,任何服务都可以简单地声明一个类型为 Environment 的构造函数参数,容器将自动实例化和注入对象。

测试

使用集中式环境变量映射器的主要原因是使测试更容易。直接从每个服务中读取环境变量是一个全局依赖项,这使得测试更加困难。相反,将专门的环境类作为可注入的服务(如上述示例所示),意味着任何使用它的服务都可以轻松地传递一个手动创建的版本。

class AService
{
    public function __construct(private readonly AwsSettings $settings) {}
    
    // ...
}

class AServiceTest extends TestCase
{
    public function testSomething(): void
        $awsSettings = new AwsSettings(awsKey: 'fake', awsSecret: 'fake');

        $s = new Something($awsSettings);

        // ...
    }
}

多个环境对象

任何设置为但不在指定类中存在的环境变量将被忽略。这意味着轻松地加载不同变量到不同类中变得非常简单。例如

class DbSettings
{
    public function __construct(
        public readonly string $dbUser,
        public readonly string $dbPass,
        public readonly string $dbHost,
        public readonly int $dbPort,
        public readonly string $dbName,
    ) {}
}

class AwsSettings
{
    public function __construct(
        public readonly string $awsKey,
        public readonly string $awsSecret,
    ) {}
}
// Laravel version.
class EnvMapperServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->singleton(DbSettings::class, fn(Application $app) => $app[EnvMapper::class]->map(DbSettings::class));
        $this->app->singleton(AwsSettings::class, fn(Application $app) => $app[EnvMapper::class]->map(AwsSettings::class));
    }
}
# Symfony version
services:
  Crell\EnvMapper\EnvMapper: ~

  App\DbSettings:
    factory:   ['@Crell\EnvMapper\EnvMapper', 'map']
    arguments: ['App\DbSettings']

  App\AwsSettings:
    factory:   ['@Crell\EnvMapper\EnvMapper', 'map']
    arguments: ['App\AwsSettings']

高级用法

EnvMapper被设计成轻量级和快速。因此,其功能集是故意有限的。

然而,有些情况下您可能希望拥有更复杂的环境设置。例如,您可能希望更自由地重命名属性,在子对象内部嵌套相关属性,或将逗号分隔的环境变量映射到数组中。EnvMapper并不设计用于处理这些情况。

然而,它的兄弟项目 Crell/Serde 可以轻松做到。Serde是一个通用序列化库,但您可以轻松地将 $_ENV 作为数组输入以反序列化到对象中。这为您提供了所有Serde的能力,在对象反序列化过程中重命名、收集、嵌套和其他转换数据。基本工作流程是相同的,并且在服务容器中的注册几乎相同。

$serde = new SerdeCommon();

$env = $serde->deserialize($_ENV, from: 'array', to: Environment::class);

使用Serde将比使用EnvMapper慢一些,但两者仍然非常快,适用于几乎任何应用。

请参阅Serde文档以了解其所有选项。

贡献

请参阅 CONTRIBUTINGCODE_OF_CONDUCT 了解详细信息。

安全性

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

致谢

许可

Lesser GPL版本3或更高版本。请参阅 许可文件 了解更多信息。