Laravel Core Interpretation--ENV Loading and Reading

Laravel loads the. env file in the project at startup. It is often useful for applications to run in different environments with different configurations. For example, you might want to use the tested Mysql database locally and switch the project to the production Mysql database automatically when it comes online. This article will introduce the use of Env file and source code analysis in detail.

Use of Env files

Settings of multi-environment env

The number of Env files in a project is usually the same as that of the environment. If a project has three environments of development, testing and production, there should be three env.dev, env.test and env.prod files 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. Specifically, there are three methods, which can be chosen according to the usage habits:

  • Set the APP_ENV environment variable fastcgi_param APP_ENV dev in the nginx configuration file of the environment.
  • Set environment variables for users running PHP on the server, such as adding export APP_ENV dev to WWW user's/home/www/.bashrc
  • Execute cp.env.dev.env in the continuous integration task or deployment script of the deployment project

For the first two methods, Laravel loads the corresponding files. env.dev,. env.test according to the variable values loaded by env('APP_ENV'). Specifically, in the later source code, it will be said that the third better understanding is to overwrite the environment configuration file into. env file when deploying the project so that no additional settings need to be made in the environment system and nginx.

Customize the path and file name of env file

The env file is placed in the project's root directory by default, and laravel provides users with functions to customize the ENV file path or file name.

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 name of the env file, 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 Load ENV Configuration

Laravel loads ENV in the Load Environment Variables phase of the bootstrap process before the framework processes the request.

Let's look at the source code of Illuminate Foundation Bootstrap Load Environment Variables 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 his bootstrap startup method, Laravel checks whether the configuration has been cached and decides which env file should be applied. For the first two of the three methods mentioned above, loading the configuration file according to the environment, Laravel will set the correct value according to the value of APP_ENV in the checkForSpecific Environment File method because APP_ENV is set in the system or nginx environment variable. The specific path of the configuration file, such as. env.dev or. env.test, is default for the third case. env. See the checkForSpecific EnvironmentFile below and the source code for the two methods in the 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 read, the next step is to load the configuration in env.

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

Laravel uses Dotenv's PHP version 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 uses the file function to read the configuration from the file line to line in the array, then excludes the annotations beginning with #, and calls the setEnvironmentVariable method to configure the environment variables in the file line to the project for the lines containing = in the content:

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

When Dotenv instantiates Loader, it sets the $immutable property of Loader object to false. When Loader sets a variable, if it reads the value of the variable through the getEnvironmentVariable method, it skips setting the environment variable. So by default, Dotenv does not override existing environment variables, which is critical. For example, in docker's container orchestration file, we will set two environment variables for PHP application container about Mysql container.

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

After setting the environment variable in the container, even if DB_HOST in the env file is read out by env function for homestead, it is still the value database of DB_HOST environment variable that was set before in the container (container links in docker default use service name, I set the service name of mysql container to database in the orchestration file, so php container should be connected through database host. Connect the mysql container. Because we usually test in containers when we do automated tests in continuous integration, it is important that Dotenv does not override existing environment variables so that I can only set the values of environment variables in containers to complete the test without changing the env files in the project, and then deploy the project directly to the environment after the test is completed.

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

Read the env configuration in the project

In Laravel applications, env() functions can be used to read the values of environment variables, such as HOST of the database:

env('DB_HOST`, 'localhost');

The second value passed to the env function is the default value. If the given key does not have an environment variable, the value is used.

Let's 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 see that putenv and getenv functions are used when loading and reading configurations. The environment variables set by putenv only survive during the request, and the settings before the environment are restored after the request is completed. Because if the variables_order configuration item in php.ini is not included in GPCS E, environment variables cannot be read by $_ENV in PHP programs, so putenv is used to dynamically set environment variables so that developers do not have to pay attention to the configuration on the server. Moreover, the environment variables configuring the running users on the server will share with all the processes initiated by the users, which can not protect such private environment variables as DB_PASSWORD and API_KEY very well. Therefore, putenv settings can better protect these configuration information. The getenv method can obtain the environment variables of the system and envput dynamic settings of environment variables.

Keywords: PHP Laravel Database MySQL

Added by kdidymus on Fri, 17 May 2019 12:59:48 +0300