Skip to main content

env 配置文件加载

简介

接着上一章所说,这一章介绍 .env 文件的引导加载。

  • 如图,Illuminate\Foundation\Http\Kernel 类属性 $bootstrappers 中第一个引导启动类

file

Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables 类,从字面意义不难看出,就是有关 env 配置文件加载

正式介绍

如图,从 Illuminate\Foundation\Application 容器的 bootstrapWith 方法开始:

file

上一章我讲过,Laravel 的配置加载其实就是实例化相关类,并调用相关类的 bootstrap 方法,在这一章这个相关类就是 Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables

接下来我们进入 Illuminate\Foundation\Bootstrap\LoadEnvironmentVariablesbootstrap 中看一下

public function bootstrap(Application $app)
{
if ($app->configurationIsCached()) {
return;
}

$this->checkForSpecificEnvironmentFile($app);

try {
(new Dotenv($app->environmentPath(), $app->environmentFile()))->load(); // 核心
} catch (InvalidPathException $e) {
//
} catch (InvalidFileException $e) {
die('The environment file is invalid: '.$e->getMessage());
}
}

从这个方法的代码上,不难看出,程序先检测有没有配置缓存,配置缓存就是 bootstrap/cache/config.php ,如果有这个文件,则退出加载,否则继续;接着检测特殊配置文件。

关于检测特殊配置文件的内容,我在这简单一说,不贴出代码了,这块不是重点:

检测特殊配置,首先是检测当前执行环境是 web 访问还是 cli 执行,如果是 cli 执行,如果命令行中含有 --env ,则设置配置文件路径,此功能结束;之后,通过 env 函数读取 APP_ENV 键,看是否有返回值,如果没有返回值,则结束检测,若果有返回值,将系统默认的 .env 文件路径与返回值拼接,设置到 env 配置路径中。

我现在执行的环境,用的的 web 访问,且 env('APP_ENV') 无返回值,即 .env 配置文件的路径为系统默认路径

下面我们看一下 env 配置加载核心代码 (new Dotenv($app->environmentPath(), $app->environmentFile()))->load(); 这段代码指,实例化 Dotenv.env 文件所在绝对路径以及文件名当做参数传入,然后调用 load 方法

如图,实例化 Dotenv 时的构造程序

file

$this->getFilePath($path, $file) 作用就是将路径和文件名拼接起来,构成完整的 .env 文件的绝对路径;然后实例化 Loader 类,将完整的 .env 文件路径传入,外加一个 true;取得 Loader 类的实例对象后,赋值给当前对象的 loader 属性

下面我们看一下 Loader 类的构造函数

public function __construct($filePath, $immutable = false)
{
$this->filePath = $filePath;
$this->immutable = $immutable;
}

不难看出,就是一个简单的属性赋值运算。

好了,整个 Dotenv 类的实例化结束,下面看一下它的 load 方法

public function load()
{
return $this->loadData();
}

调用了 loadData 方法,继续看

protected function loadData($overload = false)
{
return $this->loader->setImmutable(!$overload)->load();
}

调用了 Loader 类下的 setImmutable 方法,并传入 false,看一下这个 setImmutable 方法

public function setImmutable($immutable = false)
{
$this->immutable = $immutable;

return $this;
}

false 赋值给 immutable 属性,返回了 Loader 类对象

那么,我们就看一下 Loader 类对象下的 load 方法

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;
}

呼!走了这么长的路,终于要到了。。。

  • $this->ensureFileIsReadable(); 作用是检测 .env 文件是否存在,是否可读,可以时通过,不可以时,抛出异常,结束程序;
  • $lines = $this->readLinesFromFile($filePath); 作用是根据 .env 配置文件的换行符,将每一行的字符串叠加到一个索引数组中,并返回该数组,我们看一下这个方法
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;
}

其中 PHP 的配置项 auto_detect_line_endings ,看下图

file

意思是说,当调用 file 方法的时候,自动检测换行符。因为有性能损失,所以,先 ini_get 一下,用完,再设置回去。

关于 file 方法,看这里

最后给你看一下,读取完 .env 文件变量情况

file

我们回到 load 方法继续

foreach ($lines as $line) {
if (!$this->isComment($line) && $this->looksLikeSetter($line)) {
$this->setEnvironmentVariable($line);
}
}

这段循环值,将 $lines 数组每个元素进行解析,并赋值到系统环境变量中。

详解:

!$this->isComment($line) 指如果当前元素第一个字符有值并且是 # 代表 false ,不执行 setEnvironmentVariable 方法,这相当于是一个注释;具体看代码

protected function isComment($line)
{
$line = ltrim($line);

return isset($line[0]) && $line[0] === '#'; // 在这里哦,返回 boolean
}

$this->looksLikeSetter($line) 指在当前元素中有没有 = 号,有则返回 true ,看代码

protected function looksLikeSetter($line)
{
return strpos($line, '=') !== false;
}

通过上面两个判断,就是说如果当前元素开头不是 #,且含有 = 则,执行 setEnvironmentVariable 方法

我们看一下 setEnvironmentVariable 方法

public function setEnvironmentVariable($name, $value = null)
{
list($name, $value) = $this->normaliseEnvironmentVariable($name, $value);

$this->variableNames[] = $name;

// Don't overwrite existing environment variables if we're immutable
// Ruby's dotenv does this with `ENV[key] ||= value`.
if ($this->immutable && $this->getEnvironmentVariable($name) !== null) {
return;
}

// If PHP is running as an Apache module and an existing
// Apache environment variable exists, overwrite it
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 前,name 就是 $lines 一个元素(包含键和值的字符串), valuenull。调用 normaliseEnvironmentVariable 之后,程序会根据元素中的 = 进行键值分割,分别赋值给 $name$value

关于 normaliseEnvironmentVariable 方法,实现了元素以 = 分解键值对功能,识别值中加密串和分解值中后面的 # 注释的能力,识别 键和值 中变量的能力,最终解析出需要的内容,分别赋值给 $name$value

之后检测 PHP 内置是否存在一些函数,有则调用,并将获取的键和值作为参数传入,下面看一下 putenv 函数

if (function_exists('putenv')) {
putenv("$name=$value");
}

我执行的环境中是有这个函数的,这是官方对于这个函数讲解

最后,进行了 $_ENV$_SERVER 赋值

$_ENV[$name] = $value;
$_SERVER[$name] = $value;

文章末尾贴一张,加载完 .env 的变量图

file

写在文章尾

通过上面的学习,我们知道了。 .env 文件是支持注释的,注释的开头是 # ,仅能支持一行,另一行也以 # 开头。还可以下载当前的行尾。如下

file

下一章讲解 config 配置文件的加载