Skip to content

提示

库版本 vlucas/phpdotenv : ^5.6

phpdotenv是一个流行的 PHP 库,用于加载应用程序的环境变量。用来读取本地环境变量,以及在运行时动态设置环境变量。 源码地址如下,

Dotenv类

Dotenv类是vlucas/phpdotenv库的核心文件,实现了.env 文件的加载和解析逻辑,以及环境变量的设置功能。 查看该类源码,

php
declare(strict_types=1);

namespace Dotenv;

use Dotenv\Exception\InvalidPathException;
use Dotenv\Loader\Loader;
use Dotenv\Loader\LoaderInterface;
use Dotenv\Parser\Parser;
use Dotenv\Parser\ParserInterface;
use Dotenv\Repository\Adapter\ArrayAdapter;
use Dotenv\Repository\Adapter\PutenvAdapter;
use Dotenv\Repository\RepositoryBuilder;
use Dotenv\Repository\RepositoryInterface;
use Dotenv\Store\StoreBuilder;
use Dotenv\Store\StoreInterface;
use Dotenv\Store\StringStore;

class Dotenv
{
    /**
     * The store instance.
     *
     * @var \Dotenv\Store\StoreInterface
     */
    private $store;

    /**
     * The parser instance.
     *
     * @var \Dotenv\Parser\ParserInterface
     */
    private $parser;

    /**
     * The loader instance.
     *
     * @var \Dotenv\Loader\LoaderInterface
     */
    private $loader;

    /**
     * The repository instance.
     *
     * @var \Dotenv\Repository\RepositoryInterface
     */
    private $repository;

    /**
     * 构造函数
     */
    public function __construct(
        StoreInterface $store,
        ParserInterface $parser,
        LoaderInterface $loader,
        RepositoryInterface $repository
    ) {
        $this->store = $store;
        $this->parser = $parser;
        $this->loader = $loader;
        $this->repository = $repository;
    }
    //...
}

该类实例化时需要传入四个参数,分别是StoreInterfaceParserInterfaceLoaderInterfaceRepositoryInterface接口的实现类。 官方示例代码如下,

php
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();

createImmutable方法

打开Dotenv类的createImmutable方法,查看其具体实现逻辑,

php
/**
 * 
 * @var string|string[] $paths 
 * @var null|string|string[] $names 
 * @var bool $shortCircuit 
 * @var null|string $fileEncoding 找到第一个存在的配置文件后停止加载其他配置文件
 * @return Dotenv
 */
public static function createImmutable($paths, $names = null, bool $shortCircuit = true, string $fileEncoding = null)
{
    // createWithDefaultAdapters()返回的是一个RepositoryBuilder实例,然后调用make方法,
    // 返回一个RepositoryInterface接口的实现类AdapterRepository
    $repository = RepositoryBuilder::createWithDefaultAdapters()->immutable()->make();
    // 调用create方法,返回一个Dotenv实例
    return self::create($repository, $paths, $names, $shortCircuit, $fileEncoding);
}

RepositoryBuilder类

查看静态方法createWithDefaultAdapters,

php
public static function createWithDefaultAdapters()
{
    // iterator_to_array 拷贝迭代器中的元素到数组中
    // $adapters该数组变量就会保存DEFAULT_ADAPTERS常量中实例化的类
    $adapters = \iterator_to_array(self::defaultAdapters());
    // 返回一个RepositoryBuilder实例,
    // 这里会触发RepositoryBuilder类的构造函数
    return new self($adapters, $adapters);
}

private static function defaultAdapters()
{
    foreach (self::DEFAULT_ADAPTERS as $adapter) {
        $instance = $adapter::create();
        if ($instance->isDefined()) {
            // 这里会返回DEFAULT_ADAPTERS常量中实例化的类
            // 这里会保存对应的alue,在下面进行迭代时取出
            yield $instance->get();
        }
    }
}
// DEFAULT_ADAPTERS
private const DEFAULT_ADAPTERS = [
    ServerConstAdapter::class,
    EnvConstAdapter::class,
];

首先理解yield关键字,它提供了一种简单的方法来遍历数据,具体可参考PHP yield关键字

immutable方法

该方法只有一行,重新实例化RepositoryBuilder类,并将第三个参数$$immutable值改为true,即创建一个不可变的RepositoryBuilder实例。(键名不可重复,重复抛错)

php
public function immutable()
{
    return new self($this->readers, $this->writers, true, $this->allowList);
}
make方法
php
// 看最后的make方法
$repository = RepositoryBuilder::createWithDefaultAdapters()->immutable()->make();
//
public function make()
{
    // 创建读取器和写入器
    $reader = new MultiReader($this->readers);
    $writer = new MultiWriter($this->writers);
    // 如果设置字段为true,则创建一个不可变的写入器,
    // 当键相同时,则抛出异常
    // 键不能重复
    if ($this->immutable) {
        $writer = new ImmutableWriter($writer, $reader);
    }
    // 限制写入的数据只能是允许列表中的键
    if ($this->allowList !== null) {
        $writer = new GuardedWriter($writer, $this->allowList);
    }
    // 创建AdapterRepository实例,用于读取和写入数据
    return new AdapterRepository($reader, $writer);
}

现在,我们知道了$repository是一个AdapterRepository实例,接下来看create方法。

create方法

查看create方法的实现逻辑,

php
public static function create(RepositoryInterface $repository, $paths, $names = null, bool $shortCircuit = true, string $fileEncoding = null)
{
    $builder = $names === null ? StoreBuilder::createWithDefaultName() : StoreBuilder::createWithNoNames();

    foreach ((array) $paths as $path) {
        $builder = $builder->addPath($path);
    }

    foreach ((array) $names as $name) {
        $builder = $builder->addName($name);
    }

    if ($shortCircuit) {
        $builder = $builder->shortCircuit();
    }

    return new self($builder->fileEncoding($fileEncoding)->make(), new Parser(), new Loader(), $repository);
}