Skip to content

Commit 9479eef

Browse files
Add Env::getCast optional casting, docs, and tests
1 parent b205ce6 commit 9479eef

File tree

4 files changed

+114
-20
lines changed

4 files changed

+114
-20
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
All notable changes to this package will be documented in this file.
44

5+
## [2.3.0] - 2026-01-03
6+
7+
### Added
8+
9+
- `Env::getCast()` for optional casting of common string values.
10+
11+
### Tests
12+
13+
- Added coverage for `Env::getCast()` casting behavior.
14+
515
## [2.2.0] - 2025-12-28
616

717
### Added

README.md

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ Env::load(__DIR__ . '/.env');
4848
echo Env::get('APP_NAME'); // "MyApp"
4949
```
5050

51+
Optional casting:
52+
53+
```php
54+
Env::getCast('FEATURE_ENABLED', false, true); // true
55+
Env::getCast('OPTIONAL_VALUE', 'default', true); // null
56+
```
57+
5158
Common options:
5259

5360
```php
@@ -104,14 +111,14 @@ $loader->loadFiles([__DIR__ . '/.env', __DIR__ . '/.env.local']);
104111

105112
Notes:
106113

107-
- When encoding is null, no conversion is performed, even if mbstring is available.
108-
- UTF-8 BOM is stripped automatically.
114+
- When encoding is null, no conversion is performed, even if mbstring is available.
115+
- UTF-8 BOM is stripped automatically.
109116

110117
## Features
111118

112119
- Loading `.env` files into `$_ENV`, `$_SERVER`, and via `putenv()`.
113120
- Does not override variables already present in `$_ENV`, `$_SERVER`, or `getenv()`.
114-
- Values are returned as strings; no automatic casting is applied.
121+
- Values are returned as strings by default; `Env::getCast()` enables optional casting.
115122
- Quoted strings with escaped quotes and `\n`, `\r`, `\t`, `\v`, `\f` in double quotes (single quotes are literal).
116123
- Inline comments start with `#` in unquoted values (use quotes to keep a literal `#`).
117124
- Unquoted values cannot contain whitespace.
@@ -128,31 +135,31 @@ Notes:
128135

129136
## Parse Formats
130137

131-
| Method | Return format |
132-
| --- | --- |
133-
| `Env::parse()` | Raw entries: `[[name, value, vars], ...]` |
134-
| `Env::parseToArray()` | Associative map: `['NAME' => 'value', ...]` |
135-
| `EnvParser::parseStringRaw()` | Raw entries: `[[name, value, vars], ...]` |
138+
| Method | Return format |
139+
| ----------------------------- | ------------------------------------------- |
140+
| `Env::parse()` | Raw entries: `[[name, value, vars], ...]` |
141+
| `Env::parseToArray()` | Associative map: `['NAME' => 'value', ...]` |
142+
| `EnvParser::parseStringRaw()` | Raw entries: `[[name, value, vars], ...]` |
136143

137144
Strict mode throws `InvalidFileException` on duplicate names for `parse()` and `parseToArray()`.
138145

139146
## Load Files Order
140147

141-
- `loadFiles()` processes paths in the order provided.
142-
- Glob patterns are expanded using `glob()` with `GLOB_BRACE` by default.
143-
- Glob matches are sorted to keep load order stable.
144-
- Use the optional `globFlags` parameter to change `glob()` behavior.
145-
- If `strictResolve` is true, unresolved `${VAR}` or `$VAR` references in any loaded file will throw `InvalidFileException`.
146-
- If `shortCircuit` is true, the first successfully loaded file stops further processing.
147-
- If `shortCircuit` is false, missing files raise `InvalidPathException`.
148+
- `loadFiles()` processes paths in the order provided.
149+
- Glob patterns are expanded using `glob()` with `GLOB_BRACE` by default.
150+
- Glob matches are sorted to keep load order stable.
151+
- Use the optional `globFlags` parameter to change `glob()` behavior.
152+
- If `strictResolve` is true, unresolved `${VAR}` or `$VAR` references in any loaded file will throw `InvalidFileException`.
153+
- If `shortCircuit` is true, the first successfully loaded file stops further processing.
154+
- If `shortCircuit` is false, missing files raise `InvalidPathException`.
148155

149156
## Exceptions
150157

151-
| Scenario | Exception |
152-
| --- | --- |
153-
| Invalid syntax, invalid name, bad escapes | `Codemonster\Env\Exception\InvalidFileException` |
154-
| Invalid or unsupported encoding | `Codemonster\Env\Exception\InvalidEncodingException` |
155-
| Missing or unreadable file | `Codemonster\Env\Exception\InvalidPathException` |
158+
| Scenario | Exception |
159+
| ----------------------------------------- | ---------------------------------------------------- |
160+
| Invalid syntax, invalid name, bad escapes | `Codemonster\Env\Exception\InvalidFileException` |
161+
| Invalid or unsupported encoding | `Codemonster\Env\Exception\InvalidEncodingException` |
162+
| Missing or unreadable file | `Codemonster\Env\Exception\InvalidPathException` |
156163

157164
## Loader Interface
158165

src/Env.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,25 @@ public static function get(string $key, mixed $default = null): mixed
116116
return $value === false ? $default : $value;
117117
}
118118

119+
public static function getCast(string $key, mixed $default = null, bool $cast = false): mixed
120+
{
121+
if (!$cast) {
122+
return self::get($key, $default);
123+
}
124+
125+
if (array_key_exists($key, $_ENV)) {
126+
return self::castValue($_ENV[$key]);
127+
}
128+
129+
if (array_key_exists($key, $_SERVER)) {
130+
return self::castValue($_SERVER[$key]);
131+
}
132+
133+
$value = getenv($key);
134+
135+
return $value === false ? $default : self::castValue($value);
136+
}
137+
119138
public static function parse(
120139
string $content,
121140
?string $encoding = null,
@@ -179,4 +198,38 @@ protected static function getDefaultLoaderInternal(): LoaderInterface
179198

180199
return self::$defaultLoader;
181200
}
201+
202+
private static function castValue(mixed $value): mixed
203+
{
204+
if (!is_string($value)) {
205+
return $value;
206+
}
207+
208+
$trimmed = trim($value);
209+
$lower = strtolower($trimmed);
210+
211+
return match ($lower) {
212+
'true', '(true)' => true,
213+
'false', '(false)' => false,
214+
'empty', '(empty)' => '',
215+
'null', '(null)' => null,
216+
default => self::stripQuotes($trimmed) ?? $value,
217+
};
218+
}
219+
220+
private static function stripQuotes(string $value): ?string
221+
{
222+
if (strlen($value) < 2) {
223+
return null;
224+
}
225+
226+
$first = $value[0];
227+
$last = $value[strlen($value) - 1];
228+
229+
if (($first === '"' || $first === "'") && $last === $first) {
230+
return substr($value, 1, -1);
231+
}
232+
233+
return null;
234+
}
182235
}

tests/EnvTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,30 @@ public function testEnvLoadsEmptyStringKeyword(): void
7070
$this->assertSame('empty', Env::get('EMPTY_VALUE'));
7171
}
7272

73+
public function testEnvGetCastCastsCommonValues(): void
74+
{
75+
$this->assertTrue(Env::getCast('FEATURE_ENABLED', null, true));
76+
$this->assertFalse(Env::getCast('FEATURE_DISABLED', null, true));
77+
$this->assertNull(Env::getCast('OPTIONAL_VALUE', 'fallback', true));
78+
$this->assertSame('', Env::getCast('EMPTY_VALUE', 'fallback', true));
79+
$this->assertSame('MyApp', Env::getCast('APP_NAME', null, true));
80+
$this->assertSame('default', Env::getCast('NOT_DEFINED', 'default', true));
81+
}
82+
83+
public function testEnvGetCastStripsWrappingQuotes(): void
84+
{
85+
unset($_ENV['CAST_QUOTED'], $_SERVER['CAST_QUOTED']);
86+
putenv('CAST_QUOTED');
87+
putenv('CAST_QUOTED="hello"');
88+
89+
try {
90+
$this->assertSame('hello', Env::getCast('CAST_QUOTED', null, true));
91+
} finally {
92+
unset($_ENV['CAST_QUOTED'], $_SERVER['CAST_QUOTED']);
93+
putenv('CAST_QUOTED');
94+
}
95+
}
96+
7397
public function testEnvRemovesQuotes(): void
7498
{
7599
$this->assertSame('http://localhost:3000', Env::get('SSR_URL'));

0 commit comments

Comments
 (0)