diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..22aac70
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,14 @@
+/tests export-ignore
+/vendor export-ignore
+
+/LICENSE export-ignore
+/Makefile export-ignore
+/README.md export-ignore
+/phpmd.xml export-ignore
+/phpunit.xml export-ignore
+/phpstan.neon.dist export-ignore
+/infection.json.dist export-ignore
+
+/.github export-ignore
+/.gitignore export-ignore
+/.gitattributes export-ignore
diff --git a/.github/workflows/auto-assign.yml b/.github/workflows/auto-assign.yml
new file mode 100644
index 0000000..6a9bba4
--- /dev/null
+++ b/.github/workflows/auto-assign.yml
@@ -0,0 +1,22 @@
+name: Auto assign issues
+
+on:
+ issues:
+ types:
+ - opened
+
+jobs:
+ run:
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ pull-requests: write
+ steps:
+ - name: Assign issues
+ uses: gustavofreze/auto-assign@1.0.0
+ with:
+ assignees: '${{ secrets.ASSIGNEES }}'
+ github_token: '${{ secrets.GITHUB_TOKEN }}'
+ allow_self_assign: 'true'
+ allow_no_assignees: 'true'
+ assignment_options: 'ISSUE'
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..96e4a08
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,50 @@
+name: CI
+
+on:
+ push:
+ pull_request:
+
+permissions:
+ contents: read
+
+env:
+ PHP_VERSION: '8.3'
+
+jobs:
+ auto-review:
+ name: Auto review
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Configure PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ env.PHP_VERSION }}
+
+ - name: Install dependencies
+ run: composer update --no-progress --optimize-autoloader
+
+ - name: Run review
+ run: composer review
+
+ tests:
+ name: Tests
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Use PHP ${{ env.PHP_VERSION }}
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ env.PHP_VERSION }}
+
+ - name: Install dependencies
+ run: composer update --no-progress --optimize-autoloader
+
+ - name: Run tests
+ run: composer tests
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3333ef2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+.idea
+
+/vendor/
+/report
+*.lock
+.phpunit.*
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..b68ede6
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,25 @@
+DOCKER_RUN = docker run --rm -it --net=host -v ${PWD}:/app -w /app gustavofreze/php:8.3
+
+.PHONY: configure test test-file test-no-coverage review show-reports clean
+
+configure:
+ @${DOCKER_RUN} composer update --optimize-autoloader
+
+test:
+ @${DOCKER_RUN} composer tests
+
+test-file:
+ @${DOCKER_RUN} composer tests-file-no-coverage ${FILE}
+
+test-no-coverage:
+ @${DOCKER_RUN} composer tests-no-coverage
+
+review:
+ @${DOCKER_RUN} composer review
+
+show-reports:
+ @sensible-browser report/coverage/coverage-html/index.html report/coverage/mutation-report.html
+
+clean:
+ @sudo chown -R ${USER}:${USER} ${PWD}
+ @rm -rf report vendor .phpunit.cache .lock
diff --git a/README.md b/README.md
index fa9cd5b..7b68d7e 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,89 @@
-# environment-variable
-Provides a simple and flexible solution for managing environment variables, with easy access, type conversions, and validation handling.
+# Environment variable
+
+[](LICENSE)
+
+* [Overview](#overview)
+* [Installation](#installation)
+* [How to use](#how-to-use)
+* [License](#license)
+* [Contributing](#contributing)
+
+
+
+## Overview
+
+Provides a simple and flexible solution for managing environment variables, with easy access, type conversions, and
+validation handling.
+
+
+
+## Installation
+
+```bash
+composer require tiny-blocks/environment-variable
+```
+
+
+
+## How to use
+
+### Creating an environment variable
+
+To create and work with environment variables, use the `from` method to get an instance of the environment variable.
+
+```php
+EnvironmentVariable::from(name: 'MY_VAR');
+```
+
+### Conversions
+
+Once you have an instance of the environment variable, you can convert its value into various types.
+
+#### Convert to string
+
+To convert the environment variable to a string.
+
+```php
+$environmentVariable = EnvironmentVariable::from(name: 'MY_VAR');
+$environmentVariable->toString();
+```
+
+#### Convert to integer
+
+To convert the environment variable to an integer.
+
+```php
+$environmentVariable = EnvironmentVariable::from(name: 'MY_VAR');
+$environmentVariable->toInteger();
+```
+
+#### Convert to boolean
+
+To convert the environment variable to a boolean.
+
+```php
+$environmentVariable = EnvironmentVariable::from(name: 'MY_VAR');
+$environmentVariable->toBoolean();
+```
+
+### Check if the environment variable has a value
+
+Checks if the environment variable has a value. Values like `false`, `0`, and `-1` are valid and non-empty.
+
+```php
+$environmentVariable = EnvironmentVariable::from(name: 'MY_VAR');
+$environmentVariable->hasValue();
+```
+
+
+
+## License
+
+Environment variable is licensed under [MIT](LICENSE).
+
+
+
+## Contributing
+
+Please follow the [contributing guidelines](https://github.com/tiny-blocks/tiny-blocks/blob/main/CONTRIBUTING.md) to
+contribute to the project.
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..41a104e
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,72 @@
+{
+ "name": "tiny-blocks/environment-variable",
+ "type": "library",
+ "license": "MIT",
+ "homepage": "https://github.com/tiny-blocks/environment-variable",
+ "description": "Provides a simple and flexible solution for managing environment variables, with easy access, type conversions, and validation handling.",
+ "prefer-stable": true,
+ "minimum-stability": "stable",
+ "keywords": [
+ "psr",
+ "env",
+ "tiny-blocks",
+ "environment-variable"
+ ],
+ "authors": [
+ {
+ "name": "Gustavo Freze de Araujo Santos",
+ "homepage": "https://github.com/gustavofreze"
+ }
+ ],
+ "support": {
+ "issues": "https://github.com/tiny-blocks/environment-variable/issues",
+ "source": "https://github.com/tiny-blocks/environment-variable"
+ },
+ "autoload": {
+ "psr-4": {
+ "TinyBlocks\\EnvironmentVariable\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "TinyBlocks\\EnvironmentVariable\\": "tests/"
+ }
+ },
+ "require": {
+ "php": "^8.3"
+ },
+ "require-dev": {
+ "phpmd/phpmd": "^2.15",
+ "phpunit/phpunit": "^11",
+ "phpstan/phpstan": "^1",
+ "dg/bypass-finals": "^1.8",
+ "infection/infection": "^0",
+ "squizlabs/php_codesniffer": "^3"
+ },
+ "config": {
+ "sort-packages": true,
+ "allow-plugins": {
+ "infection/extension-installer": true
+ }
+ },
+ "scripts": {
+ "test": "phpunit --configuration phpunit.xml tests",
+ "phpcs": "phpcs --standard=PSR12 --extensions=php ./src",
+ "phpmd": "phpmd ./src text phpmd.xml --suffixes php --ignore-violations-on-exit",
+ "phpstan": "phpstan analyse -c phpstan.neon.dist --quiet --no-progress",
+ "mutation-test": "infection --only-covered --threads=max --logger-html=report/coverage/mutation-report.html --coverage=report/coverage",
+ "test-no-coverage": "phpunit --configuration phpunit.xml --no-coverage tests",
+ "review": [
+ "@phpcs",
+ "@phpmd",
+ "@phpstan"
+ ],
+ "tests": [
+ "@test",
+ "@mutation-test"
+ ],
+ "tests-no-coverage": [
+ "@test-no-coverage"
+ ]
+ }
+}
diff --git a/infection.json.dist b/infection.json.dist
new file mode 100644
index 0000000..45c49fc
--- /dev/null
+++ b/infection.json.dist
@@ -0,0 +1,23 @@
+{
+ "logs": {
+ "text": "report/infection/logs/infection-text.log",
+ "summary": "report/infection/logs/infection-summary.log"
+ },
+ "tmpDir": "report/infection/",
+ "minMsi": 100,
+ "timeout": 30,
+ "source": {
+ "directories": [
+ "src"
+ ]
+ },
+ "phpUnit": {
+ "configDir": "",
+ "customPath": "./vendor/bin/phpunit"
+ },
+ "mutators": {
+ "@default": true
+ },
+ "minCoveredMsi": 100,
+ "testFramework": "phpunit"
+}
diff --git a/phpmd.xml b/phpmd.xml
new file mode 100644
index 0000000..bb59312
--- /dev/null
+++ b/phpmd.xml
@@ -0,0 +1,59 @@
+
+
+ PHPMD Custom rules
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
new file mode 100644
index 0000000..937f06e
--- /dev/null
+++ b/phpstan.neon.dist
@@ -0,0 +1,6 @@
+parameters:
+ paths:
+ - src
+ level: 9
+ tmpDir: report/phpstan
+ reportUnmatchedIgnoredErrors: false
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 0000000..59058a4
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+ src
+
+
+
+
+
+ tests
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Environment.php b/src/Environment.php
new file mode 100644
index 0000000..8c3c7e5
--- /dev/null
+++ b/src/Environment.php
@@ -0,0 +1,53 @@
+value))) {
+ '', 'null' => false,
+ default => true
+ };
+ }
+
+ public function toString(): string
+ {
+ return $this->value;
+ }
+
+ public function toInteger(): int
+ {
+ return is_numeric($this->value)
+ ? (int)$this->value
+ : throw InvalidEnvironmentValue::fromIntegerConversion(value: $this->value, variable: $this->variable);
+ }
+
+ public function toBoolean(): bool
+ {
+ $filteredValue = filter_var($this->value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
+
+ return $filteredValue !== null
+ ? $filteredValue
+ : throw InvalidEnvironmentValue::fromBooleanConversion(value: $this->value, variable: $this->variable);
+ }
+}
diff --git a/src/Internal/Exceptions/EnvironmentVariableMissing.php b/src/Internal/Exceptions/EnvironmentVariableMissing.php
new file mode 100644
index 0000000..7dd4afb
--- /dev/null
+++ b/src/Internal/Exceptions/EnvironmentVariableMissing.php
@@ -0,0 +1,17 @@
+ is missing.';
+
+ parent::__construct(message: sprintf($template, $variable));
+ }
+}
diff --git a/src/Internal/Exceptions/InvalidEnvironmentValue.php b/src/Internal/Exceptions/InvalidEnvironmentValue.php
new file mode 100644
index 0000000..64d1d18
--- /dev/null
+++ b/src/Internal/Exceptions/InvalidEnvironmentValue.php
@@ -0,0 +1,27 @@
+ for environment variable <%s> is invalid for conversion to <%s>.';
+
+ parent::__construct(message: sprintf($template, $value, $variable, $conversionType));
+ }
+
+ public static function fromIntegerConversion(string $value, string $variable): InvalidEnvironmentValue
+ {
+ return new InvalidEnvironmentValue(value: $value, variable: $variable, conversionType: 'integer');
+ }
+
+ public static function fromBooleanConversion(string $value, string $variable): InvalidEnvironmentValue
+ {
+ return new InvalidEnvironmentValue(value: $value, variable: $variable, conversionType: 'boolean');
+ }
+}
diff --git a/tests/EnvironmentVariableTest.php b/tests/EnvironmentVariableTest.php
new file mode 100644
index 0000000..36d751c
--- /dev/null
+++ b/tests/EnvironmentVariableTest.php
@@ -0,0 +1,246 @@
+toString();
+
+ /** @Then the result should match the expected string value */
+ self::assertEquals($expected, $actual);
+ }
+
+ #[DataProvider('integerConversionDataProvider')]
+ public function testConvertToInteger(mixed $value, string $variable, int $expected): void
+ {
+ /** @Given the environment variable is set with the given integer value */
+ putenv(sprintf('%s=%s', $variable, $value));
+
+ /** @When I try to convert the environment variable value to an integer */
+ $actual = EnvironmentVariable::from(name: $variable)->toInteger();
+
+ /** @Then the result should match the expected integer value */
+ self::assertEquals($expected, $actual);
+ }
+
+ #[DataProvider('booleanConversionDataProvider')]
+ public function testConvertToBoolean(mixed $value, string $variable, bool $expected): void
+ {
+ /** @Given the environment variable is set with the given boolean value */
+ putenv(sprintf('%s=%s', $variable, $value));
+
+ /** @When I try to convert the environment variable value to a boolean */
+ $actual = EnvironmentVariable::from(name: $variable)->toBoolean();
+
+ /** @Then the result should match the expected boolean value */
+ self::assertEquals($expected, $actual);
+ }
+
+ #[DataProvider('hasValueDataProvider')]
+ public function testHasValue(mixed $value, string $variable): void
+ {
+ /** @Given the environment variable is set with the given value */
+ putenv(sprintf('%s=%s', $variable, $value));
+
+ /** @When I check if the environment variable has a value */
+ $actual = EnvironmentVariable::from(name: $variable)->hasValue();
+
+ /** @Then the result should be true (has value) */
+ self::assertTrue($actual);
+ }
+
+ #[DataProvider('hasNoValueDataProvider')]
+ public function testHasNoValue(mixed $value, string $variable): void
+ {
+ /** @Given the environment variable is set with the given value */
+ putenv(sprintf('%s=%s', $variable, $value));
+
+ /** @When I check if the environment variable has a value */
+ $actual = EnvironmentVariable::from(name: $variable)->hasValue();
+
+ /** @Then the result should be false (no value) */
+ self::assertFalse($actual);
+ }
+
+ public function testExceptionWhenVariableIsMissing(): void
+ {
+ /** @Given that the environment variable 'NON_EXISTENT' does not exist */
+ $variable = 'NON_EXISTENT';
+
+ /** @Then an error indicating the variable is missing should occur */
+ $this->expectException(EnvironmentVariableMissing::class);
+ $this->expectExceptionMessage('Environment variable is missing.');
+
+ /** @When I try to get the value of the missing environment variable */
+ EnvironmentVariable::from(name: $variable);
+ }
+
+ public function testExceptionWhenInvalidIntegerConversion(): void
+ {
+ /** @Given that the environment variable 'INVALID_INT' has an invalid integer value */
+ putenv(sprintf('%s=%s', 'INVALID_INT', 'invalid-value'));
+
+ /** @Then an error indicating the value cannot be converted to an integer should occur */
+ $this->expectException(InvalidEnvironmentValue::class);
+ $this->expectExceptionMessage(
+ 'The value for environment variable is invalid for conversion to .'
+ );
+
+ /** @When I try to convert the invalid value to an integer */
+ EnvironmentVariable::from(name: 'INVALID_INT')->toInteger();
+ }
+
+ public function testExceptionWhenInvalidBooleanConversion(): void
+ {
+ /** @Given that the environment variable 'INVALID_BOOL' has an invalid boolean value */
+ putenv(sprintf('%s=%s', 'INVALID_BOOL', 'invalid-value'));
+
+ /** @Then an error indicating the value cannot be converted to a boolean should occur */
+ $this->expectException(InvalidEnvironmentValue::class);
+ $this->expectExceptionMessage(
+ 'The value for environment variable is invalid for conversion to .'
+ );
+
+ /** @When I try to convert the invalid value to a boolean */
+ EnvironmentVariable::from(name: 'INVALID_BOOL')->toBoolean();
+ }
+
+ public static function stringConversionDataProvider(): array
+ {
+ return [
+ 'String value' => [
+ 'value' => 'Hello, world!',
+ 'variable' => 'VALID_STRING',
+ 'expected' => 'Hello, world!'
+ ],
+ 'Numeric string' => [
+ 'value' => '123',
+ 'variable' => 'NUMERIC_STRING',
+ 'expected' => '123'
+ ],
+ 'Boolean true value' => [
+ 'value' => true,
+ 'variable' => 'BOOLEAN_TRUE',
+ 'expected' => '1'
+ ],
+ 'Boolean false value' => [
+ 'value' => false,
+ 'variable' => 'BOOLEAN_FALSE',
+ 'expected' => ''
+ ]
+ ];
+ }
+
+ public static function integerConversionDataProvider(): array
+ {
+ return [
+ 'Float value' => [
+ 'value' => '99.99',
+ 'variable' => 'FLOAT_VALUE',
+ 'expected' => 99
+ ],
+ 'Integer value' => [
+ 'value' => '123',
+ 'variable' => 'VALID_INT',
+ 'expected' => 123
+ ],
+ 'Numeric string' => [
+ 'value' => '42',
+ 'variable' => 'NUMERIC_STRING',
+ 'expected' => 42
+ ]
+ ];
+ }
+
+ public static function booleanConversionDataProvider(): array
+ {
+ return [
+ 'Numeric value one as string' => [
+ 'value' => '1',
+ 'variable' => 'NUMERIC_TRUE',
+ 'expected' => true
+ ],
+ 'Numeric value zero as string' => [
+ 'value' => '0',
+ 'variable' => 'NUMERIC_FALSE',
+ 'expected' => false
+ ],
+ 'Boolean true value as string' => [
+ 'value' => 'true',
+ 'variable' => 'BOOLEAN_TRUE',
+ 'expected' => true
+ ],
+ 'Boolean false value as string' => [
+ 'value' => 'false',
+ 'variable' => 'BOOLEAN_FALSE',
+ 'expected' => false
+ ]
+ ];
+ }
+
+ public static function hasValueDataProvider(): array
+ {
+ return [
+ 'String value' => [
+ 'value' => 'Hello, World!',
+ 'variable' => 'STRING_VALUE'
+ ],
+ 'Integer value 0' => [
+ 'value' => '0',
+ 'variable' => 'INTEGER_ZERO'
+ ],
+ 'Boolean value true' => [
+ 'value' => 'true',
+ 'variable' => 'BOOLEAN_TRUE'
+ ],
+ 'Boolean value false' => [
+ 'value' => 'false',
+ 'variable' => 'BOOLEAN_FALSE'
+ ],
+ 'Integer value positive' => [
+ 'value' => '123',
+ 'variable' => 'INTEGER_POSITIVE'
+ ],
+ 'Integer value negative' => [
+ 'value' => '-1',
+ 'variable' => 'INTEGER_NEGATIVE'
+ ]
+ ];
+ }
+
+ public static function hasNoValueDataProvider(): array
+ {
+ return [
+ 'Null value' => [
+ 'value' => null,
+ 'variable' => 'NULL_VALUE'
+ ],
+ 'Empty string' => [
+ 'value' => '',
+ 'variable' => 'EMPTY_STRING'
+ ],
+ 'String null value' => [
+ 'value' => 'NULL',
+ 'variable' => 'NULL_VALUE'
+ ],
+ 'String with only spaces' => [
+ 'value' => ' ',
+ 'variable' => 'STRING_WITH_SPACES'
+ ]
+ ];
+ }
+}