php Lumen框架加载env过程详解

100人浏览   2024-11-23 09:32:58

Lumen框架加载env的过程可以分为三步,第一步、读取env,第二步、将env数据放在系统环境变量中,第三步、释放环境变量

第一步、读取env

从程序入口中的index.php中可以看到,在lumen框架启动的时候先包含了bootstrap.php文件,而在bootstrap.php文件中完成了对env文件的读取。整个过程可以描述为index.php->bootstrap.php。

图一:lumen框架入口文件


图二:入口文件包含的bootstrap.php文件

Dotenv源码分析

dotenv是一个第三方的扩展包,从目前的功能来看应该就是做env的load操作。可以在composer.json里面看到确实也引入了它。在vendor包中,可以看到他的源码目录结构,文件很少。看起来功能也没多少。我们可以顺着它去找一下最终的核心源码。

图三:composer.json文件

图四:vendor包中phpdotenv目录

在Loader.php中可以看到主要的业务逻辑代码,基本的逻辑过程可以描述为这样:首先检测env文件是否存在,如果不存在,则抛出InvalidPathException异常。然后去调用readLinesFromFile方法去读取文件。readLinesFromFile就是lumen读取env文件的核心方法。

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;
    }
protected function readLinesFromFile($filePath)
    {
        // Read file into an array of lines with auto-detected line endings
        $autodetect = ini_get('auto_detect_line_endings');
        ini_set('auto_detect_line_endings', '1');
        $lines = file($filePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        ini_set('auto_detect_line_endings', $autodetect);
        return $lines;
    }
  • readLinesFromFile方法分析

第一步、是获取php文件分隔符的配置。

第二步、将文件分隔符的值设置为1。

第三步、通过PHP原生函数file将env的内容读取到一个数组中。

第四步、将PHP文件分割符配置恢复。

第五步、将数据返回。

现在有几个问题需要解释一下。1.为什么要获取文件分割符然后又恢复。2.文件分隔符配置的值为什么要设置为1。

首先来回答第一个问题,auto_detect_line_endings配置在设置为1时,PHP将检查通过fgets()和file取得的数据中行结束符号是否符合Unix,MS-DOS,还是Macintosh的习惯。这使得PHP和Macintosh系统交互操作,但默认值是0,因为在检测第一行的EOL习惯时会有很小的性能损失。而且在Unix系统下使用回车符号作为项目分隔符的人们会遭遇向下不兼容的行为。

第二个问题,在读取env的时候为什么要设置为1,env作为一个配置性的文件,如果通过ftp等方式由一个Windows电脑上传到Linux服务器时,文件内容格式会因为兼容问题而使得env的内容无法正常读取为正确的配置数组,因此需要设置这个检测,将env的内容正确的读取。下面的图示为在Unix中读取env内容和在Windows中读取env内容的区别(编辑器可以设置文件分割方式,通过模拟的方式展示的):

图五:文件分隔符设置为\n分割

图六:文件分隔符为\n时获取的env内容

图七:将env的分隔符改为\r

图八:将env文件分隔符改为\r后读取到的env内容

第二步、将env数据放在系统变量中

在获取到env的数组之后,通过循环遍历数组进行setEnvironmentVariable()方法,进行系统变量的设置。在关键方法setEnvironmentVariable中,可以看到最终的设置名称和值通过apache_setenv和putenv原生方法分别对设置Apache子进程环境变量和系统环境变量。

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;
    }
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) !== false) {
            apache_setenv($name, $value);
        }

        if (function_exists('putenv')) {
            putenv("$name=$value");
        }

        $_ENV[$name] = $value;
        $_SERVER[$name] = $value;
    }

一个比较有趣的事情是有时候我们的env在设置的时候存在特殊字符的时候会有不生效的情况,发生这种情况的原因大概率是和写入环境变量的时候的正则表达式有冲突,有些字符没有被匹配到导致的。核心方法如下:

protected function resolveNestedVariables($value)
    {
        if (strpos($value, '$') !== false) {
            $loader = $this;
            $value = preg_replace_callback(
                '/\${([a-zA-Z0-9_.]+)}/',
                function ($matchedPatterns) use ($loader) {
                    $nestedVariable = $loader->getEnvironmentVariable($matchedPatterns[1]);
                    if ($nestedVariable === null) {
                        return $matchedPatterns[0];
                    } else {
                        return $nestedVariable;
                    }
                },
                $value
            );
        }

        return $value;
    }

第三步、释放环境变量

在PHP官方手册中,对putenv方法的解释是这样的:添加 setting 到服务器环境变量。 环境变量仅存活于当前请求期间。 在请求结束时环境会恢复到初始状态。也就是当一次请求结束,那么这些设置的环境变量就会被释放。在本请求期间,可以通过getenv、apache_getenv、$_ENV等方式拿到env文件中配置的内容。在PHP源码中可以看到putenv最终的方法,确实是调用了efree来释放设置的变量。

#if defined(HAVE_PUTENV)
static void php_putenv_destructor(zval *zv) /* {{{ */
{
	putenv_entry *pe = Z_PTR_P(zv);

	if (pe->previous_value) {
# if defined(PHP_WIN32)
		/* MSVCRT has a bug in putenv() when setting a variable that
		 * is already set; if the SetEnvironmentVariable() API call
		 * fails, the Crt will double free() a string.
		 * We try to avoid this by setting our own value first */
		SetEnvironmentVariable(pe->key, "bugbug");
# endif
		putenv(pe->previous_value);
# if defined(PHP_WIN32)
		efree(pe->previous_value);
# endif
	} else {
# if HAVE_UNSETENV
		unsetenv(pe->key);
# elif defined(PHP_WIN32)
		SetEnvironmentVariable(pe->key, NULL);
# ifndef ZTS
		_putenv_s(pe->key, "");
# endif
# else
		char **env;

		for (env = environ; env != NULL && *env != NULL; env++) {
			if (!strncmp(*env, pe->key, pe->key_len) && (*env)[pe->key_len] == '=') {	/* found it */
				*env = "";
				break;
			}
		}
# endif
	}
#ifdef HAVE_TZSET
	/* don't forget to reset the various libc globals that
	 * we might have changed by an earlier call to tzset(). */
	if (!strncmp(pe->key, "TZ", pe->key_len)) {
		tzset();
	}
#endif

	efree(pe->putenv_string);
	efree(pe->key);
	efree(pe);
}
/* }}} */
#endif

相关推荐