ThinkPHP5.0 env配置文件加载分析


ThinkPHP5.0 env配置文件加载分析


正文

引导

打开ThinkPHP5.0网站根目录,新建 .env 文件,写入变量配置(不能使用中文):

name=Lee

[array]
columnname = name

读取环境配置:

var_dump(\think\Env::get('name'));
var_dump(\think\Env::get('notexist','default'));
var_dump(\think\Env::get('array.columnname'));

加载分析

入口脚本 index.php :

<?php
// [ 应用入口文件 ]
// 定义应用目录
define('APP_PATH', __DIR__ . '/../application/');

// 判断是否安装
if (!is_file(APP_PATH . 'admin/command/Install/install.lock')) {
    header("location:./install.php");
    exit;
}

// 加载框架引导文件
require __DIR__ . '/../thinkphp/start.php';

入口脚本中包含了一个 start.php 文件:

<?php
namespace think;

// ThinkPHP 引导文件
// 1. 加载基础文件
require __DIR__ . '/base.php';

// 2. 执行应用
App::run()->send();

这个文件中又包含了一个 base.php 文件:

<?php

define('THINK_VERSION', '5.0.24');
define('THINK_START_TIME', microtime(true));
define('THINK_START_MEM', memory_get_usage());
define('EXT', '.php');
define('DS', DIRECTORY_SEPARATOR);
defined('THINK_PATH') or define('THINK_PATH', __DIR__ . DS);
define('LIB_PATH', THINK_PATH . 'library' . DS);
define('CORE_PATH', LIB_PATH . 'think' . DS);
define('TRAIT_PATH', LIB_PATH . 'traits' . DS);
defined('APP_PATH') or define('APP_PATH', dirname($_SERVER['SCRIPT_FILENAME']) . DS);
defined('ROOT_PATH') or define('ROOT_PATH', dirname(realpath(APP_PATH)) . DS);
defined('EXTEND_PATH') or define('EXTEND_PATH', ROOT_PATH . 'extend' . DS);
defined('VENDOR_PATH') or define('VENDOR_PATH', ROOT_PATH . 'vendor' . DS);
defined('RUNTIME_PATH') or define('RUNTIME_PATH', ROOT_PATH . 'runtime' . DS);
defined('LOG_PATH') or define('LOG_PATH', RUNTIME_PATH . 'log' . DS);
defined('CACHE_PATH') or define('CACHE_PATH', RUNTIME_PATH . 'cache' . DS);
defined('TEMP_PATH') or define('TEMP_PATH', RUNTIME_PATH . 'temp' . DS);
defined('CONF_PATH') or define('CONF_PATH', APP_PATH); // 配置文件目录
defined('CONF_EXT') or define('CONF_EXT', EXT); // 配置文件后缀
defined('ENV_PREFIX') or define('ENV_PREFIX', 'PHP_'); // 环境变量的配置前缀

// 环境常量
define('IS_CLI', PHP_SAPI == 'cli' ? true : false);
define('IS_WIN', strpos(PHP_OS, 'WIN') !== false);

// 载入Loader类
require CORE_PATH . 'Loader.php';

// 加载环境变量配置文件
if (is_file(ROOT_PATH . '.env')) {
    $env = parse_ini_file(ROOT_PATH . '.env', true);  // 将.env文件解析成二维数组

    foreach ($env as $key => $val) {
        $name = ENV_PREFIX . strtoupper($key);

        if (is_array($val)) {
            foreach ($val as $k => $v) {
                $item = $name . '_' . strtoupper($k);
                putenv("$item=$v");  // 将这些文件中的参数添加一些前缀转大写之后放入到系统的环境变量中,后台通过Env函数获取的时候就可以直接通过getenv()获取了
            }
        } else {
            putenv("$name=$val");
        }
    }
}

// 注册自动加载
\think\Loader::register();

// 注册错误和异常处理机制
\think\Error::register();

// 加载惯例配置文件
\think\Config::set(include THINK_PATH . 'convention' . EXT);

Env.php 文件内容:

<?php

namespace think;

class Env
{
    /**
     * 获取环境变量值
     * @access public
     * @param  string $name    环境变量名(支持二级 . 号分割)
     * @param  string $default 默认值
     * @return mixed
     */
    public static function get($name, $default = null)
    {
        $result = getenv(ENV_PREFIX . strtoupper(str_replace('.', '_', $name)));

        if (false !== $result) {
            if ('false' === $result) {
                $result = false;
            } elseif ('true' === $result) {
                $result = true;
            }

            return $result;
        }

        return $default;
    }
}

简单解释:

// 解析一个与 php.ini 结构类似的文件,返回一个数组
parse_ini_file()

// 设置环境变量,通过这个函数设置了环境变量,我们就可以通过getenv()获取了
putenv()

// 获取环境变量
getenv()

parse_ini_file()getenv()putenv() 是 PHP 原生函数:

/**
 * Parse a configuration file
 * @link http://php.net/manual/en/function.parse-ini-file.php
 * @param string $filename <p>
 * The filename of the ini file being parsed.
 * </p>
 * @param bool $process_sections [optional] <p>
 * By setting the process_sections
 * parameter to true, you get a multidimensional array, with
 * the section names and settings included. The default
 * for process_sections is false 
 * </p>
 * @param int $scanner_mode [optional] <p>
 * Can either be INI_SCANNER_NORMAL (default) or 
 * INI_SCANNER_RAW. If INI_SCANNER_RAW 
 * is supplied, then option values will not be parsed.
 * </p>
 * <p>
 * As of PHP 5.6.1 can also be specified as <strong><code>INI_SCANNER_TYPED</code></strong>.
 * In this mode boolean, null and integer types are preserved when possible.
 * String values <em>"true"</em>, <em>"on"</em> and <em>"yes"</em>
 * are converted to <b>TRUE</b>. <em>"false"</em>, <em>"off"</em>, <em>"no"</em>
 * and <em>"none"</em> are considered <b>FALSE</b>. <em>"null"</em> is converted to <b>NULL</b>
 * in typed mode. Also, all numeric strings are converted to integer type if it is possible.
 * </p>
 * @return array|bool The settings are returned as an associative array on success,
 * and false on failure.
 * @since 4.0
 * @since 5.0
 */
function parse_ini_file ($filename, $process_sections = false, $scanner_mode = INI_SCANNER_NORMAL) {}

/**
 * Gets the value of an environment variable
 * @link http://php.net/manual/en/function.getenv.php
 * @param string $varname [optional] <p>
 * The variable name.
 * </p>
 * @param bool $local_only [optional] <p>
 * Set to true to only return local environment variables (set by the operating system or putenv).
 * </p>
 * @return string|array|false the value of the environment variable
 * varname or an associative array with all environment variables if no variable name
 * is provided, or false on an error.
 * @since 4.0
 * @since 5.5.38 The local_only parameter has been added.
 * @since 5.6.24 The local_only parameter has been added.
 * @since 7.0.9 The local_only parameter has been added.
 * @since 7.1 The varname parameter was made optional.
 */
function getenv ($varname = null, $local_only = false) {}

/**
 * Sets the value of an environment variable
 * @link http://php.net/manual/en/function.putenv.php
 * @param string $setting <p>
 * The setting, like "FOO=BAR"
 * </p>
 * @return bool true on success or false on failure.
 * @since 4.0
 * @since 5.0
 */
function putenv ($setting) {}

parse_ini_file

实例 1

“test.ini” 的内容:

[names]
me = Robert
you = Peter

[urls]
first = "http://www.example.com"
second = "https://www.runoob.com" 

PHP 代码:

<?php
print_r(parse_ini_file("test.ini"));
?> 

上面的代码将输出:

Array
(
    [me] => Robert
    [you] => Peter
    [first] => http://www.example.com
    [second] => https://www.runoob.com
) 

实例 2

“test.ini” 的内容:

[names]
me = Robert
you = Peter

[urls]
first = "http://www.example.com"
second = "http://www.w3cschool.cc"

PHP 代码(process_sections 设置为 true):

<?php
print_r(parse_ini_file("test.ini", true));
?>

上面的代码将输出:

Array
(
    [names] => Array
    (
        [me] => Robert
        [you] => Peter
    )
    [urls] => Array
    (
        [first] => http://www.example.com
        [second] => http://www.w3cschool.cc
    )
) 

存在的问题

putenv() 是非线程安全的,多线程环境下有 bug。

新开一个线程,存入的环境变量没复制过去,导致新线程中getenv()获取不到指定的环境变量的值。

解决办法

不使用PHP原生的函数putenv()getenv(),而是保存在项目运行时的自定义变量中。

新开线程时,把运行环境中的变量都复制过去,看一个示例类:

<?php

class Env
{
    protected static $data;
    
    public static function load($file)
    {
        if (!is_file($file)) {
            return;
        }
        
        $env = parse_ini_file($file, true);
        static::set($env);
    }
    
    protected static function set($env = [])
    {
        foreach ($env as $key => $val) {
            $name = strtoupper($key);
            
            if (is_array($val)) {
                foreach ($val as $k => $v) {
                    $item = $name . '_' . strtoupper($k);
                    static::$data[$item] = $v;
                }
            } else {
                static::$data[$name] = $val;
            }
        }
    }
    
    public static function get($name, $default = null)
    {
        $name = strtoupper(str_replace('.', '_', $name));
        
        if (!isset(static::$data[$name])) {
            return $default;
        }
        
        $result = static::$data[$name];
        if (false !== $result) {
            if ('false' === $result) {
                $result = false;
            } elseif ('true' === $result) {
                $result = true;
            }
        }

        return $result;
    }
}






参考资料

thinkphp环境变量.env配置 https://blog.csdn.net/blank__box/article/details/79773941

解析tp5中.env配置文件的加载原理 https://blog.csdn.net/weixin_37825371/article/details/104995543

PHP parse_ini_file() 函数 https://www.runoob.com/php/func-filesystem-parse-ini-file.html

Laravel 配置:数组类型的环境变量 https://learnku.com/laravel/wikis/25999

laravel .env文件的使用 https://www.cnblogs.com/starluke/p/11979178.html

在 Yii2 中使用 .env 文件配置 https://blog.csdn.net/viva_la_free/article/details/123992769

Yii2 使用 .env 来配置项目环境变量 https://blog.csdn.net/weixin_33831196/article/details/92326006

Yii自定义加载配置文件 https://www.csdn.net/tags/MtjaUgxsNjk2MzAtYmxvZwO0O0OO0O0O.html

新版webman,取消env的原因是什么? https://www.workerman.net/q/7534


返回