热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Laravel源码解析之ENV配置

Laravel在启动时会加载项目中的.env文件。对于应用程序运行的环境来说,不同的环境有不同的配置通常是很有用的。例如,你可能希望在本地使用测试的My

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

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属性设置成了 false, Loader设置变量的时候如果通过 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动态设置的环境变量。



推荐阅读
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • PHP设置MySQL字符集的方法及使用mysqli_set_charset函数
    本文介绍了PHP设置MySQL字符集的方法,详细介绍了使用mysqli_set_charset函数来规定与数据库服务器进行数据传送时要使用的字符集。通过示例代码演示了如何设置默认客户端字符集。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • 本文介绍了高校天文共享平台的开发过程中的思考和规划。该平台旨在为高校学生提供天象预报、科普知识、观测活动、图片分享等功能。文章分析了项目的技术栈选择、网站前端布局、业务流程、数据库结构等方面,并总结了项目存在的问题,如前后端未分离、代码混乱等。作者表示希望通过记录和规划,能够理清思路,进一步完善该平台。 ... [详细]
  • 在编写业务代码时,常常会遇到复杂的业务逻辑导致代码冗长混乱的情况。为了解决这个问题,可以利用中间件模式来简化代码逻辑。中间件模式可以帮助我们更好地设计架构和代码,提高代码质量。本文介绍了中间件模式的基本概念和用法。 ... [详细]
  • 本文介绍了使用cacti监控mssql 2005运行资源情况的操作步骤,包括安装必要的工具和驱动,测试mssql的连接,配置监控脚本等。通过php连接mssql来获取SQL 2005性能计算器的值,实现对mssql的监控。详细的操作步骤和代码请参考附件。 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • 如何使用Java获取服务器硬件信息和磁盘负载率
    本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • 如何在php中将mysql查询结果赋值给变量
    本文介绍了在php中将mysql查询结果赋值给变量的方法,包括从mysql表中查询count(学号)并赋值给一个变量,以及如何将sql中查询单条结果赋值给php页面的一个变量。同时还讨论了php调用mysql查询结果到变量的方法,并提供了示例代码。 ... [详细]
  • PatchODAX8: ... [详细]
  • 基于SpringBoot打造在线教育系统(6)– 二级分类模块UI篇
    这一节来做二级分类,为了快速开发,一级分类只做新增,暂时不考虑修改和删除,如果一定要删,就去数据库删吧。我们接下来,需要通过一级分类,获取所有的二级分类。开始 ... [详细]
  • 1223  drf引入以及restful规范
    [toc]前后台的数据交互前台安装axios插件,进行与后台的数据交互安装axios,并在main.js中设置params传递拼接参数data携带数据包参数headers中发送头部 ... [详细]
author-avatar
田小多
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有