Laravel框架下ENV的加載和讀取的介紹

本篇文章給大家?guī)?lái)的內(nèi)容是關(guān)于laravel框架下env的加載和讀取的介紹,有一定的參考價(jià)值,有需要的朋友可以參考一下,希望對(duì)你有所幫助。

laravel在啟動(dòng)時(shí)會(huì)加載項(xiàng)目中的.env文件。對(duì)于應(yīng)用程序運(yùn)行的環(huán)境來(lái)說(shuō),不同的環(huán)境有不同的配置通常是很有用的。 例如,你可能希望在本地使用測(cè)試的Mysql數(shù)據(jù)庫(kù)而在上線后希望項(xiàng)目能夠自動(dòng)切換到生產(chǎn)Mysql數(shù)據(jù)庫(kù)。本文將會(huì)詳細(xì)介紹 env 文件的使用與源碼的分析。

Env文件的使用

多環(huán)境env的設(shè)置

項(xiàng)目中env文件的數(shù)量往往是跟項(xiàng)目的環(huán)境數(shù)量相同,假如一個(gè)項(xiàng)目有開(kāi)發(fā)、測(cè)試、生產(chǎn)三套環(huán)境那么在項(xiàng)目中應(yīng)該有三個(gè).env.dev、.env.test、.env.prod三個(gè)環(huán)境配置文件與環(huán)境相對(duì)應(yīng)。三個(gè)文件中的配置項(xiàng)應(yīng)該完全一樣,而具體配置的值應(yīng)該根據(jù)每個(gè)環(huán)境的需要來(lái)設(shè)置。

接下來(lái)就是讓項(xiàng)目能夠根據(jù)環(huán)境加載不同的env文件了。具體有三種方法,可以按照使用習(xí)慣來(lái)選擇使用:

在環(huán)境的nginx配置文件里設(shè)置APP_ENV環(huán)境變量fastcgi_param APP_ENV dev;

設(shè)置服務(wù)器上運(yùn)行PHP的用戶(hù)的環(huán)境變量,比如在www用戶(hù)的/home/www/.bashrc中添加export APP_ENV dev

在部署項(xiàng)目的持續(xù)集成任務(wù)或者部署腳本里執(zhí)行cp .env.dev .env

針對(duì)前兩種方法,Laravel會(huì)根據(jù)env(‘APP_ENV’)加載到的變量值去加載對(duì)應(yīng)的文件.env.dev、.env.test這些。 具體在后面源碼里會(huì)說(shuō),第三種比較好理解就是在部署項(xiàng)目時(shí)將環(huán)境的配置文件覆蓋到.env文件里這樣就不需要在環(huán)境的系統(tǒng)和nginx里做額外的設(shè)置了。

自定義env文件的路徑與文件名

env文件默認(rèn)放在項(xiàng)目的根目錄中,laravel 為用戶(hù)提供了自定義 ENV 文件路徑或文件名的函數(shù),

例如,若想要自定義 env 路徑,可以在 bootstrap 文件夾中 app.php 中使用Application實(shí)例的useEnvironmentPath方法:

$app = new IlluminateFoundationApplication(     realpath(__DIR__.'/../') );  $app->useEnvironmentPath('/customer/path')

若想要自定義 env 文件名稱(chēng),就可以在 bootstrap 文件夾中 app.php 中使用Application實(shí)例的loadEnvironmentFrom方法:

$app = new IlluminateFoundationApplication(     realpath(__DIR__.'/../') );  $app->loadEnvironmentFrom('customer.env')

Laravel 加載ENV配置

Laravel加載ENV的是在框架處理請(qǐng)求之前,bootstrap過(guò)程中的LoadEnvironmentVariables階段中完成的。

我們來(lái)看一下IlluminateFoundationBootstrapLoadEnvironmentVariables的源碼來(lái)分析下Laravel是怎么加載env中的配置的。

<?php namespace IlluminateFoundationBootstrap; use DotenvDotenv; use DotenvExceptionInvalidPathException; use SymfonyComponentConsoleInputArgvInput; use IlluminateContractsFoundationApplication; class LoadEnvironmentVariables {     /**      * Bootstrap the given application.      *      * @param  IlluminateContractsFoundationApplication  $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  IlluminateContractsFoundationApplication  $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  IlluminateContractsFoundationApplication  $app      * @param  string  $file      * @return bool      */     protected function setEnvironmentFilePath($app, $file)     {         if (file_exists($app->environmentPath().'/'.$file)) {             $app->loadEnvironmentFrom($file);              return true;         }          return false;     } }

在他的啟動(dòng)方法bootstrap中,Laravel會(huì)檢查配置是否緩存過(guò)以及判斷應(yīng)該應(yīng)用那個(gè)env文件,針對(duì)上面說(shuō)的根據(jù)環(huán)境加載配置文件的三種方法中的頭兩種,因?yàn)橄到y(tǒng)或者nginx環(huán)境變量中設(shè)置了APP_ENV,所以Laravel會(huì)在checkForSpecificEnvironmentFile方法里根據(jù) APP_ENV的值設(shè)置正確的配置文件的具體路徑, 比如.env.dev或者.env.test,而針對(duì)第三中情況則是默認(rèn)的.env, 具體可以參看下面的checkForSpecificEnvironmentFile還有相關(guān)的Application里的兩個(gè)方法的源碼:

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 IlluminateFoundation; class Application .... {      public function environmentPath()     {         return $this->environmentPath ?: $this->basePath;     }          public function environmentFile()     {         return $this->environmentFile ?: '.env';     } }

判斷好后要讀取的配置文件的路徑后,接下來(lái)就是加載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();     } }

它依賴(lài)/Dotenv/Loader來(lái)加載數(shù)據(jù):

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讀取配置時(shí)readLinesFromFile函數(shù)會(huì)用file函數(shù)將配置從文件中一行行地讀取到數(shù)組中去,然后排除以#開(kāi)頭的注釋?zhuān)槍?duì)內(nèi)容中包含=的行去調(diào)用setEnvironmentVariable方法去把文件行中的環(huán)境變量配置到項(xiàng)目中去:

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實(shí)例化Loader的時(shí)候把Loader對(duì)象的$immutable屬性設(shè)置成了false,Loader設(shè)置變量的時(shí)候如果通過(guò)getEnvironmentVariable方法讀取到了變量值,那么就會(huì)跳過(guò)該環(huán)境變量的設(shè)置。所以Dotenv默認(rèn)情況下不會(huì)覆蓋已經(jīng)存在的環(huán)境變量,這個(gè)很關(guān)鍵,比如說(shuō)在docker的容器編排文件里,我們會(huì)給PHP應(yīng)用容器設(shè)置關(guān)于Mysql容器的兩個(gè)環(huán)境變量

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

這樣在容器里設(shè)置好環(huán)境變量后,即使env文件里的DB_HOST為homestead用env函數(shù)讀取出來(lái)的也還是容器里之前設(shè)置的DB_HOST環(huán)境變量的值database(docker中容器鏈接默認(rèn)使用服務(wù)名稱(chēng),在編排文件中我把mysql容器的服務(wù)名稱(chēng)設(shè)置成了database, 所以php容器要通過(guò)database這個(gè)host來(lái)連接mysql容器)。因?yàn)橛梦覀冊(cè)诔掷m(xù)集成中做自動(dòng)化測(cè)試的時(shí)候通常都是在容器里進(jìn)行測(cè)試,所以Dotenv不會(huì)覆蓋已存在環(huán)境變量這個(gè)行為就相當(dāng)重要這樣我就可以只設(shè)置容器里環(huán)境變量的值完成測(cè)試而不用更改項(xiàng)目里的env文件,等到測(cè)試完成后直接去將項(xiàng)目部署到環(huán)境上就可以了。

如果檢查環(huán)境變量不存在那么接著Dotenv就會(huì)把環(huán)境變量通過(guò)PHP內(nèi)建函數(shù)putenv設(shè)置到環(huán)境中去,同時(shí)也會(huì)存儲(chǔ)到$_ENV和$_SERVER這兩個(gè)全局變量中。

在項(xiàng)目中讀取env配置

在Laravel應(yīng)用程序中可以使用env()函數(shù)去讀取環(huán)境變量的值,比如獲取數(shù)據(jù)庫(kù)的HOST:

env('DB_HOST`, 'localhost');

傳遞給 env 函數(shù)的第二個(gè)值是「默認(rèn)值」。如果給定的鍵不存在環(huán)境變量,則會(huì)使用該值。

我們來(lái)看看env函數(shù)的源碼:

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

它直接通過(guò)PHP內(nèi)建函數(shù)getenv讀取環(huán)境變量。

我們看到了在加載配置和讀取配置的時(shí)候,使用了putenv和getenv兩個(gè)函數(shù)。putenv設(shè)置的環(huán)境變量只在請(qǐng)求期間存活,請(qǐng)求結(jié)束后會(huì)恢復(fù)環(huán)境之前的設(shè)置。因?yàn)槿绻鹥hp.ini中的variables_order配置項(xiàng)成了 GPCS不包含E的話,那么php程序中是無(wú)法通過(guò)$_ENV讀取環(huán)境變量的,所以使用putenv動(dòng)態(tài)地設(shè)置環(huán)境變量讓開(kāi)發(fā)人員不用去關(guān)注服務(wù)器上的配置。而且在服務(wù)器上給運(yùn)行用戶(hù)配置的環(huán)境變量會(huì)共享給用戶(hù)啟動(dòng)的所有進(jìn)程,這就不能很好的保護(hù)比如DB_PASSWORD、API_KEY這種私密的環(huán)境變量,所以這種配置用putenv設(shè)置能更好的保護(hù)這些配置信息,getenv方法能獲取到系統(tǒng)的環(huán)境變量和putenv動(dòng)態(tài)設(shè)置的環(huán)境變量。

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點(diǎn)贊7 分享