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 实例,如果定义了,所有五个属性都将从环境变量中填充。现在可以在任何地方使用该对象,传递给构造函数,等等。因为它的属性都是 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或更高版本。有关更多信息,请参阅许可文件