crell / envmapper
从环境变量到类对象的简单、快速映射器。
Requires
- php: ~8.1
Requires (Dev)
- phpbench/phpbench: ^1.2
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^10.0
Suggests
- crell/serde: A full and robust serialization library. It can do much more advanced manipulation when reading in env vars if EnvMapper is insufficient for your needs.
This package is auto-updated.
Last update: 2024-08-30 01:39:43 UTC
README
读取环境变量是大多数应用程序的常见部分。然而,这通常是以一种临时和不安全的方式完成的,通过调用 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
风格,这是环境变量通常使用的风格。然后它会寻找具有该名称的环境变量并将其分配给该属性。这意味着您可以使用 lowerCamel
或 snake_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);
类型强制
环境变量始终是字符串,但您可能知道它们应该是 int
或 float
。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文档以了解其所有选项。
贡献
请参阅 CONTRIBUTING 和 CODE_OF_CONDUCT 了解详细信息。
安全性
如果您发现任何安全相关的问题,请通过电子邮件 larry at garfieldtech dot com 而不是使用问题跟踪器。
致谢
许可
Lesser GPL版本3或更高版本。请参阅 许可文件 了解更多信息。