laravel
在启动时,会加载项目的 env
文件,本文将会详细介绍 env
文件的使用与源码的分析。
一、在项目写多个 ENV
文件,例如三个 env
文件:
.env.development
、.env.staging
、.env.production
,这三个文件中分别针对不同环境为某些变量配置了不同的值,
二、配置 APP_ENV
环境变量值
配置环境变量的方法有很多,其中一个方法是在 nginx
的配置文件中写下这句代码:
fastcgi_param APP_ENV production;
那么 laravel
会通过 env('APP_ENV')
根据环境变量 APP_ENV
来判断当前具体的环境,假如环境变量 APP_ENV
为 production
,那么 laravel
将会自动加载 .env.production
文件。
laravel
为用户提供了自定义 ENV
文件路径或文件名的函数,
例如,若想要自定义 env
路径,就可以在 bootstrap
文件夹中 app.php
文件:
$app = new Illuminate\Foundation\Application(
realpath(__DIR__.'/../')
);
$app->useEnvironmentPath('/customer/path')
若想要自定义 env
文件名称,就可以在 bootstrap
文件夹中 app.php
文件:
$app = new Illuminate\Foundation\Application(
realpath(__DIR__.'/../')
);
$app->loadEnvironmentFrom('customer.env')
env
文件中,我们可以为变量赋予具体值:CFOO=bar
值得注意的是,这种具体值不允许赋予多个,例如:
CFOO=bar baz
CQUOTES="a value with a # character"
值得注意的是,这种引用不允许字符串中存在符号 \
,只能使用转义字符 \\
而且也不允许内嵌符号 ""
,只能使用转移字符 \"
,否则取值会意外结束:
CQUOTESWITHQUOTE="a value with a # character & a quote \" character inside quotes" # " this is a comment
$this->assertEquals('a value with a # character & a quote " character inside quotes', getenv('CQUOTESWITHQUOTE'));
env
文件中添加注释,方法是以 #
开始:CQUOTES="a value with a # character" # this is a comment
export
来为变量赋值:export EFOO="bar"
env
文件中使用变量为变量赋值:NVAR1="Hello"
NVAR2="World!"
NVAR3="{$NVAR1} {$NVAR2}"
NVAR4="${NVAR1} ${NVAR2}"
NVAR5="$NVAR1 {NVAR2}"
$this->assertEquals('{$NVAR1} {$NVAR2}', $_ENV['NVAR3']); // not resolved
$this->assertEquals('Hello World!', $_ENV['NVAR4']);
$this->assertEquals('$NVAR1 {NVAR2}', $_ENV['NVAR5']); // not resolved
ENV
的加载功能由类 \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class
完成,它的启动函数为:
public function bootstrap(Application $app)
{
if ($app->configurationIsCached()) {
return;
}
$this->checkForSpecificEnvironmentFile($app);
try {
(new Dotenv($app->environmentPath(), $app->environmentFile()))->load();
} catch (InvalidPathException $e) {
//
}
}
如果我们在环境变量中设置了 APP_ENV
变量,那么就会调用函数 checkForSpecificEnvironmentFile
来根据环境加载不同的 env
文件:
protected function checkForSpecificEnvironmentFile($app)
{
if (php_sapi_name() == 'cli' && with($input = new ArgvInput)->hasParameterOption('--env')) {
$this->setEnvironmentFilePath(
$app, $app->environmentFile().'.'.$input->getParameterOption('--env')
);
}
if (! env('APP_ENV')) {
return;
}
$this->setEnvironmentFilePath(
$app, $app->environmentFile().'.'.env('APP_ENV')
);
}
protected function setEnvironmentFilePath($app, $file)
{
if (file_exists($app->environmentPath().'/'.$file)) {
$app->loadEnvironmentFrom($file);
}
}
laravel
中对 env
文件的读取是采用 vlucas/phpdotenv
的开源项目:
class Dotenv
{
public function __construct($path, $file = '.env')
{
$this->filePath = $this->getFilePath($path, $file);
$this->loader = new Loader($this->filePath, true);
}
public function load()
{
return $this->loadData();
}
protected function loadData($overload = false)
{
$this->loader = new Loader($this->filePath, !$overload);
return $this->loader->load();
}
}
env
文件变量的读取依赖类 /Dotenv/Loader
:
class Loader
{
public function load()
{
$this->ensureFileIsReadable();
$filePath = $this->filePath;
$lines = $this->readLinesFromFile($filePath);
foreach ($lines as $line) {
if (!$this->isComment($line) && $this->looksLikeSetter($line)) {
$this->setEnvironmentVariable($line);
}
}
return $lines;
}
}
我们可以看到,env
文件的读取的流程:
env
文件是否可读env
文件,并将文件按行存储protected function ensureFileIsReadable()
{
if (!is_readable($this->filePath) || !is_file($this->filePath)) {
throw new InvalidPathException(sprintf('Unable to read the environment file at %s.', $this->filePath));
}
}
protected function readLinesFromFile($filePath)
{
// Read file into an array of lines with auto-detected line endings
$autodetect = ini_get('auto_detect_line_endings');
ini_set('auto_detect_line_endings', '1');
$lines = file($filePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
ini_set('auto_detect_line_endings', $autodetect);
return $lines;
}
protected function isComment($line)
{
return strpos(ltrim($line), '#') === 0;
}
protected function looksLikeSetter($line)
{
return strpos($line, '=') !== false;
}
环境变量赋值是 env
文件加载的核心,主要由 setEnvironmentVariable
函数:
public function setEnvironmentVariable($name, $value = null)
{
list($name, $value) = $this->normaliseEnvironmentVariable($name, $value);
if ($this->immutable && $this->getEnvironmentVariable($name) !== null) {
return;
}
if (function_exists('apache_getenv') && function_exists('apache_setenv') && apache_getenv($name)) {
apache_setenv($name, $value);
}
if (function_exists('putenv')) {
putenv("$name=$value");
}
$_ENV[$name] = $value;
$_SERVER[$name] = $value;
}
normaliseEnvironmentVariable
函数用来加载各种类型的环境变量:
protected function normaliseEnvironmentVariable($name, $value)
{
list($name, $value) = $this->splitCompoundStringIntoParts($name, $value);
list($name, $value) = $this->sanitiseVariableName($name, $value);
list($name, $value) = $this->sanitiseVariableValue($name, $value);
$value = $this->resolveNestedVariables($value);
return array($name, $value);
}
splitCompoundStringIntoParts
用于将赋值语句转化为环境变量名 name
和环境变量值 value
。
protected function splitCompoundStringIntoParts($name, $value)
{
if (strpos($name, '=') !== false) {
list($name, $value) = array_map('trim', explode('=', $name, 2));
}
return array($name, $value);
}
sanitiseVariableName
用于格式化环境变量名:
protected function sanitiseVariableName($name, $value)
{
$name = trim(str_replace(array('export ', '\'', '"'), '', $name));
return array($name, $value);
}
sanitiseVariableValue
用于格式化环境变量值:
protected function sanitiseVariableValue($name, $value)
{
$value = trim($value);
if (!$value) {
return array($name, $value);
}
if ($this->beginsWithAQuote($value)) { // value starts with a quote
$quote = $value[0];
$regexPattern = sprintf(
'/^
%1$s # match a quote at the start of the value
( # capturing sub-pattern used
(?: # we do not need to capture this
[^%1$s\\\\] # any character other than a quote or backslash
|\\\\\\\\ # or two backslashes together
|\\\\%1$s # or an escaped quote e.g \"
)* # as many characters that match the previous rules
) # end of the capturing sub-pattern
%1$s # and the closing quote
.*$ # and discard any string after the closing quote
/mx',
$quote
);
$value = preg_replace($regexPattern, '$1', $value);
$value = str_replace("\\$quote", $quote, $value);
$value = str_replace('\\\\', '\\', $value);
} else {
$parts = explode(' #', $value, 2);
$value = trim($parts[0]);
// Unquoted values cannot contain whitespace
if (preg_match('/\s+/', $value) > 0) {
throw new InvalidFileException('Dotenv values containing spaces must be surrounded by quotes.');
}
}
return array($name, trim($value));
}
这段代码是加载 env
文件最复杂的部分,我们详细来说:
若环境变量值是具体值,那么仅仅需要分割注释 #
部分,并判断是否存在空格符即可。
若环境变量值由引用构成,那么就需要进行正则匹配,具体的正则表达式为:
/^"((?:[^"\\]|\\\\|\\"))".*$/mx
这个正则表达式的意思是:
“”
双引号内部的字符串,抛弃双引号之后的字符串\"
,例如 "dfd\"dfd\"fdf",我们就可以提取出来 "dfd\"dfd\"fdf"。\
,但可以使用转义字符 \\