Laravel框架下ENV的加载和读取的介绍

发布时间 - 2018-10-22 00:00:00    点击率:

本篇文章给大家带来的内容是关于laravel框架下env的加载和读取的介绍,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。

Laravel在启动时会加载项目中的.env文件。对于应用程序运行的环境来说,不同的环境有不同的配置通常是很有用的。 例如,你可能希望在本地使用测试的Mysql数据库而在上线后希望项目能够自动切换到生产Mysql数据库。本文将会详细介绍 env 文件的使用与源码的分析。

Env文件的使用

多环境env的设置

项目中env文件的数量往往是跟项目的环境数量相同,假如一个项目有开发、测试、生产三套环境那么在项目中应该有三个.env.dev、.env.test、.env.prod三个环境配置文件与环境相对应。三个文件中的配置项应该完全一样,而具体配置的值应该根据每个环境的需要来设置。

接下来就是让项目能够根据环境加载不同的env文件了。具体有三种方法,可以按照使用习惯来选择使用:

在环境的nginx配置文件里设置APP_ENV环境变量fastcgi_param APP_ENV dev;

设置服务器上运行PHP的用户的环境变量,比如在www用户的/home/www/.bashrc中添加export APP_ENV dev

在部署项目的持续集成任务或者部署脚本里执行cp .env.dev .env

针对前两种方法,Laravel会根据env('APP_ENV')加载到的变量值去加载对应的文件.env.dev、.env.test这些。 具体在后面源码里会说,第三种比较好理解就是在部署项目时将环境的配置文件覆盖到.env文件里这样就不需要在环境的系统和nginx里做额外的设置了。

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

env文件默认放在项目的根目录中,laravel 为用户提供了自定义 ENV 文件路径或文件名的函数,

例如,若想要自定义 env 路径,可以在 bootstrap 文件夹中 app.php 中使用Application实例的useEnvironmentPath方法:

$app = new Illuminate\Foundation\Application(
    realpath(__DIR__.'/../')
);

$app->useEnvironmentPath('/customer/path')

若想要自定义 env 文件名称,就可以在 bootstrap 文件夹中 app.php 中使用Application实例的loadEnvironmentFrom方法:

$app = new Illuminate\Foundation\Application(
    realpath(__DIR__.'/../')
);

$app->loadEnvironmentFrom('customer.env')

Laravel 加载ENV配置

Laravel加载ENV的是在框架处理请求之前,bootstrap过程中的LoadEnvironmentVariables阶段中完成的。

我们来看一下\Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables的源码来分析下Laravel是怎么加载env中的配置的。

configurationIsCached()) {
            return;
        }

        $this->checkForSpecificEnvironmentFile($app);

        try {
            (new Dotenv($app->environmentPath(), $app->environmentFile()))->load();
        } catch (InvalidPathException $e) {
            //
        }
    }

    /**
     * Detect if a custom environment file matching the APP_ENV exists.
     *
     * @param  \Illuminate\Contracts\Foundation\Application  $app
     * @return void
     */
    protected function checkForSpecificEnvironmentFile($app)
    {
        if ($app->runningInConsole() && ($input = new ArgvInput)->hasParameterOption('--env')) {
            if ($this->setEnvironmentFilePath(
                $app, $app->environmentFile().'.'.$input->getParameterOption('--env')
            )) {
                return;
            }
        }

        if (! env('APP_ENV')) {
            return;
        }

        $this->setEnvironmentFilePath(
            $app, $app->environmentFile().'.'.env('APP_ENV')
        );
    }

    /**
     * Load a custom environment file.
     *
     * @param  \Illuminate\Contracts\Foundation\Application  $app
     * @param  string  $file
     * @return bool
     */
    protected function setEnvironmentFilePath($app, $file)
    {
        if (file_exists($app->environmentPath().'/'.$file)) {
            $app->loadEnvironmentFrom($file);

            return true;
        }

        return false;
    }
}

在他的启动方法bootstrap中,Laravel会检查配置是否缓存过以及判断应该应用那个env文件,针对上面说的根据环境加载配置文件的三种方法中的头两种,因为系统或者nginx环境变量中设置了APP_ENV,所以Laravel会在checkForSpecificEnvironmentFile方法里根据 APP_ENV的值设置正确的配置文件的具体路径, 比如.env.dev或者.env.test,而针对第三中情况则是默认的.env, 具体可以参看下面的checkForSpecificEnvironmentFile还有相关的Application里的两个方法的源码:

protected function checkForSpecificEnvironmentFile($app)
{
    if ($app->runningInConsole() && ($input = new ArgvInput)->hasParameterOption('--env')) {
        if ($this->setEnvironmentFilePath(
            $app, $app->environmentFile().'.'.$input->getParameterOption('--env')
        )) {
            return;
        }
    }

    if (! env('APP_ENV')) {
        return;
    }

    $this->setEnvironmentFilePath(
        $app, $app->environmentFile().'.'.env('APP_ENV')
    );
}

namespace Illuminate\Foundation;
class Application ....
{

    public function environmentPath()
    {
        return $this->environmentPath ?: $this->basePath;
    }
    
    public function environmentFile()
    {
        return $this->environmentFile ?: '.env';
    }
}

判断好后要读取的配置文件的路径后,接下来就是加载env里的配置了。

(new Dotenv($app->environmentPath(), $app->environmentFile()))->load();

Laravel使用的是Dotenv的PHP版本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();
    }
}

它依赖/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;
    }
}

Loader读取配置时readLinesFromFile函数会用file函数将配置从文件中一行行地读取到数组中去,然后排除以#开头的注释,针对内容中包含=的行去调用setEnvironmentVariable方法去把文件行中的环境变量配置到项目中去:

namespace Dotenv;
class Loader
{
    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;
    }
    
    public function getEnvironmentVariable($name)
    {
        switch (true) {
            case array_key_exists($name, $_ENV):
                return $_ENV[$name];
            case array_key_exists($name, $_SERVER):
                return $_SERVER[$name];
            default:
                $value = getenv($name);
                return $value === false ? null : $value; // switch getenv default to null
        }
    }
}

Dotenv实例化Loader的时候把Loader对象的$immutable属性设置成了false,Loader设置变量的时候如果通过getEnvironmentVariable方法读取到了变量值,那么就会跳过该环境变量的设置。所以Dotenv默认情况下不会覆盖已经存在的环境变量,这个很关键,比如说在docker的容器编排文件里,我们会给PHP应用容器设置关于Mysql容器的两个环境变量

    environment:
      - "DB_PORT=3306"
      - "DB_HOST=database"

这样在容器里设置好环境变量后,即使env文件里的DB_HOST为homestead用env函数读取出来的也还是容器里之前设置的DB_HOST环境变量的值database(docker中容器链接默认使用服务名称,在编排文件中我把mysql容器的服务名称设置成了database, 所以php容器要通过database这个host来连接mysql容器)。因为用我们在持续集成中做自动化测试的时候通常都是在容器里进行测试,所以Dotenv不会覆盖已存在环境变量这个行为就相当重要这样我就可以只设置容器里环境变量的值完成测试而不用更改项目里的env文件,等到测试完成后直接去将项目部署到环境上就可以了。

如果检查环境变量不存在那么接着Dotenv就会把环境变量通过PHP内建函数putenv设置到环境中去,同时也会存储到$_ENV和$_SERVER这两个全局变量中。

在项目中读取env配置

在Laravel应用程序中可以使用env()函数去读取环境变量的值,比如获取数据库的HOST:

env('DB_HOST`, 'localhost');

传递给 env 函数的第二个值是「默认值」。如果给定的键不存在环境变量,则会使用该值。

我们来看看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 (strlen($value) > 1 && Str::startsWith($value, '"') && Str::endsWith($value, '"')) {
        return substr($value, 1, -1);
    }

    return $value;
}

它直接通过PHP内建函数getenv读取环境变量。

我们看到了在加载配置和读取配置的时候,使用了putenv和getenv两个函数。putenv设置的环境变量只在请求期间存活,请求结束后会恢复环境之前的设置。因为如果php.ini中的variables_order配置项成了 GPCS不包含E的话,那么php程序中是无法通过$_ENV读取环境变量的,所以使用putenv动态地设置环境变量让开发人员不用去关注服务器上的配置。而且在服务器上给运行用户配置的环境变量会共享给用户启动的所有进程,这就不能很好的保护比如DB_PASSWORD、API_KEY这种私密的环境变量,所以这种配置用putenv设置能更好的保护这些配置信息,getenv方法能获取到系统的环境变量和putenv动态设置的环境变量。


# php  # laravel  # 加载  # 配置文件  # 自定义  # 成了  # 中去  # 容器里  # 两种  # 不存在  # 器上  # 内建 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: Laravel如何实现数据导出到PDF_Laravel使用snappy生成网页快照PDF【方案】  Laravel如何实现API版本控制_Laravel版本化API设计方案  JavaScript如何实现类型判断_typeof和instanceof有什么区别  Windows Hello人脸识别突然无法使用  Laravel事件和监听器如何实现_Laravel Events & Listeners解耦应用的实战教程  Claude怎样写约束型提示词_Claude约束提示词写法【教程】  laravel怎么配置Redis作为缓存驱动_laravel Redis缓存配置教程  深圳网站制作公司好吗,在深圳找工作哪个网站最好啊?  Win11怎么更改系统语言为中文_Windows11安装语言包并设为显示语言  宙斯浏览器怎么屏蔽图片浏览 节省手机流量使用设置方法  如何在七牛云存储上搭建网站并设置自定义域名?  HTML5打空格有哪些误区_新手常犯的空格使用错误【技巧】  大连企业网站制作公司,大连2025企业社保缴费网上缴费流程?  制作企业网站建设方案,怎样建设一个公司网站?  Android 常见的图片加载框架详细介绍  香港服务器部署网站为何提示未备案?  Android自定义控件实现温度旋转按钮效果  中国移动官方网站首页入口 中国移动官网网页登录  详解Huffman编码算法之Java实现  如何快速打造个性化非模板自助建站?  Laravel怎么集成Vue.js_Laravel Mix配置Vue开发环境  Laravel如何实现API版本控制_Laravel API版本化路由设计策略  Laravel如何使用Guzzle调用外部接口_Laravel发起HTTP请求与JSON数据解析【详解】  详解jQuery中基本的动画方法  Laravel如何发送邮件_Laravel Mailables构建与发送邮件的简明教程  教你用AI润色文章,让你的文字表达更专业  悟空识字如何进行跟读录音_悟空识字开启麦克风权限与录音  黑客如何通过漏洞一步步攻陷网站服务器?  Laravel如何实现API速率限制?(Rate Limiting教程)  Laravel Eloquent关联是什么_Laravel模型一对一与一对多关系精讲  Windows家庭版如何开启组策略(gpedit.msc)?(安装方法)  香港服务器建站指南:外贸独立站搭建与跨境电商配置流程  Android okhttputils现在进度显示实例代码  Win11怎么设置虚拟桌面 Win11新建多桌面切换操作【技巧】  想要更高端的建设网站,这些原则一定要坚持!  Laravel如何清理系统缓存命令_Laravel清除路由配置及视图缓存的方法【总结】  如何确保西部建站助手FTP传输的安全性?  Linux虚拟化技术教程_KVMQEMU虚拟机安装与调优  标准网站视频模板制作软件,现在有哪个网站的视频编辑素材最齐全的,背景音乐、音效等?  无锡营销型网站制作公司,无锡网选车牌流程?  Laravel如何实现本地化和多语言支持?(i18n教程)  Laravel如何使用Seeder填充数据_Laravel模型工厂Factory批量生成测试数据【方法】  ,南京靠谱的征婚网站?  如何实现javascript表单验证_正则表达式有哪些实用技巧  如何在宝塔面板创建新站点?  在线制作视频的网站有哪些,电脑如何制作视频短片?  北京网站制作费用多少,建立一个公司网站的费用.有哪些部分,分别要多少钱?  Laravel如何配置和使用队列处理异步任务_Laravel队列驱动与任务分发实例  Laravel如何处理CORS跨域请求?(配置示例)  百度输入法ai组件怎么删除 百度输入法ai组件移除工具