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

栏目: IT技术 · 发布时间: 6年前

内容简介:Laravel ENV—— 环境变量的加载与源码解析

前言

本文 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_ENVproduction,那么 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"。
  • 不允许引用中含有 \,但可以使用转义字符 \\

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Game Programming Patterns

Game Programming Patterns

Robert Nystrom / Genever Benning / 2014-11-2 / USD 39.95

The biggest challenge facing many game programmers is completing their game. Most game projects fizzle out, overwhelmed by the complexity of their own code. Game Programming Patterns tackles that exac......一起来看看 《Game Programming Patterns》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

SHA 加密
SHA 加密

SHA 加密工具