laravel中env底层加载和解析原理

Posted 李斌的BLOG

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了laravel中env底层加载和解析原理相关的知识,希望对你有一定的参考价值。

前言

我们的应用程序几乎都会遇到不同环境需要不同的配置文件,比如最常见的开发环境需要连接开发数据库,生产需要连接生产库。记的自己曾经有个项目的做法是写两个配置文件,然后定义一个常量IS_PRODUCTION,默认是false,然后根据这个常量取配置文件,因为当时不是自动化部署,最闹心的就是每次上线之后还要去线上服务器手动将这个常量的值改为true。终于使用了laravel和自动化部署开发,完美的解决了不同环境加载不同配置文件,再也不用担心环境多配置文件乱的问题了。今天我们先来看看laravel中env的加载。

env的使用

一般程序至少都会有开发、测试、生产三个环境。我一般习惯有.env、.env.test、.env.prod三个文件。然后在测试和环境的nginx配置中将fastcgi_param APP_ENV分别设置为testing和prod,这样就可以实现开发环境加载.env测试环境加载.env.test生产环境加载.env.prod了,下边给出nginx中的一部分配置。

location ~ \\.php$ {
        fastcgi_split_path_info ^(.+\\.php)(/.+)$;
        fastcgi_pass   php:9000;
        fastcgi_index  index.php;
        include        fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param APP_ENV test;
    }

获取不同的env文件的原理

laravel中在处理请求之前是要先注册和引导程序的,加载env配置就是发生在引导阶段,其实我们的环境配置是最先被引导进来的。

class LoadEnvironmentVariables
{
   public function bootstrap(Application $app)
    {
        if ($app->configurationIsCached()) { //配置文件是否已经被缓存
            return;
        }

        $this->checkForSpecificEnvironmentFile($app); //设置配置文件的路径,也就是在这里决定取哪个env文件

        try {
            (new Dotenv($app->environmentPath(), $app->environmentFile()))->load(); //加载配置文件,并设置环境变量
        } catch (InvalidPathException $e) {
            //
        } catch (InvalidFileException $e) {
            die('The environment file is invalid: '.$e->getMessage());
        }
    }

    protected function checkForSpecificEnvironmentFile($app)
    {
        if ($app->runningInConsole() && ($input = new ArgvInput)->hasParameterOption('--env')) { //如果是命令行运行的并且设置了--env参数则加载.env连接--env参数的值
            if ($this->setEnvironmentFilePath(
                $app, $app->environmentFile().'.'.$input->getParameterOption('--env')
            )) {
                return;
            }
        }

        if (! env('APP_ENV')) { //如果环境变量APP_ENV没有获取到,则使用默认的.env
            return;
        }

        $this->setEnvironmentFilePath( //否则为  '.env'连接环境变量的值
            $app, $app->environmentFile().'.'.env('APP_ENV')
        );
    }
    protected function setEnvironmentFilePath($app, $file)
    {
        if (file_exists($app->environmentPath().'/'.$file)) {
            $app->loadEnvironmentFrom($file);

            return true;
        }

        return false;
    }
}

class Application
{
  public function environmentFile()
    {
       //这里的environmentFile默认为.env
        return $this->environmentFile ?: '.env';
    }

  public function loadEnvironmentFrom($file) //设置配置文件路径
    {
        $this->environmentFile = $file;

        return $this;
    }
}

这里的checkForSpecificEnvironmentFile就决定了程序会加载哪个配置文件,对于命令行执行的会用 $app->environmentFile连接上参数--env的值,如果获取不到APP_ENV的环境变量就取$app->environmentFile,否则用$app->environmentFile连接上环境变量APP_ENV的值。其中如果$appenvironmentFile这个值没有特殊设置他的默认值就是".env"。

env加载的内容

有了env的路径之后,这个文件里的各种配置信息是如何用到程序中的呢?

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->loader->setImmutable(true
)->load();
    }

   
}

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

  public function setEnvironmentVariable($name, $value = null)
    {
        list($name, $value) = $this->normaliseEnvironmentVariable($name, $value); //将读取的文件修改为键值型的

        $this->variableNames[] = $name;

        if ($this->immutable && $this->getEnvironmentVariable($name) !== null) { //这里是一个点,如果外部已经设置了环境变量则使用外部的,
            return;
        }

      //如果外部没有设置则将配置文件中的设为环境变量
        if (function_exists('apache_getenv') && function_exists('apache_setenv') && apache_getenv($name) !== false) {
            apache_setenv($name, $value);
        }

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

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

在env的加载中Loader完成了文件的实际加载和处理,他先将配置文件读取到数组中,把非注释部分的键值设置为环境变量,这里有个细节就是在设置为环境变量之前会先判断环境变量是否存在,只会将存在与配置文件中但不在环境变量的值,设为环境变量,这句话有点绕总之意思就是系统的环境变量的优先级要高于配置文件中的配置,配置文件中的配置不会重写环境变量里的值。

env的读取

function env($key, $default = null)
    {
        $value = getenv($key); 

        if ($value === false) {
            return value($default);
        }

        switch (strtolower($value)) {
            case 'true':
            case '(true)':
                return true;
            case 'false':
            case '(false)':
                return false;
            case 'empty':
            case '(empty)':
                return '';
            case 'null':
            case '(null)':
                return;
        }

        if (($valueLength = strlen($value)) > 1 && $value[0] === '"' && $value[$valueLength - 1] === '"') {
            return substr($value, 1, -1);
        }

        return $value;
    }

这里的核心就是getenv()就是获取一个环境变量的值,其实整个env的核心就是将配置存入环境变量然后再从环境变量中取出提供给应用程序。

最后

这个就是我们根据不同环境加载不同配置文件的过程,最后在说一下其实在实际工作中各个环境的nginx配置也应该用同一份,我在实际的开发中用的是docker部署的所以将nginx中fastcgi_param APP_ENV test的test用了一个变量ENVIRONMENT_TYPE代替了,在docker容器生成时传进去一个环境变量然后使用sed命令将ENVIRONMENT_TYPE用环境变量替换掉。


 

以上是关于laravel中env底层加载和解析原理的主要内容,如果未能解决你的问题,请参考以下文章

laravel的源码解析:PHP自动加载功能原理解析

iOS之深入解析类加载的底层原理:分类如何加载到类以及分类和类的配合使用

iOS之深入解析类加载的底层原理:类如何加载到内存中?

在 Laravel 中重新加载自定义 .env 文件后如何更改存储设置?

Laravel PHPUnit 加载 .env.testing 文件

yii2深入理解之内核解析