Skip to content

[12.x] Typed getters for Env helper #55658

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions src/Illuminate/Support/Env.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Closure;
use Dotenv\Repository\Adapter\PutenvAdapter;
use Dotenv\Repository\RepositoryBuilder;
use InvalidArgumentException;
use PhpOption\Option;
use RuntimeException;

Expand Down Expand Up @@ -114,6 +115,90 @@ public static function getOrFail($key)
return self::getOption($key)->getOrThrow(new RuntimeException("Environment variable [$key] has no value."));
}

/**
* Get the specified string environment value.
*
* @param string $key
* @param (Closure():(string|null))|string|null $default
* @return string
*
* @throws InvalidArgumentException
*/
public static function string(string $key, $default = null): string
{
$value = Env::get($key, $default);

return check_type($value, 'string', $key, 'Environment');
}

/**
* Get the specified integer environment value.
*
* @param string $key
* @param (Closure():(int|null))|int|null $default
* @return int
*
* @throws InvalidArgumentException
*/
public static function integer(string $key, $default = null): int
{
$value = Env::get($key, $default);

return check_type($value, 'int', $key, 'Environment');
}

/**
* Get the specified float environment value.
*
* @param string $key
* @param (Closure():(float|null))|float|null $default
* @return float
*
* @throws InvalidArgumentException
*/
public static function float(string $key, $default = null): float
{
$value = Env::get($key, $default);

return check_type($value, 'float', $key, 'Environment');
}

/**
* Get the specified boolean environment value.
*
* @param string $key
* @param (Closure():(bool|null))|bool|null $default
* @return bool
*
* @throws InvalidArgumentException
*/
public static function boolean(string $key, $default = null): bool
{
$value = Env::get($key, $default);

return check_type($value, 'bool', $key, 'Environment');
}

/**
* Get the specified array environment value.
*
* @param string $key
* @param (Closure():(array|null))|array|null $default
* @return array
*
* @throws InvalidArgumentException
*/
public static function array(string $key, $default = null): array
{
$value = Env::get($key, $default);

return match (true) {
$value === null => [],
is_string($value) => array_map('trim', explode(',', $value)),
default => check_type($value, 'array', $key, 'Environment'),
};
}

/**
* Get the possible option for this environment variable.
*
Expand Down
39 changes: 39 additions & 0 deletions src/Illuminate/Support/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Illuminate\Support\Defer\DeferredCallback;
use Illuminate\Support\Defer\DeferredCallbackCollection;
use InvalidArgumentException;
use Symfony\Component\Process\PhpExecutableFinder;

if (! function_exists('Illuminate\Support\defer')) {
Expand Down Expand Up @@ -51,3 +52,41 @@ function artisan_binary()
return defined('ARTISAN_BINARY') ? ARTISAN_BINARY : 'artisan';
}
}

if (! function_exists('Illuminate\Support\check_type')) {
/**
* Validate that a given value matches the expected type.
*
* @param mixed $value
* @param 'string'|'int'|'integer'|'long'|'bool'|'boolean'|'float'|'double'|'real'|'array' $type
* @param string $key
* @param string $group
* @return mixed
*
* @throws InvalidArgumentException If the value does not match the expected type.
*/
function check_type(mixed $value, string $type, string $key, string $group = 'variable'): mixed
{
$isValid = match ($type) {
'string' => is_string($value),
'int', 'integer', 'long' => filter_var($value, FILTER_VALIDATE_INT) !== false,
'bool', 'boolean' => is_bool($value),
'float', 'double', 'real' => filter_var($value, FILTER_VALIDATE_FLOAT) !== false,
'array' => is_array($value),
default => throw new InvalidArgumentException('Type "'.$type.'" is not supported. Use one of: string, int, integer, long, bool, boolean, float, double, real, array')
};

throw_unless(
$isValid,
InvalidArgumentException::class,
sprintf('%s value for key [%s] must be a %s, %s given.',
$group,
$key,
$type,
is_object($value) ? get_class($value) : gettype($value)
),
);

return $value;
}
}
68 changes: 68 additions & 0 deletions tests/Support/SupportHelpersTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Illuminate\Support\Stringable;
use Illuminate\Tests\Support\Fixtures\IntBackedEnum;
use Illuminate\Tests\Support\Fixtures\StringBackedEnum;
use InvalidArgumentException;
use IteratorAggregate;
use LogicException;
use Mockery as m;
Expand Down Expand Up @@ -1212,6 +1213,73 @@ public function testEnvEscapedString()
$this->assertSame('x"null"x', env('foo'));
}

public function testEnvString()
{
$_ENV['string'] = 'foo bar';
$this->assertSame('foo bar', Env::string('string'));
$this->assertSame('default', Env::string('missing_key', 'default'));
}

public function testEnvInteger()
{
$_SERVER['integer_key'] = '12345';

$this->assertSame(12345, Env::integer('integer_key'));
$this->assertSame(999, Env::integer('missing_key', 999));

$_SERVER['not_integer_key'] = 'foo';

$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessageMatches('#^Environment value for key \[not_integer_key\] must be a (int|integer|long), (.*) given.#');

Env::integer('not_integer_key');
}

public function testEnvFloat()
{
$_SERVER['float_key'] = '12.34';

$this->assertSame(12.34, Env::float('float_key'));
$this->assertSame(0.5, Env::float('missing_key', 0.5));

$_SERVER['not_float_key'] = 'foo';

$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessageMatches('#^Environment value for key \[not_float_key\] must be a (float|double|real), (.*) given.#');

Env::float('not_float_key');
}

public function testEnvBoolean()
{
$_SERVER['bool_true_key'] = 'true';
$_SERVER['bool_false_key'] = 'false';

$this->assertTrue(Env::boolean('bool_true_key'));
$this->assertFalse(Env::boolean('bool_false_key'));
$this->assertTrue(Env::boolean('missing_key', true));

$_SERVER['not_bool_key'] = 'yes';

$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessageMatches('#^Environment value for key \[not_bool_key\] must be a (bool|boolean), (.*) given.#');

Env::boolean('not_bool_key');
}

public function testEnvArray()
{
$_SERVER['array_key'] = 'one,two,three';

$this->assertSame(['one', 'two', 'three'], Env::array('array_key'));
$this->assertSame(['default'], Env::array('missing_key', ['default']));

$_SERVER['not_array_key'] = 'foo';

// still returns array with one element
$this->assertSame(['foo'], Env::array('not_array_key'));
}

public function testGetFromSERVERFirst()
{
$_ENV['foo'] = 'From $_ENV';
Expand Down