Jelajahi Sumber

Initial release of php-dotenv

michelphp 1 hari lalu
melakukan
9cb5c84e69

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+/vendor/
+/.idea/
+composer.lock

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 F. Michel
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 237 - 0
README.md

@@ -0,0 +1,237 @@
+# PHP-DotEnv
+
+PHP-DotEnv is a lightweight and robust PHP library designed to simplify the management of environment variables in your PHP applications. It parses a `.env` file and loads the variables into `getenv()`, `$_ENV`, and `$_SERVER`.
+
+It comes with a powerful processor system that automatically converts values (booleans, nulls, numbers) and allows you to define your own custom processors for specific needs.
+
+---
+
+## English Documentation
+
+### Installation
+
+Requires PHP 7.4 or higher.
+
+Install via Composer:
+
+```bash
+composer require michel/dotenv
+```
+
+### Basic Usage
+
+1.  **Create a `.env` file** in your project root:
+
+    ```dotenv
+    APP_ENV=dev
+    DATABASE_URL="mysql:host=localhost;dbname=test"
+    DEBUG=true
+    CACHE_TTL=3600
+    API_KEY=null
+    # This is a comment
+    ```
+
+2.  **Load the variables** in your PHP entry point (e.g., `index.php`):
+
+    ```php
+    <?php
+    require 'vendor/autoload.php';
+
+    use Michel\Env\DotEnv;
+
+    $envFile = __DIR__ . '/.env';
+
+    // Load environment variables
+    (new DotEnv($envFile))->load();
+
+    // Access variables
+    var_dump(getenv('APP_ENV')); // string(3) "dev"
+    var_dump($_ENV['DEBUG']);    // bool(true)
+    ```
+
+### Features
+
+#### 1. Automatic Type Conversion (Default Processors)
+PHP-DotEnv automatically processes values using default processors:
+
+-   **BooleanProcessor**: Converts `true` and `false` (case-insensitive) to PHP `bool`.
+-   **NullProcessor**: Converts `null` (case-insensitive) to PHP `null`.
+-   **NumberProcessor**: Converts numeric strings to `int` or `float`.
+-   **QuotedProcessor**: Removes surrounding double quotes `"` or single quotes `'` from strings.
+
+#### 2. Comments
+Lines starting with `#` are treated as comments and ignored.
+
+#### 3. Trimming
+Keys and values are automatically trimmed of whitespace.
+
+### Advanced Usage: Custom Processors
+
+You can extend the functionality by creating custom processors. A processor must extend `Michel\Env\Processor\AbstractProcessor`.
+
+#### Creating a Custom Processor
+
+Example: A processor that converts comma-separated strings into arrays.
+
+```php
+namespace App\Processor;
+
+use Michel\Env\Processor\AbstractProcessor;
+
+class ArrayProcessor extends AbstractProcessor
+{
+    public function canBeProcessed(): bool
+    {
+        // Process only if value contains a comma
+        return strpos($this->value, ',') !== false;
+    }
+
+    public function execute()
+    {
+        return array_map('trim', explode(',', $this->value));
+    }
+}
+```
+
+#### Registering Custom Processors
+
+Pass an array of processor class names to the `DotEnv` constructor.
+**Note:** When you pass custom processors, you must also include the default ones if you still want them to run.
+
+```php
+use Michel\Env\DotEnv;
+use Michel\Env\Processor\BooleanProcessor;
+use Michel\Env\Processor\NullProcessor;
+use Michel\Env\Processor\NumberProcessor;
+use Michel\Env\Processor\QuotedProcessor;
+use App\Processor\ArrayProcessor;
+
+$processors = [
+    BooleanProcessor::class,
+    NullProcessor::class,
+    NumberProcessor::class,
+    QuotedProcessor::class,
+    ArrayProcessor::class // Your custom processor
+];
+
+(new DotEnv(__DIR__ . '/.env', $processors))->load();
+```
+
+---
+
+## 🇫🇷 Documentation Française
+
+### Introduction
+
+PHP-DotEnv est une bibliothèque PHP légère conçue pour simplifier la gestion des variables d'environnement dans vos applications PHP. Elle analyse un fichier `.env` et charge les variables dans `getenv()`, `$_ENV` et `$_SERVER`.
+
+Elle intègre un système de processeurs qui convertit automatiquement les valeurs (booléens, null, nombres) et vous permet de définir vos propres processeurs personnalisés.
+
+### Installation
+
+Nécessite PHP 7.4 ou supérieur.
+
+Installez via Composer :
+
+```bash
+composer require michel/dotenv
+```
+
+### Utilisation de Base
+
+1.  **Créez un fichier `.env`** à la racine de votre projet :
+
+    ```dotenv
+    APP_ENV=dev
+    DATABASE_URL="mysql:host=localhost;dbname=test"
+    DEBUG=true
+    CACHE_TTL=3600
+    API_KEY=null
+    # Ceci est un commentaire
+    ```
+
+2.  **Chargez les variables** dans votre point d'entrée PHP (ex: `index.php`) :
+
+    ```php
+    <?php
+    require 'vendor/autoload.php';
+
+    use Michel\Env\DotEnv;
+
+    $envFile = __DIR__ . '/.env';
+
+    // Charger les variables d'environnement
+    (new DotEnv($envFile))->load();
+
+    // Accéder aux variables
+    var_dump(getenv('APP_ENV')); // string(3) "dev"
+    var_dump($_ENV['DEBUG']);    // bool(true)
+    ```
+
+### Fonctionnalités
+
+#### 1. Conversion Automatique des Types (Processeurs par défaut)
+PHP-DotEnv traite automatiquement les valeurs à l'aide de processeurs par défaut :
+
+-   **BooleanProcessor** : Convertit `true` et `false` (insensible à la casse) en `bool` PHP.
+-   **NullProcessor** : Convertit `null` (insensible à la casse) en `null` PHP.
+-   **NumberProcessor** : Convertit les chaînes numériques en `int` ou `float`.
+-   **QuotedProcessor** : Supprime les guillemets doubles `"` ou simples `'` entourant les chaînes.
+
+#### 2. Commentaires
+Les lignes commençant par `#` sont traitées comme des commentaires et ignorées.
+
+#### 3. Nettoyage (Trimming)
+Les espaces autour des clés et des valeurs sont automatiquement supprimés.
+
+### Utilisation Avancée : Processeurs Personnalisés
+
+Vous pouvez étendre les fonctionnalités en créant des processeurs personnalisés. Un processeur doit étendre `Michel\Env\Processor\AbstractProcessor`.
+
+#### Créer un Processeur Personnalisé
+
+Exemple : Un processeur qui convertit les chaînes séparées par des virgules en tableaux.
+
+```php
+namespace App\Processor;
+
+use Michel\Env\Processor\AbstractProcessor;
+
+class ArrayProcessor extends AbstractProcessor
+{
+    public function canBeProcessed(): bool
+    {
+        // Traiter uniquement si la valeur contient une virgule
+        return strpos($this->value, ',') !== false;
+    }
+
+    public function execute()
+    {
+        return array_map('trim', explode(',', $this->value));
+    }
+}
+```
+
+#### Enregistrer les Processeurs Personnalisés
+
+Passez un tableau de noms de classes de processeurs au constructeur de `DotEnv`.
+**Note :** Lorsque vous passez des processeurs personnalisés, vous devez également inclure ceux par défaut si vous souhaitez qu'ils continuent de fonctionner.
+
+```php
+use Michel\Env\DotEnv;
+use Michel\Env\Processor\BooleanProcessor;
+use Michel\Env\Processor\NullProcessor;
+use Michel\Env\Processor\NumberProcessor;
+use Michel\Env\Processor\QuotedProcessor;
+use App\Processor\ArrayProcessor;
+
+$processors = [
+    BooleanProcessor::class,
+    NullProcessor::class,
+    NumberProcessor::class,
+    QuotedProcessor::class,
+    ArrayProcessor::class // Votre processeur personnalisé
+];
+
+(new DotEnv(__DIR__ . '/.env', $processors))->load();
+```

+ 24 - 0
composer.json

@@ -0,0 +1,24 @@
+{
+    "name": "michel/dotenv",
+    "description": "PHP-DotEnv is a lightweight PHP library designed to simplify the management of environment variables in your PHP applications.",
+    "type": "library",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "F. Michel"
+        }
+    ],
+    "autoload": {
+        "psr-4": {
+            "Michel\\Env\\": "src",
+            "Test\\Michel\\Env\\": "tests"
+        }
+    },
+    "require": {
+        "php": ">=7.4"
+    },
+    "minimum-stability": "alpha",
+    "require-dev": {
+        "michel/unitester": "^1.0.0"
+    }
+}

+ 119 - 0
src/DotEnv.php

@@ -0,0 +1,119 @@
+<?php
+
+namespace Michel\Env;
+
+use Michel\Env\Processor\AbstractProcessor;
+use Michel\Env\Processor\BooleanProcessor;
+use Michel\Env\Processor\NullProcessor;
+use Michel\Env\Processor\NumberProcessor;
+use Michel\Env\Processor\QuotedProcessor;
+use InvalidArgumentException;
+use RuntimeException;
+
+class DotEnv
+{
+    /**
+     * The directory where the .env file can be located.
+     *
+     * @var string
+     */
+    protected string $path;
+
+    /**
+     * Configure the options on which the parsed will act
+     *
+     * @var string[]
+     */
+    protected array $processors = [];
+
+    public function __construct(string $path, ?array $processors = null)
+    {
+        if (!file_exists($path)) {
+            throw new InvalidArgumentException(sprintf('%s does not exist', $path));
+        }
+
+        $this->path = $path;
+
+        $this->setProcessors($processors);
+    }
+
+    /**
+     * Loads the configuration data from the specified file path.
+     * Parses the values into $_SERVER and $_ENV arrays, skipping empty and commented lines.
+     */
+    public function load(): void
+    {
+        if (!is_readable($this->path)) {
+            throw new RuntimeException(sprintf('%s file is not readable', $this->path));
+        }
+
+        $lines = file($this->path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
+        foreach ($lines as $line) {
+
+            if (strpos(trim($line), '#') === 0) {
+                continue;
+            }
+
+            list($name, $value) = explode('=', $line, 2);
+            $name = trim($name);
+            $value = $this->processValue($value);
+
+            if (!array_key_exists($name, $_SERVER) && !array_key_exists($name, $_ENV)) {
+                putenv(sprintf('%s=%s', $name, $value));
+                $_ENV[$name] = $value;
+                $_SERVER[$name] = $value;
+            }
+        }
+    }
+
+    private function setProcessors(?array $processors = null): void
+    {
+        /**
+         * Fill with default processors
+         */
+        if ($processors === null) {
+            $this->processors = [
+                NullProcessor::class,
+                BooleanProcessor::class,
+                NumberProcessor::class,
+                QuotedProcessor::class
+            ];
+            return;
+        }
+
+        foreach ($processors as $processor) {
+            if (is_subclass_of($processor, AbstractProcessor::class)) {
+                $this->processors[] = $processor;
+            }
+        }
+    }
+
+
+    /**
+     * Process the value with the configured processors
+     *
+     * @param string $value The value to process
+     * @return mixed
+     */
+    private function processValue(string $value)
+    {
+        /**
+         * First trim spaces and quotes if configured
+         */
+        $trimmedValue = trim($value);
+
+        foreach ($this->processors as $processor) {
+            /** @var AbstractProcessor $processorInstance */
+            $processorInstance = new $processor($trimmedValue);
+
+            if ($processorInstance->canBeProcessed()) {
+                return $processorInstance->execute();
+            }
+        }
+
+        /**
+         * Does not match any processor options, return as is
+         */
+        return $trimmedValue;
+    }
+}

+ 16 - 0
src/Processor/AbstractProcessor.php

@@ -0,0 +1,16 @@
+<?php
+namespace Michel\Env\Processor;
+
+abstract class AbstractProcessor implements IProcessor
+{
+    /**
+     * The value to process
+     * @var string
+     */
+    protected string $value;
+
+    public function __construct(string $value)
+    {
+        $this->value = $value;
+    }
+}

+ 22 - 0
src/Processor/BooleanProcessor.php

@@ -0,0 +1,22 @@
+<?php
+namespace Michel\Env\Processor;
+
+class BooleanProcessor extends AbstractProcessor
+{
+    public function canBeProcessed(): bool
+    {
+        $loweredValue = strtolower($this->value);
+
+        return in_array($loweredValue, ['true', 'false'], true);
+    }
+
+    /**
+     * Executes the PHP function and returns a boolean value indicating whether the value is 'true' in lowercase.
+     *
+     * @return bool The result of the comparison between the lowercase value of $this->value and 'true'.
+     */
+    public function execute()
+    {
+        return strtolower($this->value) === 'true';
+    }
+}

+ 16 - 0
src/Processor/IProcessor.php

@@ -0,0 +1,16 @@
+<?php
+namespace Michel\Env\Processor;
+
+interface IProcessor
+{
+    public function __construct(string $value);
+
+    /**
+     * Check if the entity can be processed.
+     *
+     * @return bool
+     */
+    public function canBeProcessed(): bool;
+
+    public function execute();
+}

+ 15 - 0
src/Processor/NullProcessor.php

@@ -0,0 +1,15 @@
+<?php
+namespace Michel\Env\Processor;
+
+class NullProcessor extends AbstractProcessor
+{
+    public function canBeProcessed(): bool
+    {
+        return in_array($this->value, ['null', 'NULL'], true);
+    }
+
+    public function execute()
+    {
+        return null;
+    }
+}

+ 30 - 0
src/Processor/NumberProcessor.php

@@ -0,0 +1,30 @@
+<?php
+namespace Michel\Env\Processor;
+
+class NumberProcessor extends AbstractProcessor
+{
+    public function canBeProcessed(): bool
+    {
+        return is_numeric($this->value);
+    }
+
+    /**
+     * Executes the function and returns an integer or float value based on the input.
+     *
+     * This function uses the `filter_var` function with the `FILTER_VALIDATE_INT` filter to check if the input value can be
+     * converted to an integer. If the conversion is successful, the integer value is returned. Otherwise, the input value is
+     * cast to a float and returned.
+     *
+     * @return int|float The converted integer or float value.
+     */
+    public function execute()
+    {
+        $int = filter_var($this->value, FILTER_VALIDATE_INT);
+
+        if ($int !== false) {
+            return $int;
+        }
+
+        return (float) $this->value;
+    }
+}

+ 36 - 0
src/Processor/QuotedProcessor.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace Michel\Env\Processor;
+
+class QuotedProcessor extends AbstractProcessor
+{
+    public function canBeProcessed(): bool
+    {
+        $wrappedByDoubleQuotes = $this->isWrappedByChar($this->value, '"');
+
+        if ($wrappedByDoubleQuotes) {
+            return true;
+        }
+
+        return $this->isWrappedByChar($this->value, '\'');
+    }
+    /**
+     * Executes the function and returns a substring of the value property of the current object,
+     * excluding the first and last characters.
+     *
+     * @return string The substring of the value property.
+     */
+    public function execute()
+    {
+        /**
+         * Since this function is used for the quote removal
+         * we don't need mb_substr
+         */
+        return substr($this->value, 1, -1);
+    }
+
+    private function isWrappedByChar(string $value, string $char) : bool
+    {
+        return !empty($value) && $value[0] === $char && $value[-1] === $char;
+    }
+}

+ 268 - 0
tests/DotenvTest.php

@@ -0,0 +1,268 @@
+<?php
+
+namespace Test\Michel\Env;
+
+use Michel\Env\DotEnv;
+use Michel\Env\Processor\BooleanProcessor;
+use Michel\Env\Processor\NullProcessor;
+use Michel\Env\Processor\NumberProcessor;
+use Michel\Env\Processor\QuotedProcessor;
+use Michel\UniTester\TestCase;
+
+class DotenvTest extends TestCase
+{
+    protected function setUp(): void
+    {
+        // TODO: Implement setUp() method.
+    }
+
+    protected function tearDown(): void
+    {
+        $this->clearAllEnv();
+    }
+
+    private function clearAllEnv(): void
+    {
+        foreach ($_ENV as $key => $value) {
+            unset($_ENV[$key]);
+        }
+        foreach ($_SERVER as $key => $value) {
+            unset($_SERVER[$key]);
+        }
+    }
+
+    protected function execute(): void
+    {
+        $this->clearAllEnv();
+        $this->testLoad();
+        $this->clearAllEnv();
+
+        $this->testFileNotExist();
+        $this->clearAllEnv();
+
+        $this->testIncompatibleProcessors();
+        $this->clearAllEnv();
+
+        $this->testProcessBoolean();
+        $this->clearAllEnv();
+
+        $this->testDontProcessBoolean();
+        $this->clearAllEnv();
+
+        $this->testProcessQuotes();
+        $this->clearAllEnv();
+
+        $this->testDontProcessQuotes();
+        $this->clearAllEnv();
+
+        $this->testProcessNumbers();
+    }
+
+    private function env(string $file)
+    {
+        return __DIR__ . DIRECTORY_SEPARATOR . 'env' . DIRECTORY_SEPARATOR . $file;
+    }
+
+    /**
+     * @runInSeparateProcess
+     */
+    public function testLoad() {
+
+        (new DotEnv($this->env('.env.default')))->load();
+
+        $this->assertEquals('dev', getenv('APP_ENV'));
+        $this->assertEquals('mysql:host=localhost;dbname=test;', getenv('DATABASE_DNS'));
+        $this->assertEquals('root', getenv('DATABASE_USER'));
+        $this->assertEquals('password', getenv('DATABASE_PASSWORD'));
+        $this->assertFalse(getenv('GOOGLE_API'));
+        $this->assertFalse(getenv('GOOGLE_MANAGER_KEY'));
+        $this->assertEquals(true, getenv('BOOLEAN_LITERAL'));
+        $this->assertEquals('true', getenv('BOOLEAN_QUOTED'));
+
+        $this->assertEquals('dev', $_ENV['APP_ENV']);
+        $this->assertEquals('password', $_ENV['DATABASE_PASSWORD']);
+        $this->assertFalse(array_key_exists('GOOGLE_API', $_ENV));
+        $this->assertFalse(array_key_exists('GOOGLE_MANAGER_KEY', $_ENV));
+
+        $this->assertEquals(true, $_ENV['BOOLEAN_LITERAL']);
+        $this->assertEquals('true', $_ENV['BOOLEAN_QUOTED']);
+
+        $this->assertEquals('mysql:host=localhost;dbname=test;', $_SERVER['DATABASE_DNS']);
+        $this->assertEquals('root', $_SERVER['DATABASE_USER']);
+        $this->assertEquals('password', $_SERVER['DATABASE_PASSWORD']);
+        $this->assertFalse(array_key_exists('GOOGLE_API', $_SERVER));
+        $this->assertFalse(array_key_exists('GOOGLE_MANAGER_KEY', $_SERVER));
+        $this->assertEquals(true, $_SERVER['BOOLEAN_LITERAL']);
+        $this->assertEquals('true', $_SERVER['BOOLEAN_QUOTED']);
+
+        $this->assertEquals('🪄', $_SERVER['EMOJI']);
+
+        $this->assertTrue(is_int($_SERVER['ZERO_LITERAL']));
+        $this->assertEquals(0, $_SERVER['ZERO_LITERAL']);
+
+        $this->assertTrue(is_string($_SERVER['ZERO_QUOTED']));
+        $this->assertEquals('0', $_SERVER['ZERO_QUOTED']);
+
+        $this->assertTrue(is_int($_SERVER['NUMBER_LITERAL']));
+        $this->assertEquals(1111, $_SERVER['NUMBER_LITERAL']);
+
+        $this->assertTrue(is_string($_SERVER['NUMBER_QUOTED']));
+        $this->assertEquals('1111', $_SERVER['NUMBER_QUOTED']);
+
+        $this->assertNull($_SERVER['NULL_LITERAL']);
+        $this->assertTrue(array_key_exists('NULL_LITERAL', $_SERVER));
+
+        $this->assertEquals('null', $_SERVER['NULL_QUOTED']);
+
+        $this->assertEquals('', $_SERVER['EMPTY_LITERAL']);
+        $this->assertEquals('', $_SERVER['EMPTY_QUOTED']);
+    }
+
+    public function testFileNotExist() {
+        $this->expectException(\InvalidArgumentException::class, function () {
+            (new DotEnv($this->env('.env.not-exists')))->load();
+        });
+    }
+
+    /**
+     * @runInSeparateProcess
+     */
+    public function testIncompatibleProcessors() {
+        $this->assertProcessors(
+            [],
+            []
+        );
+
+        $this->assertProcessors(
+            null,
+            [
+                NullProcessor::class,
+                BooleanProcessor::class,
+                NumberProcessor::class,
+                QuotedProcessor::class
+            ]
+        );
+
+        $this->assertProcessors(
+            [null],
+            []
+        );
+
+        $this->assertProcessors(
+            [new \stdClass()],
+            []
+        );
+
+        $this->assertProcessors(
+            [QuotedProcessor::class, null],
+            [QuotedProcessor::class]
+        );
+    }
+
+    /**
+     * @runInSeparateProcess
+     */
+    private function assertProcessors(array $processorsToUse = null, array $expectedProcessors = [])
+    {
+        $dotEnv = new DotEnv($this->env('.env.default'), $processorsToUse);
+        $dotEnv->load();
+
+        $this->assertEquals(
+            $expectedProcessors,
+            $this->getProtectedProperty($dotEnv)
+        );
+    }
+
+    private function getProtectedProperty(object $object) {
+        $reflection = new \ReflectionClass($object);
+        $reflectionProperty = $reflection->getProperty('processors');
+        $reflectionProperty->setAccessible(true);
+
+        return $reflectionProperty->getValue($object);
+    }
+
+    /**
+     * @runInSeparateProcess
+     */
+    public function testProcessBoolean()
+    {
+        (new DotEnv($this->env('.env.boolean'), [
+            BooleanProcessor::class
+        ]))->load();
+
+        $this->assertEquals(false, $_ENV['FALSE1']);
+        $this->assertEquals(false, $_ENV['FALSE2']);
+        $this->assertEquals(false, $_ENV['FALSE3']);
+        $this->assertEquals("'false'", $_ENV['FALSE4']); // Since we don't have the QuotedProcessor::class this will be the result
+        $this->assertEquals('0', $_ENV['FALSE5']);
+
+        $this->assertEquals(true, $_ENV['TRUE1']);
+        $this->assertEquals(true, $_ENV['TRUE2']);
+        $this->assertEquals(true, $_ENV['TRUE3']);
+        $this->assertEquals("'true'", $_ENV['TRUE4']); // Since we don't have the QuotedProcessor::class this will be the result
+        $this->assertEquals('1', $_ENV['TRUE5']);
+    }
+
+    /**
+     * @runInSeparateProcess
+     */
+    public function testDontProcessBoolean()
+    {
+        (new DotEnv($this->env('.env.boolean'), []))->load();
+
+        $this->assertEquals('false', $_ENV['FALSE1']);
+        $this->assertEquals('true', $_ENV['TRUE1']);
+    }
+
+    /**
+     * @runInSeparateProcess
+     */
+    public function testProcessQuotes()
+    {
+        (new DotEnv($this->env('.env.quotes'), [
+            QuotedProcessor::class
+        ]))->load();
+
+        $this->assertEquals('q1', $_ENV['QUOTED1']);
+        $this->assertEquals('q2', $_ENV['QUOTED2']);
+        $this->assertEquals('"q3"', $_ENV['QUOTED3']);
+        $this->assertEquals('This is a "sample" value', $_ENV['QUOTED4']);
+        $this->assertEquals('\"This is a "sample" value\"', $_ENV['QUOTED5']);
+        $this->assertEquals('"q6', $_ENV['QUOTED6']);
+        $this->assertEquals('q7"', $_ENV['QUOTED7']);
+    }
+
+    /**
+     * @runInSeparateProcess
+     */
+    public function testDontProcessQuotes()
+    {
+        (new DotEnv($this->env('.env.quotes'), []))->load();
+
+        $this->assertEquals('"q1"', $_ENV['QUOTED1']);
+        $this->assertEquals('\'q2\'', $_ENV['QUOTED2']);
+        $this->assertEquals('""q3""', $_ENV['QUOTED3']);
+        $this->assertEquals('"This is a "sample" value"', $_ENV['QUOTED4']);
+        $this->assertEquals('\"This is a "sample" value\"', $_ENV['QUOTED5']);
+        $this->assertEquals('"q6', $_ENV['QUOTED6']);
+        $this->assertEquals('q7"', $_ENV['QUOTED7']);
+        $this->assertEquals('0', $_ENV['ZERO_LITERAL']);
+        $this->assertEquals('"0"', $_ENV['ZERO_QUOTED']);
+    }
+
+    /**
+     * @runInSeparateProcess
+     */
+    public function testProcessNumbers()
+    {
+        (new DotEnv($this->env('.env.number'), [
+            NumberProcessor::class
+        ]))->load();
+
+        $this->assertEquals(0, $_ENV['NUMBER1']);
+        $this->assertTrue(is_numeric($_ENV['NUMBER1']));
+        $this->assertEquals(0.0001, $_ENV['NUMBER2']);
+        $this->assertEquals(123456789, $_ENV['NUMBER3']);
+        $this->assertEquals(123456789.0, $_ENV['NUMBER4']);
+    }
+}

+ 10 - 0
tests/env/.env.boolean

@@ -0,0 +1,10 @@
+FALSE1=false
+FALSE2= false
+FALSE3=FALSE
+FALSE4='false'
+FALSE5=0
+TRUE1=true
+TRUE2= true
+TRUE3=TRUE
+TRUE4='true'
+TRUE5=1

+ 18 - 0
tests/env/.env.default

@@ -0,0 +1,18 @@
+# In all environments, the following files are loaded if they exist,
+APP_ENV=dev
+DATABASE_DNS=mysql:host=localhost;dbname=test;
+DATABASE_USER=root
+ DATABASE_PASSWORD = password
+#GOOGLE_API=DJfa7czhKaJ0Pig6j9XpSjT6NpXZUZwK
+   # GOOGLE_MANAGER_KEY=P7RkBUQHIkPUEPy3yCTT4gGYa2DjRth8
+BOOLEAN_LITERAL=true
+BOOLEAN_QUOTED="true"
+ZERO_LITERAL=0
+ZERO_QUOTED="0"
+NUMBER_LITERAL=1111
+NUMBER_QUOTED="1111"
+NULL_LITERAL=null
+NULL_QUOTED="null"
+EMPTY_LITERAL=
+EMPTY_QUOTED=""
+EMOJI=🪄

+ 4 - 0
tests/env/.env.number

@@ -0,0 +1,4 @@
+NUMBER1=0
+NUMBER2=0.0001
+NUMBER3=123456789
+NUMBER4=123456789.0

+ 9 - 0
tests/env/.env.quotes

@@ -0,0 +1,9 @@
+QUOTED1="q1"
+QUOTED2='q2'
+QUOTED3=""q3""
+QUOTED4="This is a "sample" value"
+QUOTED5=\"This is a "sample" value\"
+QUOTED6="q6
+QUOTED7=q7"
+ZERO_LITERAL=0
+ZERO_QUOTED="0"