diff --git a/.idea/workspace.xml b/.idea/workspace.xml index fd40fa3..94b79ae 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -2,12 +2,14 @@ + + + + - - - + - - - - - + + + + + @@ -133,11 +135,11 @@ + - @@ -155,6 +157,7 @@ + @@ -166,7 +169,9 @@ - + + diff --git a/composer.lock b/composer.lock index e37b6ae..e01017d 100644 --- a/composer.lock +++ b/composer.lock @@ -906,16 +906,16 @@ }, { "name": "phpunit/phpunit", - "version": "8.4.1", + "version": "8.4.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "366a4a0f2b971fd43b7c351d621e8dd7d7131869" + "reference": "67f9e35bffc0dd52d55d565ddbe4230454fd6a4e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/366a4a0f2b971fd43b7c351d621e8dd7d7131869", - "reference": "366a4a0f2b971fd43b7c351d621e8dd7d7131869", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/67f9e35bffc0dd52d55d565ddbe4230454fd6a4e", + "reference": "67f9e35bffc0dd52d55d565ddbe4230454fd6a4e", "shasum": "" }, "require": { @@ -985,7 +985,7 @@ "testing", "xunit" ], - "time": "2019-10-07T12:57:41+00:00" + "time": "2019-11-06T09:42:23+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -1662,16 +1662,16 @@ }, { "name": "symfony/yaml", - "version": "v4.3.5", + "version": "v4.3.6", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "41e16350a2a1c7383c4735aa2f9fce74cf3d1178" + "reference": "324cf4b19c345465fad14f3602050519e09e361d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/41e16350a2a1c7383c4735aa2f9fce74cf3d1178", - "reference": "41e16350a2a1c7383c4735aa2f9fce74cf3d1178", + "url": "https://api.github.com/repos/symfony/yaml/zipball/324cf4b19c345465fad14f3602050519e09e361d", + "reference": "324cf4b19c345465fad14f3602050519e09e361d", "shasum": "" }, "require": { @@ -1717,7 +1717,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2019-09-11T15:41:19+00:00" + "time": "2019-10-30T12:58:49+00:00" }, { "name": "theseer/tokenizer", diff --git a/src/EnvParser.php b/src/EnvParser.php index fcb8337..646552e 100644 --- a/src/EnvParser.php +++ b/src/EnvParser.php @@ -21,13 +21,25 @@ class EnvParser implements Tokens, EnvParserInterface */ private $handler; + /** + * @var array + */ + private $typeCasters = [ + ]; + + + /** + * @var ValueType + */ + private $typeChecker; + /** * Holder for parsed Environment Variables * * @internal * @var null|array */ - private $envs = NULL; + private $envs; /** * Flag that doesn't allows the lexing and paring stages to happen twice @@ -41,9 +53,11 @@ class EnvParser implements Tokens, EnvParserInterface * * @param string $file * - * @throws Exception + * @param array $typeCasters + * @param ValueType|null $typeChecker + * @param bool $emptyStringNull */ - public function __construct(string $file) + public function __construct(string $file, ?array $typeCasters = null, ValueType $typeChecker = null, bool $emptyStringNull = true) { $this->handler = new File($file); if ($this->handler === NULL) { @@ -55,6 +69,16 @@ public function __construct(string $file) if (!$this->handler->isReadable()) { throw new RuntimeException($file . ' is not readable'); } + + if($typeCasters !== null) { + $this->typeCasters = array_merge($this->typeCasters, $typeCasters); + } + if($typeChecker === null) + { + $typeChecker = new TypeChecker($emptyStringNull); + } + + $this->typeChecker = $typeChecker; } @@ -109,13 +133,14 @@ private function extractName(string $startingChar, int & $column): string while (($c = $this->handler->fgetc()) === self::SPACE) { continue; } - if ($c === self::EQUALS) break; - else { - $error = new DotEnvSyntaxError('Spaces are now allowed in env variable name'); - $error->setEnvLine($this->handler->key()); - $error->setColumn($column); - throw $error; + if ($c === self::EQUALS) { + break; } + + $error = new DotEnvSyntaxError('Spaces are now allowed in env variable name'); + $error->setEnvLine($this->handler->key()); + $error->setColumn($column); + throw $error; } if ($c === self::CARRIAGE_RETURN || $c === self::NEW_LINE || $c === self::COMMENT) { $error = new DotEnvSyntaxError('Unexpected end of line'); @@ -136,17 +161,18 @@ private function extractName(string $startingChar, int & $column): string * @param bool $raw * @param int $column * - * @return string + * @return int|string|float|null * @throws EnvVariableNotFound */ - private function extractValue(array $envs, bool $raw, int & $column): string + private function extractValue(array $envs, bool $raw, int & $column) { $value = ''; // Trimming the leading spaces of the value while (($c = $this->handler->fgetc()) === self::SPACE) { $column++; continue; - }; + } + $this->handler->fseek($this->handler->ftell() - 1); // Handling Multiline values @@ -187,7 +213,8 @@ private function extractValue(array $envs, bool $raw, int & $column): string } $column++; } - return $value; + + return $this->typeChecker->detectValue($value); } /** diff --git a/src/Parser.php b/src/Parser.php new file mode 100644 index 0000000..57c2609 --- /dev/null +++ b/src/Parser.php @@ -0,0 +1,14 @@ +emptyStringAsNull = $emptyStringAsNull; + } + + public function detectValue(string $value) + { + $toLower = strtolower($value); + + // Detecting NULL + if (strcmp('null', $toLower) === 0 || ($this->emptyStringAsNull === true && strcmp('', $toLower) === 0)) { + return null; + } + + // Detecting BOOLEANS + if (($isBoolean = $this->checkForBoolean($toLower)) !== null) { + return (bool)$isBoolean; + } + + // Detecting FLOATS + $trimmed = ltrim($toLower, '0'); + + + + $casted = (float)$toLower; + + if (($casted !== 0.0 || strcmp('.0', $trimmed) === 0) && preg_match('/^\d+\.\d+$/', $toLower) === 1) { + return $casted; + } + + // Detecting INTEGERS + $casted = (int)$trimmed; + + if ($casted !== 0 || strcmp('', $trimmed) === 0) { + return $casted; + } + + return $value; + } + + + private function checkForBoolean(string $value): ?int + { + switch ($value) { + case 'ok': +// case '1': + case 'true': + case 'yes': + case 'y': + return true; + case 'no': +// case '0': + case 'false': + return false; + default: + return null; + } + } +} \ No newline at end of file diff --git a/src/ValueType.php b/src/ValueType.php new file mode 100644 index 0000000..b6ea7a0 --- /dev/null +++ b/src/ValueType.php @@ -0,0 +1,16 @@ +fail($e->getMessage()); } } + + public function test_casting(): void + { + try { + $parser = new EnvParser(__DIR__ . '/.env.casts'); + $parser->parse(); + $envs = $parser->getEnvs(); + $this->assertIsArray($envs); + + $this->assertIsInt($envs['INTEGERS']); + $this->assertIsInt($envs['INT_ZERO']); + $this->assertIsBool($envs['BOOLEAN']); + $this->assertIsString($envs['STR']); + $this->assertIsFloat($envs['FLOATS']); + $this->assertIsFloat($envs['FLOAT_ZERO']); + $this->assertNull($envs['NULLABLE']); + $this->assertNull($envs['UPPERNULL']); + $this->assertEquals(123,$envs['INTEGERS']); + $this->assertEquals(0,$envs['INT_ZERO']); + $this->assertEquals(false,$envs['BOOLEAN']); + $this->assertEquals(true,$envs['BOOLEAN_TRUE']); + $this->assertEquals(1.65,$envs['FLOATS']); + $this->assertEquals(0.0,$envs['FLOAT_ZERO']); + } catch (Exception $e) { + $this->fail($e->getMessage()); + } + } }