首頁 >php框架 >Laravel >Laravel框架下ENV的載入與讀取的介紹

Laravel框架下ENV的載入與讀取的介紹

不言
不言轉載
2018-10-22 14:17:043741瀏覽

這篇文章帶給大家的內容是關於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中的配置的。

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

<span style="font-family: 微软雅黑, Microsoft YaHei;">Dotenv實例化Loader的時候把Loader物件的$immutable屬性設定成了false,Loader設定變數的時候如果透過getEnvironmentVariable方法讀取到了變數值,那麼就會跳過該環境變數的設定。所以Dotenv預設不會覆寫已經存在的環境變量,這個很關鍵,比如說在docker的容器編排檔案裡,我們會給PHP應用容器設定關於Mysql容器的兩個環境變數</span><br>

    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動態設定的環境變數。

以上是Laravel框架下ENV的載入與讀取的介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:segmentfault.com。如有侵權,請聯絡admin@php.cn刪除