phpdotenv/composer.json000064400000003175150247724300011307 0ustar00{ "name": "vlucas/phpdotenv", "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", "keywords": ["env", "dotenv", "environment"], "license": "BSD-3-Clause", "authors": [ { "name": "Graham Campbell", "email": "hello@gjcampbell.co.uk", "homepage": "https://github.com/GrahamCampbell" }, { "name": "Vance Lucas", "email": "vance@vancelucas.com", "homepage": "https://github.com/vlucas" } ], "require": { "php": "^7.1.3 || ^8.0", "ext-pcre": "*", "graham-campbell/result-type": "^1.0.2", "phpoption/phpoption": "^1.8", "symfony/polyfill-ctype": "^1.23", "symfony/polyfill-mbstring": "^1.23.1", "symfony/polyfill-php80": "^1.23.1" }, "require-dev": { "ext-filter": "*", "bamarni/composer-bin-plugin": "^1.4.1", "phpunit/phpunit": "^7.5.20 || ^8.5.30 || ^9.5.25" }, "autoload": { "psr-4": { "Dotenv\\": "src/" } }, "autoload-dev": { "psr-4": { "Dotenv\\Tests\\": "tests/Dotenv/" } }, "suggest": { "ext-filter": "Required to use the boolean validator." }, "config": { "allow-plugins": { "bamarni/composer-bin-plugin": true }, "preferred-install": "dist" }, "extra": { "bamarni-bin": { "bin-links": true, "forward-command": true }, "branch-alias": { "dev-master": "5.5-dev" } } } phpdotenv/src/Parser/Entry.php000064400000001766150247724300012426 0ustar00name = $name; $this->value = $value; } /** * Get the entry name. * * @return string */ public function getName() { return $this->name; } /** * Get the entry value. * * @return \PhpOption\Option<\Dotenv\Parser\Value> */ public function getValue() { /** @var \PhpOption\Option<\Dotenv\Parser\Value> */ return Option::fromValue($this->value); } } phpdotenv/src/Parser/ParserInterface.php000064400000000516150247724300014372 0ustar00 */ public static function lex(string $content) { static $regex; if ($regex === null) { $regex = '(('.\implode(')|(', self::PATTERNS).'))A'; } $offset = 0; while (isset($content[$offset])) { if (!\preg_match($regex, $content, $matches, 0, $offset)) { throw new \Error(\sprintf('Lexer encountered unexpected character [%s].', $content[$offset])); } $offset += \strlen($matches[0]); yield $matches[0]; } } } phpdotenv/src/Parser/Lines.php000064400000006113150247724300012366 0ustar00map(static function () use ($line) { return self::looksLikeMultilineStop($line, true) === false; })->getOrElse(false); } /** * Determine if the given line can be the start of a multiline variable. * * @param string $line * @param bool $started * * @return bool */ private static function looksLikeMultilineStop(string $line, bool $started) { if ($line === '"') { return true; } return Regex::occurrences('/(?=([^\\\\]"))/', \str_replace('\\\\', '', $line))->map(static function (int $count) use ($started) { return $started ? $count > 1 : $count >= 1; })->success()->getOrElse(false); } /** * Determine if the line in the file is a comment or whitespace. * * @param string $line * * @return bool */ private static function isCommentOrWhitespace(string $line) { $line = \trim($line); return $line === '' || (isset($line[0]) && $line[0] === '#'); } } phpdotenv/src/Parser/EntryParser.php000064400000030240150247724300013570 0ustar00 */ public static function parse(string $entry) { return self::splitStringIntoParts($entry)->flatMap(static function (array $parts) { [$name, $value] = $parts; return self::parseName($name)->flatMap(static function (string $name) use ($value) { /** @var Result */ $parsedValue = $value === null ? Success::create(null) : self::parseValue($value); return $parsedValue->map(static function (?Value $value) use ($name) { return new Entry($name, $value); }); }); }); } /** * Split the compound string into parts. * * @param string $line * * @return \GrahamCampbell\ResultType\Result */ private static function splitStringIntoParts(string $line) { /** @var array{string,string|null} */ $result = Str::pos($line, '=')->map(static function () use ($line) { return \array_map('trim', \explode('=', $line, 2)); })->getOrElse([$line, null]); if ($result[0] === '') { /** @var \GrahamCampbell\ResultType\Result */ return Error::create(self::getErrorMessage('an unexpected equals', $line)); } /** @var \GrahamCampbell\ResultType\Result */ return Success::create($result); } /** * Parse the given variable name. * * That is, strip the optional quotes and leading "export" from the * variable name. We wrap the answer in a result type. * * @param string $name * * @return \GrahamCampbell\ResultType\Result */ private static function parseName(string $name) { if (Str::len($name) > 8 && Str::substr($name, 0, 6) === 'export' && \ctype_space(Str::substr($name, 6, 1))) { $name = \ltrim(Str::substr($name, 6)); } if (self::isQuotedName($name)) { $name = Str::substr($name, 1, -1); } if (!self::isValidName($name)) { /** @var \GrahamCampbell\ResultType\Result */ return Error::create(self::getErrorMessage('an invalid name', $name)); } /** @var \GrahamCampbell\ResultType\Result */ return Success::create($name); } /** * Is the given variable name quoted? * * @param string $name * * @return bool */ private static function isQuotedName(string $name) { if (Str::len($name) < 3) { return false; } $first = Str::substr($name, 0, 1); $last = Str::substr($name, -1, 1); return ($first === '"' && $last === '"') || ($first === '\'' && $last === '\''); } /** * Is the given variable name valid? * * @param string $name * * @return bool */ private static function isValidName(string $name) { return Regex::matches('~(*UTF8)\A[\p{Ll}\p{Lu}\p{M}\p{N}_.]+\z~', $name)->success()->getOrElse(false); } /** * Parse the given variable value. * * This has the effect of stripping quotes and comments, dealing with * special characters, and locating nested variables, but not resolving * them. Formally, we run a finite state automaton with an output tape: a * transducer. We wrap the answer in a result type. * * @param string $value * * @return \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Value,string> */ private static function parseValue(string $value) { if (\trim($value) === '') { /** @var \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Value,string> */ return Success::create(Value::blank()); } return \array_reduce(\iterator_to_array(Lexer::lex($value)), static function (Result $data, string $token) { return $data->flatMap(static function (array $data) use ($token) { return self::processToken($data[1], $token)->map(static function (array $val) use ($data) { return [$data[0]->append($val[0], $val[1]), $val[2]]; }); }); }, Success::create([Value::blank(), self::INITIAL_STATE]))->flatMap(static function (array $result) { /** @psalm-suppress DocblockTypeContradiction */ if (in_array($result[1], self::REJECT_STATES, true)) { /** @var \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Value,string> */ return Error::create('a missing closing quote'); } /** @var \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Value,string> */ return Success::create($result[0]); })->mapError(static function (string $err) use ($value) { return self::getErrorMessage($err, $value); }); } /** * Process the given token. * * @param int $state * @param string $token * * @return \GrahamCampbell\ResultType\Result */ private static function processToken(int $state, string $token) { switch ($state) { case self::INITIAL_STATE: if ($token === '\'') { /** @var \GrahamCampbell\ResultType\Result */ return Success::create(['', false, self::SINGLE_QUOTED_STATE]); } elseif ($token === '"') { /** @var \GrahamCampbell\ResultType\Result */ return Success::create(['', false, self::DOUBLE_QUOTED_STATE]); } elseif ($token === '#') { /** @var \GrahamCampbell\ResultType\Result */ return Success::create(['', false, self::COMMENT_STATE]); } elseif ($token === '$') { /** @var \GrahamCampbell\ResultType\Result */ return Success::create([$token, true, self::UNQUOTED_STATE]); } else { /** @var \GrahamCampbell\ResultType\Result */ return Success::create([$token, false, self::UNQUOTED_STATE]); } case self::UNQUOTED_STATE: if ($token === '#') { /** @var \GrahamCampbell\ResultType\Result */ return Success::create(['', false, self::COMMENT_STATE]); } elseif (\ctype_space($token)) { /** @var \GrahamCampbell\ResultType\Result */ return Success::create(['', false, self::WHITESPACE_STATE]); } elseif ($token === '$') { /** @var \GrahamCampbell\ResultType\Result */ return Success::create([$token, true, self::UNQUOTED_STATE]); } else { /** @var \GrahamCampbell\ResultType\Result */ return Success::create([$token, false, self::UNQUOTED_STATE]); } case self::SINGLE_QUOTED_STATE: if ($token === '\'') { /** @var \GrahamCampbell\ResultType\Result */ return Success::create(['', false, self::WHITESPACE_STATE]); } else { /** @var \GrahamCampbell\ResultType\Result */ return Success::create([$token, false, self::SINGLE_QUOTED_STATE]); } case self::DOUBLE_QUOTED_STATE: if ($token === '"') { /** @var \GrahamCampbell\ResultType\Result */ return Success::create(['', false, self::WHITESPACE_STATE]); } elseif ($token === '\\') { /** @var \GrahamCampbell\ResultType\Result */ return Success::create(['', false, self::ESCAPE_SEQUENCE_STATE]); } elseif ($token === '$') { /** @var \GrahamCampbell\ResultType\Result */ return Success::create([$token, true, self::DOUBLE_QUOTED_STATE]); } else { /** @var \GrahamCampbell\ResultType\Result */ return Success::create([$token, false, self::DOUBLE_QUOTED_STATE]); } case self::ESCAPE_SEQUENCE_STATE: if ($token === '"' || $token === '\\') { /** @var \GrahamCampbell\ResultType\Result */ return Success::create([$token, false, self::DOUBLE_QUOTED_STATE]); } elseif ($token === '$') { /** @var \GrahamCampbell\ResultType\Result */ return Success::create([$token, false, self::DOUBLE_QUOTED_STATE]); } else { $first = Str::substr($token, 0, 1); if (\in_array($first, ['f', 'n', 'r', 't', 'v'], true)) { /** @var \GrahamCampbell\ResultType\Result */ return Success::create([\stripcslashes('\\'.$first).Str::substr($token, 1), false, self::DOUBLE_QUOTED_STATE]); } else { /** @var \GrahamCampbell\ResultType\Result */ return Error::create('an unexpected escape sequence'); } } case self::WHITESPACE_STATE: if ($token === '#') { /** @var \GrahamCampbell\ResultType\Result */ return Success::create(['', false, self::COMMENT_STATE]); } elseif (!\ctype_space($token)) { /** @var \GrahamCampbell\ResultType\Result */ return Error::create('unexpected whitespace'); } else { /** @var \GrahamCampbell\ResultType\Result */ return Success::create(['', false, self::WHITESPACE_STATE]); } case self::COMMENT_STATE: /** @var \GrahamCampbell\ResultType\Result */ return Success::create(['', false, self::COMMENT_STATE]); default: throw new \Error('Parser entered invalid state.'); } } /** * Generate a friendly error message. * * @param string $cause * @param string $subject * * @return string */ private static function getErrorMessage(string $cause, string $subject) { return \sprintf( 'Encountered %s at [%s].', $cause, \strtok($subject, "\n") ); } } phpdotenv/src/Parser/Value.php000064400000003064150247724300012372 0ustar00chars = $chars; $this->vars = $vars; } /** * Create an empty value instance. * * @return \Dotenv\Parser\Value */ public static function blank() { return new self('', []); } /** * Create a new value instance, appending the characters. * * @param string $chars * @param bool $var * * @return \Dotenv\Parser\Value */ public function append(string $chars, bool $var) { return new self( $this->chars.$chars, $var ? \array_merge($this->vars, [Str::len($this->chars)]) : $this->vars ); } /** * Get the string representation of the parsed value. * * @return string */ public function getChars() { return $this->chars; } /** * Get the locations of the variables in the value. * * @return int[] */ public function getVars() { $vars = $this->vars; \rsort($vars); return $vars; } } phpdotenv/src/Parser/Parser.php000064400000003323150247724300012550 0ustar00mapError(static function () { return 'Could not split into separate lines.'; })->flatMap(static function (array $lines) { return self::process(Lines::process($lines)); })->mapError(static function (string $error) { throw new InvalidFileException(\sprintf('Failed to parse dotenv file. %s', $error)); })->success()->get(); } /** * Convert the raw entries into proper entries. * * @param string[] $entries * * @return \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Entry[],string> */ private static function process(array $entries) { /** @var \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Entry[],string> */ return \array_reduce($entries, static function (Result $result, string $raw) { return $result->flatMap(static function (array $entries) use ($raw) { return EntryParser::parse($raw)->map(static function (Entry $entry) use ($entries) { /** @var \Dotenv\Parser\Entry[] */ return \array_merge($entries, [$entry]); }); }); }, Success::create([])); } } phpdotenv/src/Repository/AdapterRepository.php000064400000004532150247724300015722 0ustar00reader = $reader; $this->writer = $writer; } /** * Determine if the given environment variable is defined. * * @param string $name * * @return bool */ public function has(string $name) { return '' !== $name && $this->reader->read($name)->isDefined(); } /** * Get an environment variable. * * @param string $name * * @throws \InvalidArgumentException * * @return string|null */ public function get(string $name) { if ('' === $name) { throw new InvalidArgumentException('Expected name to be a non-empty string.'); } return $this->reader->read($name)->getOrElse(null); } /** * Set an environment variable. * * @param string $name * @param string $value * * @throws \InvalidArgumentException * * @return bool */ public function set(string $name, string $value) { if ('' === $name) { throw new InvalidArgumentException('Expected name to be a non-empty string.'); } return $this->writer->write($name, $value); } /** * Clear an environment variable. * * @param string $name * * @throws \InvalidArgumentException * * @return bool */ public function clear(string $name) { if ('' === $name) { throw new InvalidArgumentException('Expected name to be a non-empty string.'); } return $this->writer->delete($name); } } phpdotenv/src/Repository/RepositoryInterface.php000064400000001645150247724300016244 0ustar00 */ public static function create(); } phpdotenv/src/Repository/Adapter/ReaderInterface.php000064400000000454150247724300016644 0ustar00 */ public function read(string $name); } phpdotenv/src/Repository/Adapter/PutenvAdapter.php000064400000003545150247724300016407 0ustar00 */ public static function create() { if (self::isSupported()) { /** @var \PhpOption\Option */ return Some::create(new self()); } return None::create(); } /** * Determines if the adapter is supported. * * @return bool */ private static function isSupported() { return \function_exists('getenv') && \function_exists('putenv'); } /** * Read an environment variable, if it exists. * * @param non-empty-string $name * * @return \PhpOption\Option */ public function read(string $name) { /** @var \PhpOption\Option */ return Option::fromValue(\getenv($name), false)->filter(static function ($value) { return \is_string($value); }); } /** * Write to an environment variable, if possible. * * @param non-empty-string $name * @param string $value * * @return bool */ public function write(string $name, string $value) { \putenv("$name=$value"); return true; } /** * Delete an environment variable, if possible. * * @param non-empty-string $name * * @return bool */ public function delete(string $name) { \putenv($name); return true; } } phpdotenv/src/Repository/Adapter/ServerConstAdapter.php000064400000003646150247724300017405 0ustar00 */ public static function create() { /** @var \PhpOption\Option */ return Some::create(new self()); } /** * Read an environment variable, if it exists. * * @param non-empty-string $name * * @return \PhpOption\Option */ public function read(string $name) { /** @var \PhpOption\Option */ return Option::fromArraysValue($_SERVER, $name) ->filter(static function ($value) { return \is_scalar($value); }) ->map(static function ($value) { if ($value === false) { return 'false'; } if ($value === true) { return 'true'; } /** @psalm-suppress PossiblyInvalidCast */ return (string) $value; }); } /** * Write to an environment variable, if possible. * * @param non-empty-string $name * @param string $value * * @return bool */ public function write(string $name, string $value) { $_SERVER[$name] = $value; return true; } /** * Delete an environment variable, if possible. * * @param non-empty-string $name * * @return bool */ public function delete(string $name) { unset($_SERVER[$name]); return true; } } phpdotenv/src/Repository/Adapter/GuardedWriter.php000064400000003523150247724300016371 0ustar00writer = $writer; $this->allowList = $allowList; } /** * Write to an environment variable, if possible. * * @param non-empty-string $name * @param string $value * * @return bool */ public function write(string $name, string $value) { // Don't set non-allowed variables if (!$this->isAllowed($name)) { return false; } // Set the value on the inner writer return $this->writer->write($name, $value); } /** * Delete an environment variable, if possible. * * @param non-empty-string $name * * @return bool */ public function delete(string $name) { // Don't clear non-allowed variables if (!$this->isAllowed($name)) { return false; } // Set the value on the inner writer return $this->writer->delete($name); } /** * Determine if the given variable is allowed. * * @param non-empty-string $name * * @return bool */ private function isAllowed(string $name) { return \in_array($name, $this->allowList, true); } } phpdotenv/src/Repository/Adapter/ImmutableWriter.php000064400000004756150247724300016746 0ustar00 */ private $loaded; /** * Create a new immutable writer instance. * * @param \Dotenv\Repository\Adapter\WriterInterface $writer * @param \Dotenv\Repository\Adapter\ReaderInterface $reader * * @return void */ public function __construct(WriterInterface $writer, ReaderInterface $reader) { $this->writer = $writer; $this->reader = $reader; $this->loaded = []; } /** * Write to an environment variable, if possible. * * @param non-empty-string $name * @param string $value * * @return bool */ public function write(string $name, string $value) { // Don't overwrite existing environment variables // Ruby's dotenv does this with `ENV[key] ||= value` if ($this->isExternallyDefined($name)) { return false; } // Set the value on the inner writer if (!$this->writer->write($name, $value)) { return false; } // Record that we have loaded the variable $this->loaded[$name] = ''; return true; } /** * Delete an environment variable, if possible. * * @param non-empty-string $name * * @return bool */ public function delete(string $name) { // Don't clear existing environment variables if ($this->isExternallyDefined($name)) { return false; } // Clear the value on the inner writer if (!$this->writer->delete($name)) { return false; } // Leave the variable as fair game unset($this->loaded[$name]); return true; } /** * Determine if the given variable is externally defined. * * That is, is it an "existing" variable. * * @param non-empty-string $name * * @return bool */ private function isExternallyDefined(string $name) { return $this->reader->read($name)->isDefined() && !isset($this->loaded[$name]); } } phpdotenv/src/Repository/Adapter/EnvConstAdapter.php000064400000003627150247724300016666 0ustar00 */ public static function create() { /** @var \PhpOption\Option */ return Some::create(new self()); } /** * Read an environment variable, if it exists. * * @param non-empty-string $name * * @return \PhpOption\Option */ public function read(string $name) { /** @var \PhpOption\Option */ return Option::fromArraysValue($_ENV, $name) ->filter(static function ($value) { return \is_scalar($value); }) ->map(static function ($value) { if ($value === false) { return 'false'; } if ($value === true) { return 'true'; } /** @psalm-suppress PossiblyInvalidCast */ return (string) $value; }); } /** * Write to an environment variable, if possible. * * @param non-empty-string $name * @param string $value * * @return bool */ public function write(string $name, string $value) { $_ENV[$name] = $value; return true; } /** * Delete an environment variable, if possible. * * @param non-empty-string $name * * @return bool */ public function delete(string $name) { unset($_ENV[$name]); return true; } } phpdotenv/src/Repository/Adapter/ApacheAdapter.php000064400000003666150247724300016313 0ustar00 */ public static function create() { if (self::isSupported()) { /** @var \PhpOption\Option */ return Some::create(new self()); } return None::create(); } /** * Determines if the adapter is supported. * * This happens if PHP is running as an Apache module. * * @return bool */ private static function isSupported() { return \function_exists('apache_getenv') && \function_exists('apache_setenv'); } /** * Read an environment variable, if it exists. * * @param non-empty-string $name * * @return \PhpOption\Option */ public function read(string $name) { /** @var \PhpOption\Option */ return Option::fromValue(apache_getenv($name))->filter(static function ($value) { return \is_string($value) && $value !== ''; }); } /** * Write to an environment variable, if possible. * * @param non-empty-string $name * @param string $value * * @return bool */ public function write(string $name, string $value) { return apache_setenv($name, $value); } /** * Delete an environment variable, if possible. * * @param non-empty-string $name * * @return bool */ public function delete(string $name) { return apache_setenv($name, ''); } } phpdotenv/src/Repository/Adapter/ArrayAdapter.php000064400000003112150247724300016172 0ustar00 */ private $variables; /** * Create a new array adapter instance. * * @return void */ private function __construct() { $this->variables = []; } /** * Create a new instance of the adapter, if it is available. * * @return \PhpOption\Option<\Dotenv\Repository\Adapter\AdapterInterface> */ public static function create() { /** @var \PhpOption\Option */ return Some::create(new self()); } /** * Read an environment variable, if it exists. * * @param non-empty-string $name * * @return \PhpOption\Option */ public function read(string $name) { return Option::fromArraysValue($this->variables, $name); } /** * Write to an environment variable, if possible. * * @param non-empty-string $name * @param string $value * * @return bool */ public function write(string $name, string $value) { $this->variables[$name] = $value; return true; } /** * Delete an environment variable, if possible. * * @param non-empty-string $name * * @return bool */ public function delete(string $name) { unset($this->variables[$name]); return true; } } phpdotenv/src/Repository/Adapter/MultiWriter.php000064400000002424150247724300016107 0ustar00writers = $writers; } /** * Write to an environment variable, if possible. * * @param non-empty-string $name * @param string $value * * @return bool */ public function write(string $name, string $value) { foreach ($this->writers as $writers) { if (!$writers->write($name, $value)) { return false; } } return true; } /** * Delete an environment variable, if possible. * * @param non-empty-string $name * * @return bool */ public function delete(string $name) { foreach ($this->writers as $writers) { if (!$writers->delete($name)) { return false; } } return true; } } phpdotenv/src/Repository/Adapter/MultiReader.php000064400000001711150247724300016033 0ustar00readers = $readers; } /** * Read an environment variable, if it exists. * * @param non-empty-string $name * * @return \PhpOption\Option */ public function read(string $name) { foreach ($this->readers as $reader) { $result = $reader->read($name); if ($result->isDefined()) { return $result; } } return None::create(); } } phpdotenv/src/Repository/Adapter/ReplacingWriter.php000064400000004243150247724300016722 0ustar00 */ private $seen; /** * Create a new replacement writer instance. * * @param \Dotenv\Repository\Adapter\WriterInterface $writer * @param \Dotenv\Repository\Adapter\ReaderInterface $reader * * @return void */ public function __construct(WriterInterface $writer, ReaderInterface $reader) { $this->writer = $writer; $this->reader = $reader; $this->seen = []; } /** * Write to an environment variable, if possible. * * @param non-empty-string $name * @param string $value * * @return bool */ public function write(string $name, string $value) { if ($this->exists($name)) { return $this->writer->write($name, $value); } // succeed if nothing to do return true; } /** * Delete an environment variable, if possible. * * @param non-empty-string $name * * @return bool */ public function delete(string $name) { if ($this->exists($name)) { return $this->writer->delete($name); } // succeed if nothing to do return true; } /** * Does the given environment variable exist. * * Returns true if it currently exists, or existed at any point in the past * that we are aware of. * * @param non-empty-string $name * * @return bool */ private function exists(string $name) { if (isset($this->seen[$name])) { return true; } if ($this->reader->read($name)->isDefined()) { $this->seen[$name] = ''; return true; } return false; } } phpdotenv/src/Repository/Adapter/WriterInterface.php000064400000001006150247724300016710 0ustar00readers = $readers; $this->writers = $writers; $this->immutable = $immutable; $this->allowList = $allowList; } /** * Create a new repository builder instance with no adapters added. * * @return \Dotenv\Repository\RepositoryBuilder */ public static function createWithNoAdapters() { return new self(); } /** * Create a new repository builder instance with the default adapters added. * * @return \Dotenv\Repository\RepositoryBuilder */ public static function createWithDefaultAdapters() { $adapters = \iterator_to_array(self::defaultAdapters()); return new self($adapters, $adapters); } /** * Return the array of default adapters. * * @return \Generator<\Dotenv\Repository\Adapter\AdapterInterface> */ private static function defaultAdapters() { foreach (self::DEFAULT_ADAPTERS as $adapter) { $instance = $adapter::create(); if ($instance->isDefined()) { yield $instance->get(); } } } /** * Determine if the given name if of an adapterclass. * * @param string $name * * @return bool */ private static function isAnAdapterClass(string $name) { if (!\class_exists($name)) { return false; } return (new ReflectionClass($name))->implementsInterface(AdapterInterface::class); } /** * Creates a repository builder with the given reader added. * * Accepts either a reader instance, or a class-string for an adapter. If * the adapter is not supported, then we silently skip adding it. * * @param \Dotenv\Repository\Adapter\ReaderInterface|string $reader * * @throws \InvalidArgumentException * * @return \Dotenv\Repository\RepositoryBuilder */ public function addReader($reader) { if (!(\is_string($reader) && self::isAnAdapterClass($reader)) && !($reader instanceof ReaderInterface)) { throw new InvalidArgumentException( \sprintf( 'Expected either an instance of %s or a class-string implementing %s', ReaderInterface::class, AdapterInterface::class ) ); } $optional = Some::create($reader)->flatMap(static function ($reader) { return \is_string($reader) ? $reader::create() : Some::create($reader); }); $readers = \array_merge($this->readers, \iterator_to_array($optional)); return new self($readers, $this->writers, $this->immutable, $this->allowList); } /** * Creates a repository builder with the given writer added. * * Accepts either a writer instance, or a class-string for an adapter. If * the adapter is not supported, then we silently skip adding it. * * @param \Dotenv\Repository\Adapter\WriterInterface|string $writer * * @throws \InvalidArgumentException * * @return \Dotenv\Repository\RepositoryBuilder */ public function addWriter($writer) { if (!(\is_string($writer) && self::isAnAdapterClass($writer)) && !($writer instanceof WriterInterface)) { throw new InvalidArgumentException( \sprintf( 'Expected either an instance of %s or a class-string implementing %s', WriterInterface::class, AdapterInterface::class ) ); } $optional = Some::create($writer)->flatMap(static function ($writer) { return \is_string($writer) ? $writer::create() : Some::create($writer); }); $writers = \array_merge($this->writers, \iterator_to_array($optional)); return new self($this->readers, $writers, $this->immutable, $this->allowList); } /** * Creates a repository builder with the given adapter added. * * Accepts either an adapter instance, or a class-string for an adapter. If * the adapter is not supported, then we silently skip adding it. We will * add the adapter as both a reader and a writer. * * @param \Dotenv\Repository\Adapter\WriterInterface|string $adapter * * @throws \InvalidArgumentException * * @return \Dotenv\Repository\RepositoryBuilder */ public function addAdapter($adapter) { if (!(\is_string($adapter) && self::isAnAdapterClass($adapter)) && !($adapter instanceof AdapterInterface)) { throw new InvalidArgumentException( \sprintf( 'Expected either an instance of %s or a class-string implementing %s', WriterInterface::class, AdapterInterface::class ) ); } $optional = Some::create($adapter)->flatMap(static function ($adapter) { return \is_string($adapter) ? $adapter::create() : Some::create($adapter); }); $readers = \array_merge($this->readers, \iterator_to_array($optional)); $writers = \array_merge($this->writers, \iterator_to_array($optional)); return new self($readers, $writers, $this->immutable, $this->allowList); } /** * Creates a repository builder with mutability enabled. * * @return \Dotenv\Repository\RepositoryBuilder */ public function immutable() { return new self($this->readers, $this->writers, true, $this->allowList); } /** * Creates a repository builder with the given allow list. * * @param string[]|null $allowList * * @return \Dotenv\Repository\RepositoryBuilder */ public function allowList(array $allowList = null) { return new self($this->readers, $this->writers, $this->immutable, $allowList); } /** * Creates a new repository instance. * * @return \Dotenv\Repository\RepositoryInterface */ public function make() { $reader = new MultiReader($this->readers); $writer = new MultiWriter($this->writers); if ($this->immutable) { $writer = new ImmutableWriter($writer, $reader); } if ($this->allowList !== null) { $writer = new GuardedWriter($writer, $this->allowList); } return new AdapterRepository($reader, $writer); } } phpdotenv/src/Util/Regex.php000064400000006000150247724300012042 0ustar00 */ public static function matches(string $pattern, string $subject) { return self::pregAndWrap(static function (string $subject) use ($pattern) { return @\preg_match($pattern, $subject) === 1; }, $subject); } /** * Perform a preg match all, wrapping up the result. * * @param string $pattern * @param string $subject * * @return \GrahamCampbell\ResultType\Result */ public static function occurrences(string $pattern, string $subject) { return self::pregAndWrap(static function (string $subject) use ($pattern) { return (int) @\preg_match_all($pattern, $subject); }, $subject); } /** * Perform a preg replace callback, wrapping up the result. * * @param string $pattern * @param callable $callback * @param string $subject * @param int|null $limit * * @return \GrahamCampbell\ResultType\Result */ public static function replaceCallback(string $pattern, callable $callback, string $subject, int $limit = null) { return self::pregAndWrap(static function (string $subject) use ($pattern, $callback, $limit) { return (string) @\preg_replace_callback($pattern, $callback, $subject, $limit ?? -1); }, $subject); } /** * Perform a preg split, wrapping up the result. * * @param string $pattern * @param string $subject * * @return \GrahamCampbell\ResultType\Result */ public static function split(string $pattern, string $subject) { return self::pregAndWrap(static function (string $subject) use ($pattern) { /** @var string[] */ return (array) @\preg_split($pattern, $subject); }, $subject); } /** * Perform a preg operation, wrapping up the result. * * @template V * * @param callable(string):V $operation * @param string $subject * * @return \GrahamCampbell\ResultType\Result */ private static function pregAndWrap(callable $operation, string $subject) { $result = $operation($subject); if (\preg_last_error() !== \PREG_NO_ERROR) { /** @var \GrahamCampbell\ResultType\Result */ return Error::create(\preg_last_error_msg()); } /** @var \GrahamCampbell\ResultType\Result */ return Success::create($result); } } phpdotenv/src/Util/Str.php000064400000004735150247724300011555 0ustar00 */ public static function utf8(string $input, string $encoding = null) { if ($encoding !== null && !\in_array($encoding, \mb_list_encodings(), true)) { /** @var \GrahamCampbell\ResultType\Result */ return Error::create( \sprintf('Illegal character encoding [%s] specified.', $encoding) ); } $converted = $encoding === null ? @\mb_convert_encoding($input, 'UTF-8') : @\mb_convert_encoding($input, 'UTF-8', $encoding); /** * this is for support UTF-8 with BOM encoding * @see https://en.wikipedia.org/wiki/Byte_order_mark * @see https://github.com/vlucas/phpdotenv/issues/500 */ if (\substr($converted, 0, 3) == "\xEF\xBB\xBF") { $converted = \substr($converted, 3); } /** @var \GrahamCampbell\ResultType\Result */ return Success::create($converted); } /** * Search for a given substring of the input. * * @param string $haystack * @param string $needle * * @return \PhpOption\Option */ public static function pos(string $haystack, string $needle) { /** @var \PhpOption\Option */ return Option::fromValue(\mb_strpos($haystack, $needle, 0, 'UTF-8'), false); } /** * Grab the specified substring of the input. * * @param string $input * @param int $start * @param int|null $length * * @return string */ public static function substr(string $input, int $start, int $length = null) { return \mb_substr($input, $start, $length, 'UTF-8'); } /** * Compute the length of the given string. * * @param string $input * * @return int */ public static function len(string $input) { return \mb_strlen($input, 'UTF-8'); } } phpdotenv/src/Exception/InvalidFileException.php000064400000000310150247724300016054 0ustar00repository = $repository; $this->variables = $variables; } /** * Assert that each variable is present. * * @throws \Dotenv\Exception\ValidationException * * @return \Dotenv\Validator */ public function required() { return $this->assert( static function (?string $value) { return $value !== null; }, 'is missing' ); } /** * Assert that each variable is not empty. * * @throws \Dotenv\Exception\ValidationException * * @return \Dotenv\Validator */ public function notEmpty() { return $this->assertNullable( static function (string $value) { return Str::len(\trim($value)) > 0; }, 'is empty' ); } /** * Assert that each specified variable is an integer. * * @throws \Dotenv\Exception\ValidationException * * @return \Dotenv\Validator */ public function isInteger() { return $this->assertNullable( static function (string $value) { return \ctype_digit($value); }, 'is not an integer' ); } /** * Assert that each specified variable is a boolean. * * @throws \Dotenv\Exception\ValidationException * * @return \Dotenv\Validator */ public function isBoolean() { return $this->assertNullable( static function (string $value) { if ($value === '') { return false; } return \filter_var($value, \FILTER_VALIDATE_BOOLEAN, \FILTER_NULL_ON_FAILURE) !== null; }, 'is not a boolean' ); } /** * Assert that each variable is amongst the given choices. * * @param string[] $choices * * @throws \Dotenv\Exception\ValidationException * * @return \Dotenv\Validator */ public function allowedValues(array $choices) { return $this->assertNullable( static function (string $value) use ($choices) { return \in_array($value, $choices, true); }, \sprintf('is not one of [%s]', \implode(', ', $choices)) ); } /** * Assert that each variable matches the given regular expression. * * @param string $regex * * @throws \Dotenv\Exception\ValidationException * * @return \Dotenv\Validator */ public function allowedRegexValues(string $regex) { return $this->assertNullable( static function (string $value) use ($regex) { return Regex::matches($regex, $value)->success()->getOrElse(false); }, \sprintf('does not match "%s"', $regex) ); } /** * Assert that the callback returns true for each variable. * * @param callable(?string):bool $callback * @param string $message * * @throws \Dotenv\Exception\ValidationException * * @return \Dotenv\Validator */ public function assert(callable $callback, string $message) { $failing = []; foreach ($this->variables as $variable) { if ($callback($this->repository->get($variable)) === false) { $failing[] = \sprintf('%s %s', $variable, $message); } } if (\count($failing) > 0) { throw new ValidationException(\sprintf( 'One or more environment variables failed assertions: %s.', \implode(', ', $failing) )); } return $this; } /** * Assert that the callback returns true for each variable. * * Skip checking null variable values. * * @param callable(string):bool $callback * @param string $message * * @throws \Dotenv\Exception\ValidationException * * @return \Dotenv\Validator */ public function assertNullable(callable $callback, string $message) { return $this->assert( static function (?string $value) use ($callback) { if ($value === null) { return true; } return $callback($value); }, $message ); } } phpdotenv/src/Loader/LoaderInterface.php000064400000000711150247724300014313 0ustar00 */ public function load(RepositoryInterface $repository, array $entries); } phpdotenv/src/Loader/Resolver.php000064400000003330150247724300013065 0ustar00getVars(), static function (string $s, int $i) use ($repository) { return Str::substr($s, 0, $i).self::resolveVariable($repository, Str::substr($s, $i)); }, $value->getChars()); } /** * Resolve a single nested variable. * * @param \Dotenv\Repository\RepositoryInterface $repository * @param string $str * * @return string */ private static function resolveVariable(RepositoryInterface $repository, string $str) { return Regex::replaceCallback( '/\A\${([a-zA-Z0-9_.]+)}/', static function (array $matches) use ($repository) { return Option::fromValue($repository->get($matches[1])) ->getOrElse($matches[0]); }, $str, 1 )->success()->getOrElse($str); } } phpdotenv/src/Loader/Loader.php000064400000002642150247724300012477 0ustar00 */ public function load(RepositoryInterface $repository, array $entries) { return \array_reduce($entries, static function (array $vars, Entry $entry) use ($repository) { $name = $entry->getName(); $value = $entry->getValue()->map(static function (Value $value) use ($repository) { return Resolver::resolve($repository, $value); }); if ($value->isDefined()) { $inner = $value->get(); if ($repository->set($name, $inner)) { return \array_merge($vars, [$name => $inner]); } } else { if ($repository->clear($name)) { return \array_merge($vars, [$name => null]); } } return $vars; }, []); } } phpdotenv/src/Dotenv.php000064400000020102150247724300011311 0ustar00store = $store; $this->parser = $parser; $this->loader = $loader; $this->repository = $repository; } /** * Create a new dotenv instance. * * @param \Dotenv\Repository\RepositoryInterface $repository * @param string|string[] $paths * @param string|string[]|null $names * @param bool $shortCircuit * @param string|null $fileEncoding * * @return \Dotenv\Dotenv */ 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); } /** * Create a new mutable dotenv instance with default repository. * * @param string|string[] $paths * @param string|string[]|null $names * @param bool $shortCircuit * @param string|null $fileEncoding * * @return \Dotenv\Dotenv */ public static function createMutable($paths, $names = null, bool $shortCircuit = true, string $fileEncoding = null) { $repository = RepositoryBuilder::createWithDefaultAdapters()->make(); return self::create($repository, $paths, $names, $shortCircuit, $fileEncoding); } /** * Create a new mutable dotenv instance with default repository with the putenv adapter. * * @param string|string[] $paths * @param string|string[]|null $names * @param bool $shortCircuit * @param string|null $fileEncoding * * @return \Dotenv\Dotenv */ public static function createUnsafeMutable($paths, $names = null, bool $shortCircuit = true, string $fileEncoding = null) { $repository = RepositoryBuilder::createWithDefaultAdapters() ->addAdapter(PutenvAdapter::class) ->make(); return self::create($repository, $paths, $names, $shortCircuit, $fileEncoding); } /** * Create a new immutable dotenv instance with default repository. * * @param string|string[] $paths * @param string|string[]|null $names * @param bool $shortCircuit * @param string|null $fileEncoding * * @return \Dotenv\Dotenv */ public static function createImmutable($paths, $names = null, bool $shortCircuit = true, string $fileEncoding = null) { $repository = RepositoryBuilder::createWithDefaultAdapters()->immutable()->make(); return self::create($repository, $paths, $names, $shortCircuit, $fileEncoding); } /** * Create a new immutable dotenv instance with default repository with the putenv adapter. * * @param string|string[] $paths * @param string|string[]|null $names * @param bool $shortCircuit * @param string|null $fileEncoding * * @return \Dotenv\Dotenv */ public static function createUnsafeImmutable($paths, $names = null, bool $shortCircuit = true, string $fileEncoding = null) { $repository = RepositoryBuilder::createWithDefaultAdapters() ->addAdapter(PutenvAdapter::class) ->immutable() ->make(); return self::create($repository, $paths, $names, $shortCircuit, $fileEncoding); } /** * Create a new dotenv instance with an array backed repository. * * @param string|string[] $paths * @param string|string[]|null $names * @param bool $shortCircuit * @param string|null $fileEncoding * * @return \Dotenv\Dotenv */ public static function createArrayBacked($paths, $names = null, bool $shortCircuit = true, string $fileEncoding = null) { $repository = RepositoryBuilder::createWithNoAdapters()->addAdapter(ArrayAdapter::class)->make(); return self::create($repository, $paths, $names, $shortCircuit, $fileEncoding); } /** * Parse the given content and resolve nested variables. * * This method behaves just like load(), only without mutating your actual * environment. We do this by using an array backed repository. * * @param string $content * * @throws \Dotenv\Exception\InvalidFileException * * @return array */ public static function parse(string $content) { $repository = RepositoryBuilder::createWithNoAdapters()->addAdapter(ArrayAdapter::class)->make(); $phpdotenv = new self(new StringStore($content), new Parser(), new Loader(), $repository); return $phpdotenv->load(); } /** * Read and load environment file(s). * * @throws \Dotenv\Exception\InvalidPathException|\Dotenv\Exception\InvalidEncodingException|\Dotenv\Exception\InvalidFileException * * @return array */ public function load() { $entries = $this->parser->parse($this->store->read()); return $this->loader->load($this->repository, $entries); } /** * Read and load environment file(s), silently failing if no files can be read. * * @throws \Dotenv\Exception\InvalidEncodingException|\Dotenv\Exception\InvalidFileException * * @return array */ public function safeLoad() { try { return $this->load(); } catch (InvalidPathException $e) { // suppressing exception return []; } } /** * Required ensures that the specified variables exist, and returns a new validator object. * * @param string|string[] $variables * * @return \Dotenv\Validator */ public function required($variables) { return (new Validator($this->repository, (array) $variables))->required(); } /** * Returns a new validator object that won't check if the specified variables exist. * * @param string|string[] $variables * * @return \Dotenv\Validator */ public function ifPresent($variables) { return new Validator($this->repository, (array) $variables); } } phpdotenv/src/Store/File/Paths.php000064400000001354150247724300013114 0ustar00 */ public static function read(array $filePaths, bool $shortCircuit = true, string $fileEncoding = null) { $output = []; foreach ($filePaths as $filePath) { $content = self::readFromFile($filePath, $fileEncoding); if ($content->isDefined()) { $output[$filePath] = $content->get(); if ($shortCircuit) { break; } } } return $output; } /** * Read the given file. * * @param string $path * @param string|null $encoding * * @throws \Dotenv\Exception\InvalidEncodingException * * @return \PhpOption\Option */ private static function readFromFile(string $path, string $encoding = null) { /** @var Option */ $content = Option::fromValue(@\file_get_contents($path), false); return $content->flatMap(static function (string $content) use ($encoding) { return Str::utf8($content, $encoding)->mapError(static function (string $error) { throw new InvalidEncodingException($error); })->success(); }); } } phpdotenv/src/Store/StoreBuilder.php000064400000006142150247724300013561 0ustar00paths = $paths; $this->names = $names; $this->shortCircuit = $shortCircuit; $this->fileEncoding = $fileEncoding; } /** * Create a new store builder instance with no names. * * @return \Dotenv\Store\StoreBuilder */ public static function createWithNoNames() { return new self(); } /** * Create a new store builder instance with the default name. * * @return \Dotenv\Store\StoreBuilder */ public static function createWithDefaultName() { return new self([], [self::DEFAULT_NAME]); } /** * Creates a store builder with the given path added. * * @param string $path * * @return \Dotenv\Store\StoreBuilder */ public function addPath(string $path) { return new self(\array_merge($this->paths, [$path]), $this->names, $this->shortCircuit, $this->fileEncoding); } /** * Creates a store builder with the given name added. * * @param string $name * * @return \Dotenv\Store\StoreBuilder */ public function addName(string $name) { return new self($this->paths, \array_merge($this->names, [$name]), $this->shortCircuit, $this->fileEncoding); } /** * Creates a store builder with short circuit mode enabled. * * @return \Dotenv\Store\StoreBuilder */ public function shortCircuit() { return new self($this->paths, $this->names, true, $this->fileEncoding); } /** * Creates a store builder with the specified file encoding. * * @param string|null $fileEncoding * * @return \Dotenv\Store\StoreBuilder */ public function fileEncoding(string $fileEncoding = null) { return new self($this->paths, $this->names, $this->shortCircuit, $fileEncoding); } /** * Creates a new store instance. * * @return \Dotenv\Store\StoreInterface */ public function make() { return new FileStore( Paths::filePaths($this->paths, $this->names), $this->shortCircuit, $this->fileEncoding ); } } phpdotenv/src/Store/FileStore.php000064400000003217150247724300013052 0ustar00filePaths = $filePaths; $this->shortCircuit = $shortCircuit; $this->fileEncoding = $fileEncoding; } /** * Read the content of the environment file(s). * * @throws \Dotenv\Exception\InvalidEncodingException|\Dotenv\Exception\InvalidPathException * * @return string */ public function read() { if ($this->filePaths === []) { throw new InvalidPathException('At least one environment file path must be provided.'); } $contents = Reader::read($this->filePaths, $this->shortCircuit, $this->fileEncoding); if (\count($contents) > 0) { return \implode("\n", $contents); } throw new InvalidPathException( \sprintf('Unable to read any of the environment file(s) at [%s].', \implode(', ', $this->filePaths)) ); } } phpdotenv/src/Store/StoreInterface.php000064400000000474150247724300014075 0ustar00content = $content; } /** * Read the content of the environment file(s). * * @return string */ public function read() { return $this->content; } } phpdotenv/LICENSE000064400000003025150247724300007564 0ustar00BSD 3-Clause License Copyright (c) 2014, Graham Campbell. Copyright (c) 2013, Vance Lucas. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.