gmazzap/gea

PHP 环境变量管理

0.1.1 2016-03-16 12:15 UTC

This package is auto-updated.

Last update: 2024-08-26 14:08:16 UTC


README

PHP 环境变量管理

travis-ci status codecov.io release

来自 PHP Dotenv 的分支,由 Vance Lucas 开发。

什么是 Gea

Gea 是一个通过环境变量管理应用程序配置的 PHP 库。

什么是环境变量

环境变量是可能根据应用环境(生产、开发、预发布...)变化的配置。

很多时候,这些都是 敏感 的配置,例如数据库凭证、API 密钥等。

这类配置 绝不能 放置在代码中,出于安全原因,同时也为了能够根据环境轻松更改它们。

如何存储环境变量

环境变量可以设置在运行应用程序的服务器上。这可以通过命令行、通过自动化部署工具(如 Capistrano)或从云托管服务提供的界面(如 Heroku)进行配置。

这很好,但在开发过程中这并不是特别方便。还有另一种方法:.env 文件。

.env 文件

.env 文件是一个文本文件,包含一系列环境变量,每行一个。其写法类似于 bash 语法,例如:

FOO=Bar
BAR=Baz

嵌套变量

您甚至可以使用变量来定义其他变量

BASE_PATH=/var/www/app
PUBLIC_PATH=${BASE_PATH}/public

包含空格的字符串

包含空格的字符串需要用引号括起来

TITLE="Hello World"

注释

任何以 # 开头的字符串都是注释

# Following line is super secret
PASSWORD=mypassword # Cool, isn't it?

导出

还可以在变量前添加 bash 命令 export

export PASSWORD=mypassword

环境变量与 PHP

PHP 有两个函数用于读取和写入环境变量:getenv()putenv()

然而,使用这些函数一次无法设置更多变量,因此您需要解析 .env 文件,逐行读取,处理空格、引号、注释等...

...别担心,这就是 Gea 的作用。

使用 Gea 加载 .env 文件

使用 Gea 从文件加载环境变量非常简单。

首先,我们需要使用静态 instance() 方法获取 Gea\Gea 类的实例。

$gea = Gea\Gea::instance(__DIR__);

唯一必需的参数是保存文件的文件夹路径。文件名默认为 .env,但可以通过传递名称作为第二个参数进行自定义。

然后,我们可以在获取的实例上调用 load()

$gea->load();

就是这样。文件中的所有环境变量现在都已加载。

读取环境变量

当然,可以使用 PHP 函数 getenv() 读取环境变量,但 Gea 还提供了一种 read() 方法,以及 ArrayAccess 支持。

以下行执行相同操作

$apiKey = $gea->read('APY_KEY');
$apiKey = $gea['APY_KEY'];
$apiKey = getenv('APY_KEY');

写入环境变量

如你所料,Gea 还提供了一种 write() 方法,你可以使用 ArrayAccess 语法来写入变量

$gea->write('APY_KEY', 'mysupersecretkey');
$gea['APY_KEY'] = 'mysupersecretkey';
putenv('APY_KEY=mysupersecretkey');

不可变性

环境变量本身不是不可变的。您可以随时更改它们。这对许多人来说可能看起来很正常,但实际上它会增加应用程序的复杂性。

因此,Gea致力于实现不可变性。

例如,无法多次调用load()

$gea->load(); # ok
$gea->load(); # throw an exception

这也意味着已经写入(或从.env文件加载)的环境变量不能被覆盖。

$gea->write('A_VAR', 'A value'); # ok
$gea->write('A_VAR', 'Another value'); # throw an exception

请注意,Gea不控制putenv(),因此调用该函数实际上可以覆盖变量,所以不可变行为仅在通过Gea方法访问变量时适用。

但是,如果您真的需要,有两种方法可以更改已设置的变量的值

  • 丢弃它
  • 强制刷新它

丢弃变量

可以使用Gea的discard()方法或简单地使用ArrayAccess语法中的unset()来丢弃变量。

$gea->discard('FOO');
unset($gea['FOO']);

变量被刷新后,可以设置不同的值

$gea['FOO'] = 'First Value';

echo $gea['FOO']; # print 'First Value'

unset($gea['FOO']); # Without this an exception had been thrown on next line

$gea['FOO'] = 'Second Value';

echo $gea['FOO']; # Print 'Second Value'

刷新变量

Gea有两种不同的变量刷新方式:硬刷新软刷新

软刷新

软刷新允许执行更多的连续load()调用。然而,如果在第二次调用中加载的变量与第一次加载的相同,则会抛出异常,因为不可变行为。

$gea->flush(Gea\Gea::FLUSH_SOFT);

因此,软刷新仅在所有加载的变量都已丢弃或.env文件在应用程序执行期间已更改的情况下才有用...这种情况很少见。

硬刷新

硬刷新可以看作是一种批量丢弃,实际上,它可以一次性丢弃更多变量。

$gea->flush(Gea\Gea::FLUSH_HARD, ['FOO', 'BAR', 'BAZ']);

如你所猜,flush()的第二个参数是需要丢弃的变量的数组。

这意味着我们可能希望知道哪些变量已被设置。

获取变量的名称

当变量从.env文件加载时,load()方法返回一个包含所有已加载变量名称的数组(不是值)。

默认情况下,Gea不会将此数组存储在任何地方。但可以通过将Gea\Gea::VAR_NAMES_HOLD标志作为第三个参数传递给instance()方法来指示Gea这样做。

$gea = Gea\Gea::instance(__DIR__, '.env', Gea\Gea::VAR_NAMES_HOLD);
$gea->load();

Gea被指示保存变量名称后,varNames()方法会返回它。

var_export( $gea->varNames() ); // array( 'FOO', 'BAR', 'BAZ' )

数组会保持更新,这意味着任何写入的变量都会被添加,任何丢弃的变量都会被删除。

这也意味着此方法与硬刷新结合使用可以丢弃所有已加载的变量。

$gea = Gea\Gea::instance(__DIR__, '.env', Gea\Gea::VAR_NAMES_HOLD);
$gea->load();

$gea->flush(Gea\Gea::FLUSH_HARD, $gea->varNames());

实际上,第二个参数可以省略:如果未提供内容且使用Gea\Gea::VAR_NAMES_HOLD标志实例化Gea,则硬刷新默认为丢弃所有变量。

只读行为

Gea提供了一种禁用所有写入、刷新和丢弃操作的方法。变量加载后,它们不能进行任何更改。

可以通过将Gea\Gea::READ_ONLY标志作为第三个参数传递给instance()方法来实现。

$gea = Gea\Gea::instance(__DIR__, '.env', Gea\Gea::READ_ONLY);
$gea->load();

之后,任何对write()discard()flush()的调用都会抛出异常。

请注意,instance()的第三个参数接受一个标志位掩码,因此可以组合它们。

$flags = Gea\Gea::READ_ONLY|Gea\Gea::VAR_NAMES_HOLD;
$gea = Gea\Gea::instance(__DIR__, '.env', $flags);

过滤和验证变量

Gea的一个强大功能是过滤和验证变量。

环境变量是字符串。然而,配置值不总是字符串...您可能想要整数、布尔值等。

此外,一些配置值是必需的,如果这些必需的配置不存在,则“尽早失败”是正常的。

Gea变量过滤器可以做到所有这些。

使用Gea实例的addFilter()方法添加过滤器。Gea附带以下过滤器:

  • required
  • enum
  • choices
  • int
  • float
  • bool
  • array
  • object
  • callback

还可以编写自定义过滤器。

过滤器的作用

使用Gea方法访问变量时,返回值可以被过滤器更改。

大多数过滤器是懒加载的,这意味着它们在第一次访问值时(如果需要)才进行评估(仅评估一次)。

执行“required”或“enum”之类的验证过滤器的操作是在变量加载后立即评估的,以确保如果缺少必需的值,则尽早失败。

必需的变量

要使环境变量成为必需的,可以使用“required”过滤器,如下所示

$gea = Gea\Gea::instance(__DIR__);

$gea->addFilter('API_KEY', 'required');

现在,当加载变量时,如果API_KEY变量未设置,则会抛出异常。

并非所有过滤器都具有相同的行为:事实上,只有'required'enumchoices在加载时进行评估,所有其他过滤器都是懒加载的,它们在第一次访问变量时(如果需要)进行评估。

将变量约束为某些值

'enum''choices'过滤器几乎做同样的事情:强制变量为可能值集。唯一的区别是enum执行严格的比较(===),而'choices'执行弱比较(==)。

$gea->addFilter('MY_SWITCH', ['enum' => ['on', 'off']]);

这次我传递了一个包含单个项目的数组作为过滤器,其中键是过滤器名称,值是一个用于配置过滤器的数组。这是实际需要配置的过滤器的使用语法。

值得注意的是,此过滤器可以用来自定义变量,例如上面的代码将在'MY_SWITCH'变量未设置时触发异常。通过将null添加到值列表中,变量变为可选。

转换为数值

'int''float'两个过滤器用于确保应用它们的变量分别转换为整数和浮点数。这些过滤器,就像以下所有过滤器一样,是懒加载的,它们在关联的变量第一次访问时(如果需要)进行评估。

$gea->addFilter('MY_NUMBER', 'int');

如果值不是数值,则过滤器会触发异常。

转换为布尔值

'bool'过滤器用于将变量转换为布尔值。请注意,此过滤器结合使用filter_var()FILTER_VALIDATE_BOOLEAN常量,这意味着字符串'1''true''yes''on'都被转换为true

$gea->addFilter('AWESOMENESS_ALLOWED', 'bool');

从变量创建数组

'array'过滤器非常灵活,用于将变量转换为数组。在其最简单形式中,只需用逗号拆分字符串即可

$gea->addFilter('MY_LIST', 'array');

此过滤器的配置接受三个参数:第一个参数允许设置不同的分隔符,第二个参数可用于打开或关闭trim拆分后的项目,最后第三个参数可以设置为回调,该回调将用于映射所有项目。

例如,假设变量USER.env文件中设置如下

USER=" john | doe"

使用过滤器

$gea->addFilter('USER', ['array' => ['|', ArrayFilter::DO_TRIM, 'ucfirst']]);
$gea->load();

那么

var_export($gea['USER']); // array( 'John', 'Doe' )

从变量实例化对象

'object'过滤器可用于使用在添加过滤器时给出的类来实例化对象,并将变量的当前值传递给类构造函数。

$gea->addFilter('USER_EMAIL', ['object' => [My\App\EmailObject::class]);

这对于直接从环境变量实例化值对象非常有用。

使用回调转换变量

Gea附带的最灵活的过滤器可能是callback,它允许设置一个回调,该回调将传递当前变量的值作为参数。无论回调返回什么,都将用作变量值。

$gea->addFilter('USER_ID', ['callback' => [function($userId) {
   return My\App\UserFactory::fromID($userId);
}]);

组合过滤器

在Gea中,可以组合变量过滤器。当设置了相同变量的多个过滤器时,它们将按照管道模式依次调用:下一个过滤器将使用前一个过滤器的结果作为参数。

要向变量添加更多过滤器,您可以

  • 多次使用相同变量名调用addFilter()
  • 将过滤器数组作为addFilter()的第二个参数传递
  • 以上两种方法都可以

一些示例

$gea->addFilter('USER_ID', ['required', 'int']);
$gea->addFilter('MY_LIST', ['array', ['object' => [\ArrayIterator::class]]);
$gea->addFilter('USER_EMAIL', ['required', ['callback' => function($email) {
   return filter_var($email, FILTER_SANITIZE_EMAIL);
}]);

Gea适用于生产环境

.env文件加载变量对于开发来说非常好用,但是在生产环境中应避免使用,以避免加载和解析.env文件的额外开销。

Gea的不错之处在于您不需要用它来加载变量就可以使用它:您可以使用Gea访问(可选地验证和过滤)环境变量,而无需调用load()

Gea将查找变量,无论它们是如何设置的。

这种行为,加上Gea实现的ArrayAccess接口,允许将其用作配置,这在测试中很容易模拟或替换,从而将应用程序代码与任何环境相关操作解耦。

无加载器实例

在不加载任何内容的情况下使用Gea作为配置“容器”的最简单方法是使用名为noLoaderInstance()的命名构造函数,就像这样简单

$gea = Gea\Gea::noLoaderInstance();

由于不需要加载任何文件,因此不需要传递文件夹路径或文件名。如果需要,可以将Gea标志作为第一个参数传递。

以这种方式获得的实例可以执行任何“正常”Gea实例可以执行的操作,您甚至可以在它上面调用load(),它将加载没有任何内容,但是任何非懒加载过滤器都将立即评估。

无加载器且只读实例

Gea中另一个可用的命名构造函数是readOnlyInstance()。使用此方法获得的实例不仅不加载任何内容,而且还处于只读模式。

$gea = Gea\Gea::readOnlyInstance();

等价于

$gea = Gea\Gea::noLoaderInstance(Gea\Gea::READ_ONLY);

集成示例

让我们想象一个类似这样的伪类

namespace MyApp;

class App {
  
    public function run(\ArrayAccess $configs) {
       // bootstrap the application here
    }
}

以及一个索引文件,如下所示

$gea = getenv('APP_ENV') === 'production'
   ? Gea\Gea::noLoaderInstance() # in production we load nothing
   : Gea\Gea::instance(__DIR__); # in development we load .env file

$gea->addFilter(['DB_NAME', 'DB_USER', 'DB_PASS'] 'required');
// maybe more filters here...

// This will load nothing on production, but ensures filter are validated
$gea->load(); 

$myApp = new MyApp\App();
$myApp->run($gea);

App的代码与环境函数、全局变量甚至Gea特定方法完全解耦。这意味着在测试中模拟配置将非常容易,或者可能切换到另一种类型的配置,例如JSON、Yaml或仅仅是PHP文件。

为什么选择Gea?

Gea是由Vance Lucas的PHP dotenv分支而来。

该库被广泛使用,并被PHP社区视为非常经济的代码。

那么,为什么是这个

一切始于我为一个应用程序工作,我无法允许环境变量存储在$_SERVER数组中(这是PHP Dotenv的默认行为),这是暴露的。

我意识到定制此行为并不像我之前想象的那么简单,于是决定分叉该库并添加一个接口,该接口允许更容易地替换PHP Dotenv的Loader类。

原始想法是向PHP Dotenv做出贡献,但在分叉并添加了接口之后,我无法停止自己重构……最终以完全不同的架构进行了完全重写。

这肯定不能合并到一个pull请求中。

Gea的架构比PHP Dotenv更复杂,这是不好的,但它也更加灵活,并且具有更多PHP Dotenv中没有的“糖”。

当然,人们可以使用Gea像Dotenv那样只加载一个.env文件,而不使用其他功能或自定义;在这种情况下,与PHP Dotenv的差异几乎为零。

根据您的使用情况,Gea可能适合或可能不适合。

PHP Dotenv和Gea之间的一些差异

  • Gea具有高级变量类型转换和过滤的过滤器(PHP Dotenv有“required”和“allowedValues”用于验证)
  • Gea实现了ArrayAccessread()write()discard()flush()方法,使其可以用作配置"桶"。这使得Gea更像是一个配置"桶",而不仅仅是加载环境变量。这个特性与可能根本不使用的加载器完全解耦。
  • Gea默认确保了不可变性,当使用其方法访问值时。
  • 在Gea中,很容易了解哪些变量已被设置/加载。
  • Gea是完全面向对象的,这意味着实现其接口很容易自定义其行为。
  • 最低要求的PHP版本:PHP Dotenv为5.3.9,Gea为5.5。
  • 许可证为PHP Dotenv的BSD-3-clause,Gea的MIT。

包含PHP Dotenv的部分。

此包包含来自PHP Dotenv的代码部分。更具体地说,解析"引擎"基本上是从那里直接采用的。

测试固定值和一些测试来源来自PHP Dotenv,以确保Gea加载文件的方式与Dotenv一样。

包含来自PHP Dotenv代码的所有文件都在顶部包含许可证声明。

最低要求

  • PHP 5.5+

安装

使用Composer require gmazzap/gea

许可证

Gea在MIT许可证下发布 https://open-source.org.cn/licenses/MIT

贡献

请参阅CONTRIBUTING.md

不要使用问题跟踪器(也不发送任何pull请求)如果您发现一个安全问题。它们是公开的,所以请将电子邮件发送到我的Github个人资料上的地址。谢谢。