我有一个Configuration读取环境变量的类:
class Configuration {
has $.config_string_a;
has $.config_string_b;
has Bool $.config_flag_c;
method new() {
sub assertHasEnv(Str $envVar) {
die "environment variable $envVar must exist" unless %*ENV{$envVar}:exists;
}
assertHasEnv('CONFIG_STRING_A');
assertHasEnv('CONFIG_STRING_B');
assertHasEnv('CONFIG_FLAG_C');
return self.bless(
config_string_a => %*ENV{'CONFIG_STRING_A'},
config_string_b => %*ENV{'CONFIG_STRING_B'},
config_flag_c => Bool(%*ENV{'CONFIG_FLAG_C'}),
);
}
}
my $config = Configuration.new;
say $config.config_string_a;
say $config.config_string_b;
say $config.config_flag_c;
Run Code Online (Sandbox Code Playgroud)
是否有更简洁的方式来表达这一点?例如,我在检查中重复环境变量名称和构造函数的返回值.
我可以很容易地看到编写另一个更通用的类,它封装了config参数的必要信息:
class ConfigurationParameter {
has $.name;
has $.envVarName;
has Bool $.required;
method new (:$name, :$envVarName, :$required = True) {
return self.bless(:$name, :$envVarName, :$required);
}
}
Run Code Online (Sandbox Code Playgroud)
然后将它们滚动到Configuration类中的List中.但是,我不知道如何重构构造函数Configuration以适应这一点.
想到的最直接的变化是改变new为:
method new() {
sub env(Str $envVar) {
%*ENV{$envVar} // die "environment variable $envVar must exist"
}
return self.bless(
config_string_a => env('CONFIG_STRING_A'),
config_string_b => env('CONFIG_STRING_B'),
config_flag_c => Bool(env('CONFIG_FLAG_C')),
);
}
Run Code Online (Sandbox Code Playgroud)
虽然//是定义检查而不是存在检查,但环境变量未定义的唯一方法是未设置.这可以归结为%*ENV每个环境变量的提及.
如果只有少数,那么我可能会停在那里,但下一个重复的事情让我感到震惊的是,属性的名称只是环境变量名称的小写,所以我们也可以消除那些重复,更复杂的成本:
method new() {
multi env(Str $envVar) {
$envVar.lc => %*ENV{$envVar} // die "environment variable $envVar must exist"
}
multi env(Str $envVar, $type) {
.key => $type(.value) given env($envVar)
}
return self.bless(
|env('CONFIG_STRING_A'),
|env('CONFIG_STRING_B'),
|env('CONFIG_FLAG_C', Bool),
);
}
Run Code Online (Sandbox Code Playgroud)
现在env返回a Pair,并将其展|平到参数列表中,就像它是一个命名参数一样.
最后,"电动工具"的方法是在课外写一个这样的特征:
multi trait_mod:<is>(Attribute $attr, :$from-env!) {
my $env-name = $attr.name.substr(2).uc;
$attr.set_build(-> | {
with %*ENV{$env-name} -> $value {
Any ~~ $attr.type ?? $value !! $attr.type()($value)
}
else {
die "environment variable $env-name must exist"
}
});
}
Run Code Online (Sandbox Code Playgroud)
然后把课程写成:
class Configuration {
has $.config_string_a is from-env;
has $.config_string_b is from-env;
has Bool $.config_flag_c is from-env;
}
Run Code Online (Sandbox Code Playgroud)
特征在编译时运行,并且可以以各种方式操纵声明.此特征根据属性名称计算环境变量的名称(属性名称总是如此$!config_string_a,因此substr).该set_build组将运行在创建类时初始化属性的代码.这传递了各种各样的事情,在我们的情况下并不重要,所以我们忽略了这些论点|.该with就像if defined,所以这是同样的方法为//早.最后,Any ~~ $attr.type检查询问参数是否以某种方式受约束,如果是,则执行强制(通过使用值调用类型来完成).
所以我在评论中提到了这一点,但我认为作为实际答案会很好。我认为这对于构建基于 Docker 的系统的任何人来说都是有用的功能,因此采用 Jonanthan 的示例代码,添加了一些用于导出 Traits 的功能 Elizabeth 向我展示并制作了Trait::Env
用法是:
use Trait::Env;
class Configuration {
has $.config_string_a is env;
has $.config-string-b is env(:required);
has Bool $.config-flag-c is env is default(True);
}
Run Code Online (Sandbox Code Playgroud)
如果未找到,该:required标志将打开die。它与这个is default特性很好地结合在一起。属性名称是大写的,-并_在检查之前替换为%*ENV。
我有几个计划中的更改,让它抛出一个命名的异常而不是死掉,并且更好地处理布尔值。由于%*ENV是一个字符串有一个布尔值false是一个有点疼痛。