Laravel ENV—— 环境变量的加载与源码解析

2018-08-05 23:54:40

前言

本文 GitBook 地址: https://www.gitbook.com/book/leoyang90/laravel-source-analysis

laravel 在启动时,会加载项目的 env 文件,本文将会详细介绍 env 文件的使用与源码的分析。

 

ENV 文件的使用

多环境 ENV 文件的设置

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 文件。

自定义 ENV 文件的路径与文件名

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 文件变量设置

  • 在 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 加载源码分析

laravel 加载 ENV

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

vlucas/phpdotenv 源码解读

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\"dfd\"fdf",我们就可以提取出来 "dfd\"dfd\"fdf"。

  • 不允许引用中含有 \,但可以使用转义字符 \\

  • 2020-04-15 21:14:13

    .d.ts与.ts的区别 .d.ts怎么用

    在TypeScript项目中直接引入Javascript包是不能使用的,因为包中缺少TypeScript类型声明,如果是自己写的包,可以考虑自己增加一个.d.ts类型声明文件,如果代码比较多或者使用的是第三方的包,自己写就比较麻烦了。第三方的包首先考虑找一个别人写好的声明文件,如果没有可以使用一些自动生成声明文件的工具。

  • 2020-04-17 09:27:38

    推荐一个老前端开发者的博客

    前端修炼场,首页标签大全greenSock前端研究VUE研究我们的作品flash技术探讨开发心得个人档案培训与招聘服务报价

  • 2020-04-17 09:41:47

    前端css博客推荐

    这个博客有大量的css内容,有svg,TweenMax等教程,抽空通读一下

  • 2020-04-17 10:20:47

    GreenSocks Animation Platform详细工作机制以及TweenMax用法

    GSAP(GreenSocks Animation Platform)是一个性能较好的前端动画库。最近在写一个前端SVG动画编辑器时选择了它作为底层的动画库。为了减少踩坑,我大致浏览了它的源代码,这篇文章主要是对我的理解进行记录。 我会先简单介绍一下这个动画库的API,再介绍它的插件机制,最后会从一个用例出发跟踪其运行机制。

  • 2020-04-17 10:39:02

    CSS 滤镜技巧与细节,实现火焰,融合等特效

    简单来说,CSS 滤镜就是提供类似 PS 的图形特效,像模糊,锐化或元素变色等功能。通常被用于调整图片,背景和边界的渲染。本文就会围绕这些滤镜展开,看看具体能怎么使用或者玩出什么花活。

  • 2020-04-17 10:42:29

    (三)TweenMax运动效果

    运动效果 实例化对象.set() 立刻运动到指定地点,不用加时间

  • 2020-04-17 11:19:55

    Vue中的is和操作DOM

    vue中is的属性引入是为了解决dom结构中对放入html的元素有限制的问题,譬如ul里面要接上li的标签,引入is的属性后,你完全可以写成这样

  • 2020-04-17 11:27:48

    TweenMax中文初级教程一

    TweenMax.js集成了GreenSock动画平台的大部分核心功能,且具有极高的兼容性。