Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,6 @@ parameters:
count: 1
path: src/Parser/EntryParser.php

-
message: '#^Parameter \#2 \$callback of function array_reduce expects callable\(GrahamCampbell\\ResultType\\Result\<array\{Dotenv\\Parser\\Value, int\}, mixed\>\|GrahamCampbell\\ResultType\\Result\<array\{mixed, int\}, string\>, string\)\: \(GrahamCampbell\\ResultType\\Result\<array\{Dotenv\\Parser\\Value, int\}, mixed\>\|GrahamCampbell\\ResultType\\Result\<array\{mixed, int\}, string\>\), Closure\(GrahamCampbell\\ResultType\\Result, string\)\: GrahamCampbell\\ResultType\\Result\<array\{Dotenv\\Parser\\Value, int\}, string\> given\.$#'
identifier: argument.type
count: 1
path: src/Parser/EntryParser.php

-
message: '#^Only booleans are allowed in a negated boolean, int\|false given\.$#'
identifier: booleanNot.exprNotBoolean
Expand Down
25 changes: 10 additions & 15 deletions src/Loader/Loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

namespace Dotenv\Loader;

use Dotenv\Parser\Entry;
use Dotenv\Parser\Value;
use Dotenv\Repository\RepositoryInterface;

final class Loader implements LoaderInterface
Expand All @@ -23,26 +21,23 @@ final class Loader implements LoaderInterface
*/
public function load(RepositoryInterface $repository, array $entries)
{
/** @var array<string, string|null> */
return \array_reduce($entries, static function (array $vars, Entry $entry) use ($repository) {
$vars = [];

foreach ($entries as $entry) {
$name = $entry->getName();

$value = $entry->getValue()->map(static function (Value $value) use ($repository) {
return Resolver::resolve($repository, $value);
});
$value = $entry->getValue();

if ($value->isDefined()) {
$inner = $value->get();
$inner = Resolver::resolve($repository, $value->get());
if ($repository->set($name, $inner)) {
return \array_merge($vars, [$name => $inner]);
}
} else {
if ($repository->clear($name)) {
return \array_merge($vars, [$name => null]);
$vars[$name] = $inner;
}
} elseif ($repository->clear($name)) {
$vars[$name] = null;
}
}

return $vars;
}, []);
return $vars;
}
}
56 changes: 47 additions & 9 deletions src/Parser/EntryParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
use GrahamCampbell\ResultType\Error;
use GrahamCampbell\ResultType\Result;
use GrahamCampbell\ResultType\Success;
use PhpOption\None;
use PhpOption\Some;

final class EntryParser
{
Expand Down Expand Up @@ -68,12 +70,13 @@ public static function parse(string $entry)
*/
private static function splitStringIntoParts(string $line)
{
/** @var array{string, string|null} */
$result = Str::pos($line, '=')->map(static function () use ($line) {
return \array_map(static function (string $part) {
return \trim($part, " \n\r\t\0\x0B");
}, \explode('=', $line, 2));
})->getOrElse([$line, null]);
$result = \explode('=', $line, 2);

if (isset($result[1])) {
$result = [\trim($result[0], " \n\r\t\0\x0B"), \trim($result[1], " \n\r\t\0\x0B")];
} else {
$result = [$line, null];
}

if ($result[0] === '') {
/** @var \GrahamCampbell\ResultType\Result<array{string, string|null},string> */
Expand Down Expand Up @@ -141,6 +144,10 @@ private static function isQuotedName(string $name)
*/
private static function isValidName(string $name)
{
if (\preg_match('~\A[a-zA-Z0-9_.]+\z~', $name) === 1) {
return true;
}

return Regex::matches('~(*UTF8)\A[\p{Ll}\p{Lu}\p{M}\p{N}_.]+\z~', $name)->success()->getOrElse(false);
}

Expand All @@ -163,13 +170,24 @@ private static function parseValue(string $value)
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) {
$literal = self::parseLiteral($value);

if ($literal->isDefined()) {
/** @var \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Value, string> */
return Success::create(Value::blank()->append($literal->get(), false));
}

$result = Success::create([Value::blank(), self::INITIAL_STATE]);

foreach (Lexer::lex($value) as $token) {
$result = $result->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) {
}

return $result->flatMap(static function (array $result) {
if (in_array($result[1], self::REJECT_STATES, true)) {
/** @var \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Value, string> */
return Error::create('a missing closing quote');
Expand All @@ -182,6 +200,26 @@ private static function parseValue(string $value)
});
}

/**
* Parse the given variable value, if it is a literal.
*
* That is, a value which can be used verbatim, save stripping quotes.
* Unquoted literals are capped at the lexer's chunk size, so that the
* fast path agrees with the transducer under any libc locale.
*
* @param string $value
*
* @return \PhpOption\Option<string>
*/
private static function parseLiteral(string $value)
{
if (\preg_match('~\A(?|([^\s\\\\\'"#$]{1,1000})|\'([^\']*)\'|"([^"\\\\$]*)")\z~', $value, $matches) === 1) {
return Some::create($matches[1]);
}

return None::create();
}

/**
* Process the given token.
*
Expand Down
25 changes: 16 additions & 9 deletions src/Parser/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

use Dotenv\Exception\InvalidFileException;
use Dotenv\Util\Regex;
use GrahamCampbell\ResultType\Result;
use GrahamCampbell\ResultType\Error;
use GrahamCampbell\ResultType\Success;

final class Parser implements ParserInterface
Expand Down Expand Up @@ -40,14 +40,21 @@ public function parse(string $content)
*/
private static function process(array $entries)
{
/** @var \Dotenv\Parser\Entry[] */
$output = [];

foreach ($entries as $raw) {
$entry = EntryParser::parse($raw);

if ($entry->error()->isDefined()) {
/** @var \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Entry[], string> */
return Error::create($entry->error()->get());
}

$output[] = $entry->success()->get();
}

/** @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([]));
return Success::create($output);
}
}
29 changes: 10 additions & 19 deletions src/Util/Regex.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,7 @@ private function __construct()
*/
public static function matches(string $pattern, string $subject)
{
return self::pregAndWrap(static function (string $subject) use ($pattern) {
return @\preg_match($pattern, $subject) === 1;
}, $subject);
return self::wrap(@\preg_match($pattern, $subject) === 1);
}

/**
Expand All @@ -49,9 +47,7 @@ public static function matches(string $pattern, string $subject)
*/
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);
return self::wrap((int) @\preg_match_all($pattern, $subject));
}

/**
Expand All @@ -66,9 +62,7 @@ public static function occurrences(string $pattern, string $subject)
*/
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);
return self::wrap((string) @\preg_replace_callback($pattern, $callback, $subject, $limit ?? -1));
}

/**
Expand All @@ -81,26 +75,23 @@ public static function replaceCallback(string $pattern, callable $callback, stri
*/
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);
/** @var string[] */
$result = (array) @\preg_split($pattern, $subject);

return self::wrap($result);
}

/**
* Perform a preg operation, wrapping up the result.
* Wrap the result of a preg operation.
*
* @template V
*
* @param callable(string): V $operation
* @param string $subject
* @param V $result
*
* @return \GrahamCampbell\ResultType\Result<V, string>
*/
private static function pregAndWrap(callable $operation, string $subject)
private static function wrap($result)
{
$result = $operation($subject);

if (\preg_last_error() !== \PREG_NO_ERROR) {
/** @var \GrahamCampbell\ResultType\Result<V,string> */
return Error::create(\preg_last_error_msg());
Expand Down
7 changes: 7 additions & 0 deletions tests/Dotenv/DotenvTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,13 @@ public function testDotenvParseExample2()
self::assertSame($output, ['FOO' => 'Bar', 'BAZ' => 'Hello Bar']);
}

public function testDotenvParseNumericNames()
{
$output = Dotenv::parse("7=1\nFOO=b\n123=x");

self::assertSame($output, [7 => '1', 'FOO' => 'b', 123 => 'x']);
}

public function testDotenvParseEmptyCase()
{
$output = Dotenv::parse('');
Expand Down
Loading