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中添加 exportAPP_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中的配置的。

<?php
namespace Illuminate\Foundation\Bootstrap;
use Dotenv\Dotenv;
use Dotenv\Exception\InvalidPathException;
use Symfony\Component\Console\Input\ArgvInput;
use Illuminate\Contracts\Foundation\Application;
class LoadEnvironmentVariables
{/*** Bootstrap the given application.** @param  \Illuminate\Contracts\Foundation\Application  $app* @return void*/public function bootstrap(Application $app){if ($app->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 itif (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属性设置成了 falseLoader设置变量的时候如果通过 getEnvironmentVariable方法读取到了变量值,那么就会跳过该环境变量的设置。所以 Dotenv默认情况下不会覆盖已经存在的环境变量,这个很关键,比如说在 docker的容器编排文件里,我们会给 PHP应用容器设置关于 Mysql容器的两个环境变量

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

这样在容器里设置好环境变量后,即使 env文件里的 DB_HOSThomesteadenv函数读取出来的也还是容器里之前设置的 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读取环境变量。

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

Laravel源码解析之ENV配置相关推荐

  1. 8145v5 参数_SpringBoot外化配置源码解析:外化配置简介、参数处理|value|spring|调用|参数值

    SpringBoot外化配置源码解析 在前面章节我们讲解了 Spring Boot 的运作核心原理及启动过程中进行的一系列核心操作. 从本章开始,我们将针对在实践过程中应用的不同知识点的源代码进行解读 ...

  2. Laravel源码解析之从入口开始

    前言 提升能力的方法并非使用更多工具,而是解刨自己所使用的工具.今天我们从Laravel启动的第一步开始讲起. 入口文件 laravel是单入口框架,所有请求必将经过index.php define( ...

  3. tns03505 无法解析名称_SpringBootWeb源码解析SpringMVC自动配置

    SpringMVC自动配置 在 Spring Boot 中引入了 spring-boot-starter-web 依赖,并完成了 DispatcherServlet 的自动配置之后,便会通过 WebM ...

  4. Myth源码解析系列之四- 配置与启动详解

    在上一篇中,我们项目所需的整个环境都已搭建完成,下面我们主要介绍项目的相关配置于启动环节 配置详解 注意: 这里事务存储我们这里采用的是 : mysql, 消息中间件选择的是:rocketmq, 其他 ...

  5. Laravel源码解析之Console内核

    上一篇文章我们介绍了Laravel的HTTP内核,详细概述了网络请求从进入应用到应用处理完请求返回HTTP响应整个生命周期中HTTP内核是如何调动Laravel各个核心组件来完成任务的.除了处理HTT ...

  6. Laravel源码解析之QueryBuilder

    Database 查询构建器 上文我们说到执行 DB::table('users')->get()是由Connection对象执行table方法返回了一个QueryBuilder对象,Query ...

  7. Laravel源码解析之中间件

    中间件(Middleware)在Laravel中起着过滤进入应用的HTTP请求对象(Request)和完善离开应用的HTTP响应对象(Reponse)的作用, 而且可以通过应用多个中间件来层层过滤请求 ...

  8. Laravel源码解析之事件系统

    Laravel 的事件提供了一个简单的观察者实现,能够订阅和监听应用中发生的各种事件.事件机制是一种很好的应用解耦方式,因为一个事件可以拥有多个互不依赖的监听器. laravel 中事件系统由两部分构 ...

  9. Laravel源码解析之Eloquent Model

    上篇文章我们讲了Database的查询构建器Query Builder, 学习了Query Builder为构建生成SQL语句而提供的Fluent Api的代码实现.这篇文章我们来学习Laravel ...

最新文章

  1. 【FPGA】流水线学习笔记
  2. Altium Designer Winter09 的使用心得
  3. 初始化列表||类对象作为类成员|| 静态成员
  4. Linux与Windows比较出的20个优势
  5. P4213 【模板】杜教筛
  6. wpf 切换搜狗输入法英文_搜狗输入法全新升级手写功能,中英数字自由写,告别切换丨本周新闻...
  7. C++轻量级微服务_从微服务架构解析信源新一代“金融e采”产品
  8. VSFTPD Centos 7.6 _配置篇
  9. c语言的c语句ab2,当初我在重庆交通大学读书的时候C语言成绩也相当不错的
  10. Spring 全局异常捕获
  11. OSI模型七层模型结构
  12. ts 报错:‘new‘ expression, whose target lacks a construct signature, implicitly has an ‘any‘ type.
  13. ubuntu22.04安装网易云音乐不能打开的问题
  14. 使用计算机管理文件教后反思,《管理计算机中的文件》教学设计
  15. google play电子市场和gmail如何安装在国产手机、三星手机、摩托手机里
  16. 交通大学计算机科学考研_选择计算机科学作为大学专业之前需要知道的事情
  17. git push origin master报错的解决方法 常见git命令(待更新)
  18. BIOS设置中还原coms设定的操作方法
  19. 2021-05-23:打开Office后界面上显示一个横幅提示:你的许可证不是正版,并且你可能是盗版软件的受害者
  20. 笔记本电脑java记事本在哪_系统自带有记事本吗_电脑自带的记事本在哪-win7之家...

热门文章

  1. 实战HTML5与CSS3 第一篇】初探水深,美丽的导航,绚丽的图片爆炸!!
  2. SQL 2000 中如何 纵表变横表
  3. Python基础之补充1
  4. 大一下学期的自我目标
  5. Disabling contextual LOB creation as createClob()
  6. ProtocolBuffer for Objective-C 运行环境配置(真正测试过的)
  7. vnc报错 font catalog is not properly configured
  8. 使用 Moq 测试.NET Core 应用 -- Mock 方法
  9. spring boot 的使用(一)
  10. WPF中路由事件的传播