aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--phpunit.xml.dist25
-rw-r--r--readme.md0
-rw-r--r--src/Compiler/Compiler.php80
-rw-r--r--src/Compiler/CompilerInterface.php10
-rw-r--r--src/Dotenv.php23
-rw-r--r--src/Exception/RuntimeException.php7
-rw-r--r--src/Loader/FileLoader.php41
-rw-r--r--src/Loader/LoaderInterface.php7
-rw-r--r--src/Parser/Parser.php55
-rw-r--r--src/Parser/ParserInterface.php6
-rw-r--r--src/Types/KeyValue.php11
-rw-r--r--tests/CompilerTest.php44
-rw-r--r--tests/DotenvTest.php68
-rw-r--r--tests/FileLoaderTest.php23
-rw-r--r--tests/ParserTest.php37
-rw-r--r--tests/bootstrap.php20
-rw-r--r--tests/misc/.env.test7
-rw-r--r--tests/mocks/MockCompiler.php34
-rw-r--r--tests/mocks/MockLoader.php32
-rw-r--r--tests/mocks/MockParser.php28
20 files changed, 550 insertions, 8 deletions
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 100644
index 0000000..89a39e0
--- /dev/null
+++ b/phpunit.xml.dist
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit backupGlobals="false"
+ backupStaticAttributes="false"
+ colors="true"
+ convertErrorsToExceptions="true"
+ convertNoticesToExceptions="true"
+ convertWarningsToExceptions="true"
+ processIsolation="false"
+ stopOnFailure="false"
+ syntaxCheck="false"
+ bootstrap="./tests/bootstrap.php"
+>
+
+ <testsuites>
+ <testsuite name="Dotenv tests">
+ <directory>./tests/</directory>
+ </testsuite>
+ </testsuites>
+ <filter>
+ <whitelist processUncoveredFilesFromWhitelist="true">
+ <directory suffix=".php">src</directory>
+ </whitelist>
+ </filter>
+</phpunit> \ No newline at end of file
diff --git a/readme.md b/readme.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/readme.md
diff --git a/src/Compiler/Compiler.php b/src/Compiler/Compiler.php
new file mode 100644
index 0000000..6a587d2
--- /dev/null
+++ b/src/Compiler/Compiler.php
@@ -0,0 +1,80 @@
+<?php
+declare(strict_types=1);
+
+/**
+ * @author: Alexander Kiryukhin <alexander@kiryukhin.su>
+ * @license: MIT
+ */
+
+namespace NeonXP\Dotenv\Compiler;
+
+use NeonXP\Dotenv\Exception\RuntimeException;
+use NeonXP\Dotenv\Types\KeyValue;
+
+/**
+ * Class Compiler
+ * @package NeonXP\Dotenv\Compiler
+ */
+class Compiler implements CompilerInterface
+{
+ const REGEX_VARIABLE = '/\$\{(.+?)\}/';
+
+ /**
+ * @var KeyValue[]
+ */
+ protected $collection = [];
+
+ /**
+ * @var KeyValue[]
+ */
+ protected $cache = [];
+
+ /**
+ * @inheritdoc
+ * @param KeyValue[] $collection
+ */
+ public function setRawCollection(array $collection): void
+ {
+ $this->collection = [];
+ $this->cache = [];
+ foreach ($collection as $keyValue) {
+ $this->collection[$keyValue->getKey()] = $keyValue;
+ }
+ }
+
+ /**
+ * @inheritdoc
+ * @param KeyValue $keyValue
+ * @return KeyValue
+ */
+ public function compileKeyValue(KeyValue $keyValue): KeyValue
+ {
+ $newValue = preg_replace_callback(self::REGEX_VARIABLE, function ($variable) use ($keyValue) {
+ $variable = $variable[1];
+ if ($variable === $keyValue->getKey()) {
+ throw new RuntimeException('Self referencing');
+ }
+ if (isset($this->cache[$variable])) {
+ return $this->cache[$variable]->getValue();
+ } elseif (isset($this->collection[$variable]) && !$this->needToCompile($this->collection[$variable])) {
+ return $this->collection[$variable]->getValue();
+ } elseif (isset($this->collection[$variable]) && $this->needToCompile($this->collection[$variable])) {
+ return $this->compileKeyValue($this->collection[$variable])->getValue();
+ }
+ return "UNKNOWN VARIABLE {$variable}";
+ }, $keyValue->getValue());
+ $result = new KeyValue($keyValue->getKey(), $newValue);
+ $this->cache[$result->getKey()] = $result;
+
+ return $result;
+ }
+
+ /**
+ * @param KeyValue $keyValue
+ * @return bool
+ */
+ protected function needToCompile(KeyValue $keyValue): bool
+ {
+ return !!preg_match(self::REGEX_VARIABLE, $keyValue->getValue());
+ }
+} \ No newline at end of file
diff --git a/src/Compiler/CompilerInterface.php b/src/Compiler/CompilerInterface.php
index 042a7d0..15b6823 100644
--- a/src/Compiler/CompilerInterface.php
+++ b/src/Compiler/CompilerInterface.php
@@ -1,4 +1,6 @@
<?php
+declare(strict_types=1);
+
/**
* @author: Alexander Kiryukhin <alexander@kiryukhin.su>
* @license: MIT
@@ -8,16 +10,20 @@ namespace NeonXP\Dotenv\Compiler;
use NeonXP\Dotenv\Types\KeyValue;
+/**
+ * Interface CompilerInterface
+ * @package NeonXP\Dotenv\Compiler
+ */
interface CompilerInterface
{
/**
* @param KeyValue[] $collection
*/
- function setRawCollection(array $collection): void;
+ public function setRawCollection(array $collection): void;
/**
* @param KeyValue $keyValue
* @return KeyValue
*/
- function compileKeyValue(KeyValue $keyValue): KeyValue;
+ public function compileKeyValue(KeyValue $keyValue): KeyValue;
} \ No newline at end of file
diff --git a/src/Dotenv.php b/src/Dotenv.php
index 8e7ac10..3f3b0c6 100644
--- a/src/Dotenv.php
+++ b/src/Dotenv.php
@@ -1,4 +1,6 @@
<?php
+declare(strict_types=1);
+
/**
* @author: Alexander Kiryukhin <alexander@kiryukhin.su>
* @license: MIT
@@ -8,10 +10,16 @@ namespace NeonXP\Dotenv;
use NeonXP\Dotenv\Compiler\CompilerInterface;
use NeonXP\Dotenv\Exception\RuntimeException;
+use NeonXP\Dotenv\Loader\FileLoader;
use NeonXP\Dotenv\Loader\LoaderInterface;
+use NeonXP\Dotenv\Parser\Parser;
use NeonXP\Dotenv\Parser\ParserInterface;
use NeonXP\Dotenv\Types\KeyValue;
+/**
+ * Class Dotenv
+ * @package NeonXP\Dotenv
+ */
class Dotenv implements \ArrayAccess, \IteratorAggregate
{
/**
@@ -40,6 +48,13 @@ class Dotenv implements \ArrayAccess, \IteratorAggregate
*/
public function __construct(LoaderInterface $loader = null, ParserInterface $parser = null, CompilerInterface $compiler = null)
{
+ if (!$loader) {
+ $loader = new FileLoader(); // Default loader
+ }
+ if (!$parser) {
+ $parser = new Parser(); // Default parser
+ }
+
$this->loader = $loader;
$this->parser = $parser;
$this->compiler = $compiler;
@@ -63,6 +78,10 @@ class Dotenv implements \ArrayAccess, \IteratorAggregate
},
[]
);
+ foreach ($this->loadedValues as $key => $value) {
+ $_ENV[$key] = $value;
+ putenv($key . "=" . $value);
+ }
return $this;
}
@@ -85,7 +104,7 @@ class Dotenv implements \ArrayAccess, \IteratorAggregate
* @return mixed
* @throws RuntimeException
*/
- public function get(string $key, mixed $default = null): mixed
+ public function get(string $key, $default = null)
{
if (!$this->has($key)) {
return $default;
@@ -122,7 +141,7 @@ class Dotenv implements \ArrayAccess, \IteratorAggregate
* @return bool
* @throws RuntimeException
*/
- public function offsetExists($offset)
+ public function offsetExists($offset): bool
{
return $this->has($offset);
}
diff --git a/src/Exception/RuntimeException.php b/src/Exception/RuntimeException.php
index 1a9be9c..5728623 100644
--- a/src/Exception/RuntimeException.php
+++ b/src/Exception/RuntimeException.php
@@ -1,4 +1,6 @@
<?php
+declare(strict_types=1);
+
/**
* @author: Alexander Kiryukhin <alexander@kiryukhin.su>
* @license: MIT
@@ -6,7 +8,10 @@
namespace NeonXP\Dotenv\Exception;
-
+/**
+ * Class RuntimeException
+ * @package NeonXP\Dotenv\Exception
+ */
class RuntimeException extends \Exception
{
diff --git a/src/Loader/FileLoader.php b/src/Loader/FileLoader.php
new file mode 100644
index 0000000..0702a53
--- /dev/null
+++ b/src/Loader/FileLoader.php
@@ -0,0 +1,41 @@
+<?php
+declare(strict_types=1);
+
+/**
+ * @author: Alexander Kiryukhin <alexander@kiryukhin.su>
+ * @license: MIT
+ */
+
+namespace NeonXP\Dotenv\Loader;
+
+use NeonXP\Dotenv\Exception\RuntimeException;
+
+/**
+ * Class FileLoader
+ * @package NeonXP\Dotenv\Loader
+ */
+class FileLoader implements LoaderInterface
+{
+ const COMMENT_LINE_REGEX = '/^\s*#/';
+
+ /**
+ * @inheritdoc
+ * @param string $filePath
+ * @return array
+ * @throws RuntimeException
+ */
+ public function load(string $filePath = '.env'): array
+ {
+ if (!file_exists($filePath)) {
+ throw new RuntimeException("There is no {$filePath} file!");
+ }
+ $lines = file($filePath);
+ $lines = array_map('trim', $lines);
+ $lines = array_filter($lines, function (string $line) {
+ return trim($line) && !preg_match(self::COMMENT_LINE_REGEX, $line);
+ });
+ $lines = array_values($lines);
+
+ return $lines;
+ }
+} \ No newline at end of file
diff --git a/src/Loader/LoaderInterface.php b/src/Loader/LoaderInterface.php
index 1c4f166..c848e6c 100644
--- a/src/Loader/LoaderInterface.php
+++ b/src/Loader/LoaderInterface.php
@@ -1,4 +1,6 @@
<?php
+declare(strict_types=1);
+
/**
* @author: Alexander Kiryukhin <alexander@kiryukhin.su>
* @license: MIT
@@ -6,9 +8,14 @@
namespace NeonXP\Dotenv\Loader;
+/**
+ * Interface LoaderInterface
+ * @package NeonXP\Dotenv\Loader
+ */
interface LoaderInterface
{
/**
+ * Load not empty lines from file or other source
* @param string $filePath
* @return string[]
*/
diff --git a/src/Parser/Parser.php b/src/Parser/Parser.php
new file mode 100644
index 0000000..7a5c37b
--- /dev/null
+++ b/src/Parser/Parser.php
@@ -0,0 +1,55 @@
+<?php
+declare(strict_types=1);
+
+/**
+ * @author: Alexander Kiryukhin <alexander@kiryukhin.su>
+ * @license: MIT
+ */
+
+namespace NeonXP\Dotenv\Parser;
+
+use NeonXP\Dotenv\Types\KeyValue;
+
+/**
+ * Class Parser
+ * @package NeonXP\Dotenv\Parser
+ */
+class Parser implements ParserInterface
+{
+ const REGEX_EXPORT_PREFIX = '/^\s*export\s/i';
+
+ // Quotes and comments
+ const SINGLE_QUOTED_LINE_WITH_COMMENT = '/^\'(.*?)\'\s+#.*?$/i';
+ const DOUBLE_QUOTED_LINE_WITH_COMMENT = '/^\"(.+?)\"\s+#.*?$/i';
+ const SINGLE_QUOTED_LINE = '/^\'(.+?)\'$/i';
+ const DOUBLE_QUOTED_LINE = '/^\"(.*?)\"$/i';
+
+ // Types
+ const BOOLEAN = '/^(true|false)$/i';
+ const NUMBER = '/^(\d+)$/';
+
+ public function parseLine(string $line): KeyValue
+ {
+ $line = preg_replace(self::REGEX_EXPORT_PREFIX, '', $line);
+ list($key, $value) = explode('=', $line, 2) + ['', ''];
+ $key = trim($key);
+ $value = trim($value);
+ $matches = [];
+ if (
+ preg_match(self::SINGLE_QUOTED_LINE_WITH_COMMENT, $value, $matches) ||
+ preg_match(self::DOUBLE_QUOTED_LINE_WITH_COMMENT, $value, $matches) ||
+ preg_match(self::SINGLE_QUOTED_LINE, $value, $matches) ||
+ preg_match(self::DOUBLE_QUOTED_LINE, $value, $matches)
+ ) {
+ $value = $matches[1];
+ }
+
+ if (preg_match(self::BOOLEAN, $value)) {
+ $value = (strtolower($value) === 'true');
+ } elseif (preg_match(self::NUMBER, $value)) {
+ $value = intval($value);
+ }
+
+ return new KeyValue($key, $value);
+ }
+} \ No newline at end of file
diff --git a/src/Parser/ParserInterface.php b/src/Parser/ParserInterface.php
index b53e29f..f67ffb5 100644
--- a/src/Parser/ParserInterface.php
+++ b/src/Parser/ParserInterface.php
@@ -1,4 +1,6 @@
<?php
+declare(strict_types=1);
+
/**
* @author: Alexander Kiryukhin <alexander@kiryukhin.su>
* @license: MIT
@@ -8,6 +10,10 @@ namespace NeonXP\Dotenv\Parser;
use NeonXP\Dotenv\Types\KeyValue;
+/**
+ * Interface ParserInterface
+ * @package NeonXP\Dotenv\Parser
+ */
interface ParserInterface
{
/**
diff --git a/src/Types/KeyValue.php b/src/Types/KeyValue.php
index e17dc4d..54b0360 100644
--- a/src/Types/KeyValue.php
+++ b/src/Types/KeyValue.php
@@ -1,4 +1,6 @@
<?php
+declare(strict_types=1);
+
/**
* @author: Alexander Kiryukhin <alexander@kiryukhin.su>
* @license: MIT
@@ -6,7 +8,10 @@
namespace NeonXP\Dotenv\Types;
-
+/**
+ * Class KeyValue
+ * @package NeonXP\Dotenv\Types
+ */
class KeyValue
{
/**
@@ -24,7 +29,7 @@ class KeyValue
* @param string $key
* @param mixed $value
*/
- public function __construct(string $key, mixed $value)
+ public function __construct(string $key, $value)
{
$this->key = $key;
$this->value = $value;
@@ -41,7 +46,7 @@ class KeyValue
/**
* @return mixed
*/
- public function getValue(): mixed
+ public function getValue()
{
return $this->value;
}
diff --git a/tests/CompilerTest.php b/tests/CompilerTest.php
new file mode 100644
index 0000000..9a820ec
--- /dev/null
+++ b/tests/CompilerTest.php
@@ -0,0 +1,44 @@
+<?php
+declare(strict_types=1);
+
+/**
+ * @author: Alexander Kiryukhin <alexander@kiryukhin.su>
+ * @license: MIT
+ */
+
+use NeonXP\Dotenv\Types\KeyValue;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * Class CompilerTest
+ */
+class CompilerTest extends TestCase
+{
+ public function testParseLines()
+ {
+ $collection = [
+ 'KEY1' => 'VALUE1',
+ 'KEY2' => '${KEY1} ${KEY3}',
+ 'KEY3' => 'VALUE3',
+ 'KEY4' => 'Test ${KEY2} => ${KEY3}'
+ ];
+ $tests = [
+ 'KEY1' => 'VALUE1',
+ 'KEY2' => 'VALUE1 VALUE3',
+ 'KEY3' => 'VALUE3',
+ 'KEY4' => 'Test VALUE1 VALUE3 => VALUE3',
+ ];
+ $compiler = new \NeonXP\Dotenv\Compiler\Compiler();
+ $collectionOfKeyValues = [];
+ foreach ($collection as $key => $value) {
+ $collectionOfKeyValues[] = new KeyValue($key, $value);
+ }
+ $compiler->setRawCollection($collectionOfKeyValues);
+
+ foreach ($tests as $key => $expected) {
+ $result = $compiler->compileKeyValue(new KeyValue($key, $collection[$key]));
+ $this->assertEquals($key, $result->getKey());
+ $this->assertEquals($expected, $result->getValue());
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/DotenvTest.php b/tests/DotenvTest.php
new file mode 100644
index 0000000..59dd8b0
--- /dev/null
+++ b/tests/DotenvTest.php
@@ -0,0 +1,68 @@
+<?php
+declare(strict_types=1);
+
+/**
+ * @author: Alexander Kiryukhin <alexander@kiryukhin.su>
+ * @license: MIT
+ */
+
+use NeonXP\Dotenv\Compiler\CompilerInterface;
+use NeonXP\Dotenv\Dotenv;
+use NeonXP\Dotenv\Exception\RuntimeException;
+use NeonXP\Dotenv\Loader\LoaderInterface;
+use NeonXP\Dotenv\Parser\ParserInterface;
+
+use PHPUnit\Framework\TestCase;
+
+/**
+ * Class TestDotenv
+ */
+class DotenvTest extends TestCase
+{
+ /**
+ * @var LoaderInterface
+ */
+ private $mockLoader;
+
+ /**
+ * @var ParserInterface
+ */
+ private $mockParser;
+
+ /**
+ * @var CompilerInterface
+ */
+ private $mockCompiler;
+
+ public function setUp()
+ {
+ $this->mockLoader = new MockLoader();
+ $this->mockParser = new MockParser();
+ $this->mockCompiler = new MockCompiler();
+ }
+
+ public function testLoad()
+ {
+ $dotenv = new Dotenv($this->mockLoader, $this->mockParser, $this->mockCompiler);
+
+ try {
+ $dotenv->get('TEST1');
+ $this->assertTrue(false, 'Dotenv must throws exception if it not loaded');
+ } catch (RuntimeException $exception) {
+ $this->assertTrue(true, 'Dotenv must throws exception if it not loaded');
+ }
+
+ $dotenv->load();
+
+ $this->assertNull( $dotenv->get('NOT_EXISTS'));
+ $this->assertEquals('default value', $dotenv->get('NOT_EXISTS', 'default value'));
+ $this->assertEquals('VALUE3', $dotenv->get('KEY3', 'default value'));
+ $this->assertEquals('VALUE3', $dotenv['KEY3']);
+ $idx = 1;
+ foreach ($dotenv as $key => $value) {
+ $this->assertEquals("KEY{$idx}", $key);
+ $this->assertEquals("VALUE{$idx}", $value);
+ $idx++;
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/FileLoaderTest.php b/tests/FileLoaderTest.php
new file mode 100644
index 0000000..16c7a43
--- /dev/null
+++ b/tests/FileLoaderTest.php
@@ -0,0 +1,23 @@
+<?php
+declare(strict_types=1);
+
+/**
+ * @author: Alexander Kiryukhin <alexander@kiryukhin.su>
+ * @license: MIT
+ */
+use PHPUnit\Framework\TestCase;
+
+/**
+ * Class FileLoaderTest
+ */
+class FileLoaderTest extends TestCase
+{
+ public function testLoadfile()
+ {
+ $loader = new NeonXP\Dotenv\Loader\FileLoader();
+
+ $result = $loader->load(__DIR__ . '/misc/.env.test');
+
+ $this->assertEquals(['KEY1=VALUE1', 'KEY2=VALUE2'], $result);
+ }
+} \ No newline at end of file
diff --git a/tests/ParserTest.php b/tests/ParserTest.php
new file mode 100644
index 0000000..9f13261
--- /dev/null
+++ b/tests/ParserTest.php
@@ -0,0 +1,37 @@
+<?php
+declare(strict_types=1);
+
+/**
+ * @author: Alexander Kiryukhin <alexander@kiryukhin.su>
+ * @license: MIT
+ */
+use NeonXP\Dotenv\Parser\Parser;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * Class ParserTest
+ */
+class ParserTest extends TestCase
+{
+ public function testParseLines()
+ {
+ $tests = [
+ "key1='value1' # comment" => ['key1', 'value1'],
+ "key2 = 'value2'" => ['key2', 'value2'],
+ "key3 = \"value3\" # comment" => ['key3', 'value3'],
+ "key4 =\"value4\"" => ['key4', 'value4'],
+ "key5 ='value5 # not comment'" => ['key5', 'value5 # not comment'],
+ "key6 = \"value6 # not comment\"" => ['key6', 'value6 # not comment'],
+ "boolean=true" => ['boolean', true],
+ "numeric = 123" => ['numeric', 123]
+ ];
+
+ $parser = new Parser();
+
+ foreach ($tests as $test => $expected) {
+ $result = $parser->parseLine($test);
+ $this->assertEquals($expected[0], $result->getKey());
+ $this->assertEquals($expected[1], $result->getValue());
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
new file mode 100644
index 0000000..88dc08e
--- /dev/null
+++ b/tests/bootstrap.php
@@ -0,0 +1,20 @@
+<?php
+declare(strict_types=1);
+
+/**
+ * @author: Alexander Kiryukhin <alexander@kiryukhin.su>
+ * @license: MIT
+ */
+
+require_once (__DIR__ . "/mocks/MockLoader.php");
+require_once (__DIR__ . "/mocks/MockParser.php");
+require_once (__DIR__ . "/mocks/MockCompiler.php");
+
+$vendorDir = __DIR__ . '/../../..';
+if (file_exists($file = $vendorDir . '/autoload.php')) {
+ require_once $file;
+} else if (file_exists($file = './vendor/autoload.php')) {
+ require_once $file;
+} else {
+ throw new \RuntimeException("Not found composer autoload");
+} \ No newline at end of file
diff --git a/tests/misc/.env.test b/tests/misc/.env.test
new file mode 100644
index 0000000..e781a8d
--- /dev/null
+++ b/tests/misc/.env.test
@@ -0,0 +1,7 @@
+# This is comment
+
+# Before was empty line
+KEY1=VALUE1
+
+KEY2=VALUE2
+#KEY3=VALUE3
diff --git a/tests/mocks/MockCompiler.php b/tests/mocks/MockCompiler.php
new file mode 100644
index 0000000..89db68f
--- /dev/null
+++ b/tests/mocks/MockCompiler.php
@@ -0,0 +1,34 @@
+<?php
+declare(strict_types=1);
+
+/**
+ * @author: Alexander Kiryukhin <alexander@kiryukhin.su>
+ * @license: MIT
+ */
+
+use NeonXP\Dotenv\Compiler\CompilerInterface;
+use NeonXP\Dotenv\Types\KeyValue;
+
+/**
+ * Class MockCompiler
+ */
+class MockCompiler implements CompilerInterface
+{
+
+ /**
+ * @param KeyValue[] $collection
+ */
+ function setRawCollection(array $collection): void
+ {
+ // Do nothing
+ }
+
+ /**
+ * @param KeyValue $keyValue
+ * @return KeyValue
+ */
+ function compileKeyValue(KeyValue $keyValue): KeyValue
+ {
+ return $keyValue;
+ }
+} \ No newline at end of file
diff --git a/tests/mocks/MockLoader.php b/tests/mocks/MockLoader.php
new file mode 100644
index 0000000..bb0d557
--- /dev/null
+++ b/tests/mocks/MockLoader.php
@@ -0,0 +1,32 @@
+<?php
+declare(strict_types=1);
+
+/**
+ * @author: Alexander Kiryukhin <alexander@kiryukhin.su>
+ * @license: MIT
+ */
+
+use NeonXP\Dotenv\Loader\LoaderInterface;
+
+/**
+ * Class MockLoader
+ */
+class MockLoader implements LoaderInterface
+{
+
+ /**
+ * Load not empty lines from file or other source
+ * @param string $filePath
+ * @return string[]
+ */
+ public function load(string $filePath = '.env'): array
+ {
+ return [
+ 'KEY1=VALUE1',
+ 'KEY2=VALUE2',
+ 'KEY3=VALUE3',
+ 'KEY4=VALUE4',
+ 'KEY5=VALUE5',
+ ];
+ }
+} \ No newline at end of file
diff --git a/tests/mocks/MockParser.php b/tests/mocks/MockParser.php
new file mode 100644
index 0000000..39c369c
--- /dev/null
+++ b/tests/mocks/MockParser.php
@@ -0,0 +1,28 @@
+<?php
+declare(strict_types=1);
+
+/**
+ * @author: Alexander Kiryukhin <alexander@kiryukhin.su>
+ * @license: MIT
+ */
+
+use NeonXP\Dotenv\Parser\ParserInterface;
+use NeonXP\Dotenv\Types\KeyValue;
+
+/**
+ * Class MockParser
+ */
+class MockParser implements ParserInterface
+{
+
+ /**
+ * @param string $line
+ * @return KeyValue
+ */
+ public function parseLine(string $line): KeyValue
+ {
+ list($key, $value) = explode("=", $line);
+
+ return new KeyValue($key, $value);
+ }
+} \ No newline at end of file