diff --git a/README.md b/README.md index fe9f077..c7bcc1d 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,13 @@ BASE_DIR="/var/webroot/project-root" CACHE_DIR="${BASE_DIR}/cache" TMP_DIR="${BASE_DIR}/tmp" ``` +### URL and Email Variables + +You can validate common types like URLs and email addresses: + +```php +$dotenv->required('APP_URL')->isUrl(); +$dotenv->ifPresent('SUPPORT_EMAIL')->isEmail(); ### Immutability and Repository Customization diff --git a/src/Validator.php b/src/Validator.php index d5580c7..31bf7d7 100644 --- a/src/Validator.php +++ b/src/Validator.php @@ -8,6 +8,7 @@ use Dotenv\Repository\RepositoryInterface; use Dotenv\Util\Regex; use Dotenv\Util\Str; +use InvalidArgumentException; class Validator { @@ -110,6 +111,44 @@ static function (string $value) { 'is not a boolean' ); } + /** + * Assert that each specified variable is a valid URL. + * + * @param int $flags Optional FILTER_VALIDATE_URL flags (e.g., FILTER_FLAG_PATH_REQUIRED) + * @throws ValidationException + * @return self + */ +public function isUrl(int $flags = 0): self +{ + return $this->assertNullable( + static function (string $value) use ($flags): bool { + if (filter_var($value, FILTER_VALIDATE_URL) === false) { + return false; + } + if ($flags !== 0 && filter_var($value, FILTER_VALIDATE_URL, $flags) === false) { + return false; + } + return true; + }, + 'is not a valid URL' + ); +} + +/** + * Assert that each specified variable is a valid email address. + * + * @throws ValidationException + * @return self + */ +public function isEmail(): self +{ + return $this->assertNullable( + static function (string $value): bool { + return filter_var($value, FILTER_VALIDATE_EMAIL) !== false; + }, + 'is not a valid email' + ); +} /** * Assert that each variable is amongst the given choices. diff --git a/tests/Dotenv/ValidatorTest.php b/tests/Dotenv/ValidatorTest.php index dd5bcf9..5c5c4bf 100644 --- a/tests/Dotenv/ValidatorTest.php +++ b/tests/Dotenv/ValidatorTest.php @@ -16,6 +16,78 @@ final class ValidatorTest extends TestCase * @var string */ private static $folder; + private string $dir; + + protected function setUp(): void + { + $this->dir = sys_get_temp_dir() . '/phpdotenv-tests-' . uniqid(); + mkdir($this->dir); + } + + protected function tearDown(): void + { + array_map('unlink', glob($this->dir . '/*') ?: []); + @rmdir($this->dir); + } + + private function writeEnv(string $contents): void + { + file_put_contents($this->dir . '/.env', $contents); + } + + public function testIsUrlPasses(): void + { + $this->writeEnv("APP_URL=https://example.com/path?x=1\n"); + $dotenv = Dotenv::createImmutable($this->dir); + $dotenv->load(); + + $dotenv->required('APP_URL')->isUrl(); // should not throw + + $this->assertTrue(true); + } + + public function testIsUrlFails(): void + { + $this->writeEnv("APP_URL=not-a-url\n"); + $dotenv = Dotenv::createImmutable($this->dir); + $dotenv->load(); + + $this->expectException(RuntimeException::class); + $dotenv->required('APP_URL')->isUrl(); + } + + public function testIsEmailPasses(): void + { + $this->writeEnv("SUPPORT_EMAIL=helpdesk@example.org\n"); + $dotenv = Dotenv::createImmutable($this->dir); + $dotenv->load(); + + $dotenv->required('SUPPORT_EMAIL')->isEmail(); // should not throw + + $this->assertTrue(true); + } + + public function testIsEmailFails(): void + { + $this->writeEnv("SUPPORT_EMAIL=foo@@bar\n"); + $dotenv = Dotenv::createImmutable($this->dir); + $dotenv->load(); + + $this->expectException(RuntimeException::class); + $dotenv->required('SUPPORT_EMAIL')->isEmail(); + } + + public function testIfPresentWithIsUrl(): void + { + $this->writeEnv(""); // not set + $dotenv = Dotenv::createImmutable($this->dir); + $dotenv->load(); + + // Should not throw since var is absent and ifPresent() defers validation + $dotenv->ifPresent('OPTIONAL_WEBHOOK')->isUrl(); + + $this->assertTrue(true); + } /** * @beforeClass