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

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


<?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 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属性设置成了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源码学习里,欢迎访问阅读。

原文地址:https://segmentfault.com/a/1190000016760696

Laravel核心解读--ENV的加载和读取的更多相关文章

  1. Laravel核心解读--HTTP内核

    Http Kernel Http Kernel是Laravel中用来串联框架的各个核心组件来网络请求的,简单的说只要是通过public/index.php来启动框架的都会用到Http Kernel,而 ...

  2. 006-spring cloud gateway-GatewayAutoConfiguration核心配置-GatewayProperties初始化加载、Route初始化加载

    一.GatewayProperties 1.1.在GatewayAutoConfiguration中加载 在Spring-Cloud-Gateway初始化时,同时GatewayAutoConfigur ...

  3. Laravel核心解读--Console内核

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

  4. laravel 嵌套的渴求式加载

    今天在通过需求表A查询场地类型表B,然后通过表B的场地类型id去查询表C场地类型名的时候遇到了一个小的问题. 需求表A的字段:id.user_id .name等等: 中间表B的字段:id.appeal ...

  5. 一种laravel特有的serviceProvider的加载方式

    这里的laravel版本5.5. 我是使用到dingo这个包的时候,觉得很奇怪,我们一般的包使用的时候都需要加载一个serviceProvider,提供服务,dingo/api这里也有ServiceP ...

  6. 007-spring cloud gateway-GatewayAutoConfiguration核心配置-RouteDefinition初始化加载

    一.RouteDefinitionLocator 在Spring-Cloud-Gateway的GatewayAutoConfiguration初始化加载中会加载RouteDefinitionLocat ...

  7. jvm源码解读--01 jvm加载java/lang/object过程

    现在做一下记录,这个看了两天,看的过程发现了很多c++的高级特性,没接触过,还得慢慢撸,禁止很慢 那么现在开始 吧 先打两个断点 java.c:351 JavaMain(void * _args) { ...

  8. laravel 关联中的预加载

    预加载 当作为属性访问 Eloquent 关联时,关联数据是「懒加载」的.意味着在你第一次访问该属性时,才会加载关联数据.不过,是当你查询父模型时,Eloquent 可以「预加载」关联数据.预加载避免 ...

  9. Prism 源码解读2-View的加载和控制

    介绍 上一篇介绍了Region,这一篇跟Region息息相关,讲一下Region中View的加载方式及控制. 4.ViewDiscovery 在创建好Region后需要将View添加到Region中. ...

随机推荐

  1. Pyhton学习——Day5

    # s=set('hello')# print(s)## s=set(['alex','alex','sb'])# print(s) # s={1,2,3,4,5,6} #添加# s.add('s') ...

  2. PHP SOAP 使用示例

    soap_client.php <?php try { $client = new SoapClient( null, array('location' =>"http://lo ...

  3. 【RHEL7/CentOS7防火墙之firewall-cmd命令详解】

    目录 Firewalld zone firewall-cmd 开始配置防火墙策略 总结 Redhat Enterprise Linux7已默认使用firewalld防火墙,其管理工具是firewall ...

  4. 【基于mini2440开发板的交叉编译环境及内核树配置.

    在学习linux驱动开发过程中,交叉编译环境的配置及内核树的生成无疑是对linux不是十分了解的新人面前的一堵墙.高高大大的墙...笔者在初探这一方向时,就在这2个问题上苦恼了很久.查阅无数资料,大多 ...

  5. 【Python 学习】通过while循环和for循环猜测年龄

    Python中可以通过while和for循环来限制猜测年龄的次数 1. 在猜测年龄的小程序中,不加循环的代码是这样的: age_of_yu = 23 guess_age = int(input(&qu ...

  6. python--(常用模块-3-正则表达式)

    python--(常用模块-3-正则表达式) 正则表达式是对字符串操作的⼀种逻辑公式. 我们⼀般使⽤正则表达式对字符串进⾏匹 配和过滤. 使⽤正则的优缺点: 优点: 灵活, 功能性强, 逻辑性强. 缺 ...

  7. 推荐几款常用的Eclipse插件

    Eclipse 应该说是老牌也是最常用的Java开发工具,尽管这几年 InstelliJ IDEA 的发展势头很强劲,身边使用和推崇的人也大有人在,但个人而言还是觉有些不太习惯.这里也介绍几款自己常用 ...

  8. 前端的标配:npm是什么及其安装(含cnpm)

    前端的标配:npm是什么及其安装 一:npm是什么及其来源 参考来源:npm是干什么的 总结:不需要去相关的网站下载依赖,用一个工具把这些依赖集中起来管理 NPM 的思路大概是这样的: 1)买个服务器 ...

  9. STM32使用HAL库实现ADC单通道转换

    STM32的ADC转换还是很强大的,它具有多个通道选择,这里我就不细说,不了解的可以自行百度,这里只是选取单通道,实现ADC转换.在文章开始之前,我说一下数据左对齐跟右对齐的差别,以前一直糊里糊涂的, ...

  10. MySql的replace into 语句

    MySQL REPLACE语句介绍 MySQL的REPLACE语句是一个MySQL扩展于SQL标准的语句. 官方定义:REPLACE works exactly like INSERT, except ...