2019-06-10

phpdotenvコードリーディング

phpdotenvのコードリーディングをしました。バージョンは3.3.3です。

このケースで追ってみます

<?php

require 'vendor/autoload.php'
$dotenv = Dotenv\Dotenv::create(__DIR__);
$dotenv->load();

echo getenv('FOO');
echo $_ENV('BAR');
echo $_SERVER('BAZ');

コードリーディング

Dotenv\Dotenv::createは以下のようにLoaderのインスタンスを生成し、それを引数にDotenv\Dotenvオブジェクトを生成して返します。

class Dotenv
{
    public static function create($paths, $file = null, FactoryInterface $envFactory = null)
    {
        $loader = new Loader(
            self::getFilePaths((array) $paths, $file ?: '.env'),
            $envFactory ?: new DotenvFactory(),
            true
        );
        return new self($loader);
    }

Dotenv\Dotenv#loadDotenv\Loader#setImmutatableしたLoaderインスタンスの#loadをコールします。

class Dotenv
{
    public function load()
    {
        return $this->loadData();
    }
    
    protected function loadData($overload = false)
    {
        return $this->loader->setImmutable(!$overload)->load();
    }

LoaderのsetImmutableは以下のような実装になっていて、envVariablesにDotenv\DotenvFactory#createImmutableで生成したDotenv\DotenvVariablesオブジェクトをセットします。

class Loader
{
    public function setImmutable($immutable = false)
    {
        $this->envVariables = $immutable
            ? $this->envFactory->createImmutable()
            : $this->envFactory->create();
        return $this;
    }

Loader#loadはパス上のenvファイルをfile_get_contentsで読み込み、Lines::processで環境変数を書き込んだ各行をstringのArrayにして返します。

class Loader
{
    public function load()
    {
        return $this->loadDirect(
            self::findAndRead($this->filePaths)
        );
    }
    
    public function loadDirect($content)
    {
        return $this->processEntries(
            Lines::process(preg_split("/(\r\n|\n|\r)/", $content))
        );
    }

    private static function findAndRead(array $filePaths)
    {
        if ($filePaths === []) {
            throw new InvalidPathException('At least one environment file path must be provided.');
        }
        foreach ($filePaths as $filePath) {
            $lines = self::readFromFile($filePath);
            if ($lines->isDefined()) {
                return $lines->get();
            }
        }
        throw new InvalidPathException(
            sprintf('Unable to read any of the environment file(s) at [%s].', implode(', ', $filePaths))
        );
    }

processEntriesでは各行(エントリ)をDotenv\Parser::parseでパースしてキーバリューに分解してLoader#setEnvironmentVariableで環境変数にセットします。

class Loader
{
    private function processEntries(array $entries)
    {
        $vars = [];
        foreach ($entries as $entry) {
            list($name, $value) = Parser::parse($entry);
            $vars[$name] = $this->resolveNestedVariables($value);
            $this->setEnvironmentVariable($name, $vars[$name]);
        }
        return $vars;
    }

setEnvironmentVariableDotenv\DotenvVariables#setを呼び出します。 Dotenv\DotenvVariables#setは以下のように実装されています。

class DotenvVariables
{
    public function set($name, $value = null)
    {
        if (!is_string($name)) {
            throw new InvalidArgumentException('Expected name to be a string.');
        }
        // Don't overwrite existing environment variables if we're immutable
        // Ruby's dotenv does this with `ENV[key] ||= value`.
        if ($this->isImmutable() && $this->get($name) !== null) {
            return;
        }
        foreach ($this->adapters as $adapter) {
            $adapter->set($name, $value);
        }
    }

$adapter->set($name, $value);のところで実際に環境変数にセットしています。
adapterは以下の4種類が実装されています。

EnvConstやServerConstはそれぞれ$_ENV, $_SERVERに値をセットするアダプターです。
ApacheAdapterはapache\_{get,set}env関数をPutenvAdapterは{get,put}env関数を呼び出して環境変数をセットします。
ArrayAdapterはAdapter内の変数格納用のarrayに環境変数のデータを入れていてデータを出し入れすることになります。

DotenvFactoryではデフォルトで{Apache,EnvConst,Putenv,Server}Adapterが利用されます

class DotenvFactory
{
    public function __construct(array $adapters = null)
    {
        $this->adapters = array_filter($adapters === null ? [new ApacheAdapter(), new EnvConstAdapter(), new ServerConstAdapter(), new PutenvAdapter()] : $adapters, function (AdapterInterface $adapter) {
            return $adapter->isSupported();
        });
    }
このエントリーをはてなブックマークに追加