Home  >  Article  >  PHP Framework  >  Introduction to ENV loading and reading under the Laravel framework

Introduction to ENV loading and reading under the Laravel framework

不言
不言forward
2018-10-22 14:17:043693browse

This article brings you an introduction to loading and reading ENV under the Laravel framework. It has certain reference value. Friends in need can refer to it. I hope it will be helpful to you.

Laravel will load the .env file in the project when it starts. It is often useful to have different configurations for different environments in which the application is running. For example, you may want to use the test MySQL database locally and want the project to automatically switch to the production MySQL database after going online. This article will introduce in detail the use of env files and source code analysis.

Use of Env files

Multi-environment env settings

The number of env files in a project is often the same as the number of environments in the project. If a project has development , test, and production environments, then there should be three environment configuration files in the project, .env.dev, .env.test, and .env.prod, corresponding to the environment. The configuration items in the three files should be exactly the same, and the specific configuration values ​​should be set according to the needs of each environment.

The next step is to enable the project to load different env files according to the environment. There are three specific methods, which can be chosen according to usage habits:

Set the APP_ENV environment variable fastcgi_param APP_ENV dev in the nginx configuration file of the environment;

Set the environment variables of the user running PHP on the server , for example, add export APP_ENV dev

in the www user’s /home/www/.bashrc and execute cp .env.dev .env

in the continuous integration task or deployment script of the deployment project. For the first two methods, Laravel will load the corresponding files .env.dev and .env.test based on the variable values ​​loaded by env('APP_ENV'). Specifically, as will be said in the source code later, the third method that is easier to understand is to overwrite the environment's configuration file into the .env file when deploying the project, so that there is no need to make additional settings in the environment's system and nginx.

Customized env file path and file name

The env file is placed in the root directory of the project by default. Laravel provides users with a custom ENV file path or file function,

For example, if you want to customize the env path, you can use the useEnvironmentPath method of the Application instance in app.php in the bootstrap folder:

$app = new Illuminate\Foundation\Application(
    realpath(__DIR__.'/../')
);

$app->useEnvironmentPath('/customer/path')

If you want to customize the env file name, you can use the loadEnvironmentFrom method of the Application instance in app.php in the bootstrap folder:

$app = new Illuminate\Foundation\Application(
    realpath(__DIR__.'/../')
);

$app->loadEnvironmentFrom('customer.env')

Laravel loads ENV configuration

Laravel loads ENV in This is done in the LoadEnvironmentVariables phase of the bootstrap process before the framework handles the request.

Let's take a look at the source code of \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables to analyze how Laravel loads the configuration in 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;
    }
}

In its startup method bootstrap, Laravel will check whether the configuration has been cached and determine which env file should be applied. For the first two of the three methods of loading configuration files based on the environment mentioned above, because APP_ENV is set in the system or nginx environment variable, so Laravel will set the correct specific path of the configuration file according to the value of APP_ENV in the checkForSpecificEnvironmentFile method, such as .env.dev or .env.test, and for the third case, it is the default .env, for details, please refer to the checkForSpecificEnvironmentFile below and the source code of the two methods in the related 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';
    }
}

After determining the path of the configuration file to be read, the next step is to load the env. Configured.

(new Dotenv($app->environmentPath(), $app->environmentFile()))->load();

Laravel uses the PHP version of Dotenv 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();
    }
}

It relies on /Dotenv/Loader to load data:

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

When Loader reads the configuration, the readLinesFromFile function will Use the file function to read the configuration from the file into the array line by line, then exclude the comments starting with #, and call the setEnvironmentVariable method for the lines containing = in the content to configure the environment variables in the file lines into the project. :

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;">When Dotenv instantiates Loader, set the $immutable property of the Loader object to false. When Loader sets the variable, if the variable value is read through the getEnvironmentVariable method, then it will Skip setting this environment variable. So Dotenv will not overwrite existing environment variables by default. This is very important. For example, in the docker container orchestration file, we will set two environment variables for the Mysql container for the PHP application container</span><br>

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

After setting the environment variables in the container, even if the DB_HOST in the env file is homestead and is read using the env function, it will still be the value of the DB_HOST environment variable previously set in the container database (in docker The container link uses the service name by default. In the orchestration file, I set the service name of the mysql container to database, so the php container needs to connect to the mysql container through the database host). Because when we do automated testing in continuous integration, we usually test in the container, so it is very important that Dotenv does not overwrite existing environment variables. This way I can only set the value of the environment variable in the container to complete the test. There is no need to change the env file in the project, just deploy the project to the environment directly after the test is completed.

If you check that the environment variable does not exist, then Dotenv will set the environment variable to the environment through the PHP built-in function putenv, and also store it in the two global variables $_ENV and $_SERVER.

Read env configuration in the project

In the Laravel application, you can use the env() function to read the value of the environment variable, such as obtaining the HOST of the database:

env('DB_HOST`, 'localhost');

The second value passed to the env function is the "default value". If no environment variable exists for the given key, this value will be used.

Let’s take a look at the source code of the env function:

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

It reads environment variables directly through the PHP built-in function getenv.

We saw that when loading configuration and reading configuration, two functions putenv and getenv are used. The environment variables set by putenv only survive during the request, and the previous settings of the environment will be restored after the request ends. Because if the variables_order configuration item in php.ini becomes GPCS and does not contain E, then the environment variables cannot be read through $_ENV in the PHP program, so putenv is used to dynamically set the environment variables so that developers do not need to pay attention to the server configuration. Moreover, the environment variables configured for the running user on the server will be shared with all processes started by the user, which cannot protect private environment variables such as DB_PASSWORD and API_KEY well. Therefore, this configuration can be better protected with putenv settings. For these configuration information, the getenv method can obtain the system's environment variables and the environment variables dynamically set by putenv.

The above is the detailed content of Introduction to ENV loading and reading under the Laravel framework. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:segmentfault.com. If there is any infringement, please contact admin@php.cn delete