From 54a23cc7fa2c2e217160652a73f2dea9ffacedb7 Mon Sep 17 00:00:00 2001
From: Alejandro Celaya <alejandrocelaya@gmail.com>
Date: Sat, 23 Apr 2022 12:44:17 +0200
Subject: [PATCH] Converted EnvVars to enum

---
 config/autoload/delete_short_urls.global.php |   2 +-
 config/autoload/entity-manager.global.php    |  14 +-
 config/autoload/geolite2.global.php          |   2 +-
 config/autoload/locks.global.php             |   2 +-
 config/autoload/mercure.global.php           |   6 +-
 config/autoload/qr-codes.global.php          |  10 +-
 config/autoload/rabbit.global.php            |  12 +-
 config/autoload/redirects.global.php         |  10 +-
 config/autoload/redis.global.php             |   4 +-
 config/autoload/router.global.php            |   2 +-
 config/autoload/swoole.global.php            |   6 +-
 config/autoload/tracking.global.php          |  16 +-
 config/autoload/url-shortener.global.php     |  10 +-
 config/autoload/webhooks.global.php          |   4 +-
 config/container.php                         |   2 +-
 module/Core/src/Config/EnvVars.php           | 193 ++++++-------------
 module/Core/src/Entity/Visit.php             |   1 +
 module/Core/src/Model/ShortUrlsParams.php    |   2 +
 module/Core/test/Config/EnvVarsTest.php      | 107 ++--------
 module/Rest/src/ApiKey/Role.php              |   1 +
 20 files changed, 123 insertions(+), 283 deletions(-)

diff --git a/config/autoload/delete_short_urls.global.php b/config/autoload/delete_short_urls.global.php
index 3d562f78..2d203ea1 100644
--- a/config/autoload/delete_short_urls.global.php
+++ b/config/autoload/delete_short_urls.global.php
@@ -7,7 +7,7 @@ namespace Shlinkio\Shlink;
 use Shlinkio\Shlink\Core\Config\EnvVars;
 
 return (static function (): array {
-    $threshold = EnvVars::DELETE_SHORT_URL_THRESHOLD()->loadFromEnv();
+    $threshold = EnvVars::DELETE_SHORT_URL_THRESHOLD->loadFromEnv();
 
     return [
 
diff --git a/config/autoload/entity-manager.global.php b/config/autoload/entity-manager.global.php
index d98d37dc..5a75ca6b 100644
--- a/config/autoload/entity-manager.global.php
+++ b/config/autoload/entity-manager.global.php
@@ -8,7 +8,7 @@ use Shlinkio\Shlink\Core\Config\EnvVars;
 use function Functional\contains;
 
 return (static function (): array {
-    $driver = EnvVars::DB_DRIVER()->loadFromEnv();
+    $driver = EnvVars::DB_DRIVER->loadFromEnv();
     $isMysqlCompatible = contains(['maria', 'mysql'], $driver);
 
     $resolveDriver = static fn () => match ($driver) {
@@ -35,12 +35,12 @@ return (static function (): array {
         ],
         default => [
             'driver' => $resolveDriver(),
-            'dbname' => EnvVars::DB_NAME()->loadFromEnv('shlink'),
-            'user' => EnvVars::DB_USER()->loadFromEnv(),
-            'password' => EnvVars::DB_PASSWORD()->loadFromEnv(),
-            'host' => EnvVars::DB_HOST()->loadFromEnv(EnvVars::DB_UNIX_SOCKET()->loadFromEnv()),
-            'port' => EnvVars::DB_PORT()->loadFromEnv($resolveDefaultPort()),
-            'unix_socket' => $isMysqlCompatible ? EnvVars::DB_UNIX_SOCKET()->loadFromEnv() : null,
+            'dbname' => EnvVars::DB_NAME->loadFromEnv('shlink'),
+            'user' => EnvVars::DB_USER->loadFromEnv(),
+            'password' => EnvVars::DB_PASSWORD->loadFromEnv(),
+            'host' => EnvVars::DB_HOST->loadFromEnv(EnvVars::DB_UNIX_SOCKET->loadFromEnv()),
+            'port' => EnvVars::DB_PORT->loadFromEnv($resolveDefaultPort()),
+            'unix_socket' => $isMysqlCompatible ? EnvVars::DB_UNIX_SOCKET->loadFromEnv() : null,
             'charset' => $resolveCharset(),
         ],
     };
diff --git a/config/autoload/geolite2.global.php b/config/autoload/geolite2.global.php
index cf1f57fc..b31cfc6d 100644
--- a/config/autoload/geolite2.global.php
+++ b/config/autoload/geolite2.global.php
@@ -9,7 +9,7 @@ return [
     'geolite2' => [
         'db_location' => __DIR__ . '/../../data/GeoLite2-City.mmdb',
         'temp_dir' => __DIR__ . '/../../data',
-        'license_key' => EnvVars::GEOLITE_LICENSE_KEY()->loadFromEnv(),
+        'license_key' => EnvVars::GEOLITE_LICENSE_KEY->loadFromEnv(),
     ],
 
 ];
diff --git a/config/autoload/locks.global.php b/config/autoload/locks.global.php
index bdbdb8e5..9b014496 100644
--- a/config/autoload/locks.global.php
+++ b/config/autoload/locks.global.php
@@ -24,7 +24,7 @@ return [
             LOCAL_LOCK_FACTORY => ConfigAbstractFactory::class,
         ],
         'aliases' => [
-            'lock_store' => EnvVars::REDIS_SERVERS()->existsInEnv() ? 'redis_lock_store' : 'local_lock_store',
+            'lock_store' => EnvVars::REDIS_SERVERS->existsInEnv() ? 'redis_lock_store' : 'local_lock_store',
 
             'redis_lock_store' => Lock\Store\RedisStore::class,
             'local_lock_store' => Lock\Store\FlockStore::class,
diff --git a/config/autoload/mercure.global.php b/config/autoload/mercure.global.php
index ba261369..67143919 100644
--- a/config/autoload/mercure.global.php
+++ b/config/autoload/mercure.global.php
@@ -9,14 +9,14 @@ use Symfony\Component\Mercure\Hub;
 use Symfony\Component\Mercure\HubInterface;
 
 return (static function (): array {
-    $publicUrl = EnvVars::MERCURE_PUBLIC_HUB_URL()->loadFromEnv();
+    $publicUrl = EnvVars::MERCURE_PUBLIC_HUB_URL->loadFromEnv();
 
     return [
 
         'mercure' => [
             'public_hub_url' => $publicUrl,
-            'internal_hub_url' => EnvVars::MERCURE_INTERNAL_HUB_URL()->loadFromEnv($publicUrl),
-            'jwt_secret' => EnvVars::MERCURE_JWT_SECRET()->loadFromEnv(),
+            'internal_hub_url' => EnvVars::MERCURE_INTERNAL_HUB_URL->loadFromEnv($publicUrl),
+            'jwt_secret' => EnvVars::MERCURE_JWT_SECRET->loadFromEnv(),
             'jwt_issuer' => 'Shlink',
         ],
 
diff --git a/config/autoload/qr-codes.global.php b/config/autoload/qr-codes.global.php
index d72198af..dc4f5f9e 100644
--- a/config/autoload/qr-codes.global.php
+++ b/config/autoload/qr-codes.global.php
@@ -13,13 +13,13 @@ use const Shlinkio\Shlink\DEFAULT_QR_CODE_SIZE;
 return [
 
     'qr_codes' => [
-        'size' => (int) EnvVars::DEFAULT_QR_CODE_SIZE()->loadFromEnv(DEFAULT_QR_CODE_SIZE),
-        'margin' => (int) EnvVars::DEFAULT_QR_CODE_MARGIN()->loadFromEnv(DEFAULT_QR_CODE_MARGIN),
-        'format' => EnvVars::DEFAULT_QR_CODE_FORMAT()->loadFromEnv(DEFAULT_QR_CODE_FORMAT),
-        'error_correction' => EnvVars::DEFAULT_QR_CODE_ERROR_CORRECTION()->loadFromEnv(
+        'size' => (int) EnvVars::DEFAULT_QR_CODE_SIZE->loadFromEnv(DEFAULT_QR_CODE_SIZE),
+        'margin' => (int) EnvVars::DEFAULT_QR_CODE_MARGIN->loadFromEnv(DEFAULT_QR_CODE_MARGIN),
+        'format' => EnvVars::DEFAULT_QR_CODE_FORMAT->loadFromEnv(DEFAULT_QR_CODE_FORMAT),
+        'error_correction' => EnvVars::DEFAULT_QR_CODE_ERROR_CORRECTION->loadFromEnv(
             DEFAULT_QR_CODE_ERROR_CORRECTION,
         ),
-        'round_block_size' => (bool) EnvVars::DEFAULT_QR_CODE_ROUND_BLOCK_SIZE()->loadFromEnv(
+        'round_block_size' => (bool) EnvVars::DEFAULT_QR_CODE_ROUND_BLOCK_SIZE->loadFromEnv(
             DEFAULT_QR_CODE_ROUND_BLOCK_SIZE,
         ),
     ],
diff --git a/config/autoload/rabbit.global.php b/config/autoload/rabbit.global.php
index faa5f569..a9764c8c 100644
--- a/config/autoload/rabbit.global.php
+++ b/config/autoload/rabbit.global.php
@@ -10,12 +10,12 @@ use Shlinkio\Shlink\Core\Config\EnvVars;
 return [
 
     'rabbitmq' => [
-        'enabled' => (bool) EnvVars::RABBITMQ_ENABLED()->loadFromEnv(false),
-        'host' => EnvVars::RABBITMQ_HOST()->loadFromEnv(),
-        'port' => (int) EnvVars::RABBITMQ_PORT()->loadFromEnv('5672'),
-        'user' => EnvVars::RABBITMQ_USER()->loadFromEnv(),
-        'password' => EnvVars::RABBITMQ_PASSWORD()->loadFromEnv(),
-        'vhost' => EnvVars::RABBITMQ_VHOST()->loadFromEnv('/'),
+        'enabled' => (bool) EnvVars::RABBITMQ_ENABLED->loadFromEnv(false),
+        'host' => EnvVars::RABBITMQ_HOST->loadFromEnv(),
+        'port' => (int) EnvVars::RABBITMQ_PORT->loadFromEnv('5672'),
+        'user' => EnvVars::RABBITMQ_USER->loadFromEnv(),
+        'password' => EnvVars::RABBITMQ_PASSWORD->loadFromEnv(),
+        'vhost' => EnvVars::RABBITMQ_VHOST->loadFromEnv('/'),
     ],
 
     'dependencies' => [
diff --git a/config/autoload/redirects.global.php b/config/autoload/redirects.global.php
index 08439b2a..426bb2ac 100644
--- a/config/autoload/redirects.global.php
+++ b/config/autoload/redirects.global.php
@@ -10,14 +10,14 @@ use const Shlinkio\Shlink\DEFAULT_REDIRECT_STATUS_CODE;
 return [
 
     'not_found_redirects' => [
-        'invalid_short_url' => EnvVars::DEFAULT_INVALID_SHORT_URL_REDIRECT()->loadFromEnv(),
-        'regular_404' => EnvVars::DEFAULT_REGULAR_404_REDIRECT()->loadFromEnv(),
-        'base_url' => EnvVars::DEFAULT_BASE_URL_REDIRECT()->loadFromEnv(),
+        'invalid_short_url' => EnvVars::DEFAULT_INVALID_SHORT_URL_REDIRECT->loadFromEnv(),
+        'regular_404' => EnvVars::DEFAULT_REGULAR_404_REDIRECT->loadFromEnv(),
+        'base_url' => EnvVars::DEFAULT_BASE_URL_REDIRECT->loadFromEnv(),
     ],
 
     'redirects' => [
-        'redirect_status_code' => (int) EnvVars::REDIRECT_STATUS_CODE()->loadFromEnv(DEFAULT_REDIRECT_STATUS_CODE),
-        'redirect_cache_lifetime' => (int) EnvVars::REDIRECT_CACHE_LIFETIME()->loadFromEnv(
+        'redirect_status_code' => (int) EnvVars::REDIRECT_STATUS_CODE->loadFromEnv(DEFAULT_REDIRECT_STATUS_CODE),
+        'redirect_cache_lifetime' => (int) EnvVars::REDIRECT_CACHE_LIFETIME->loadFromEnv(
             DEFAULT_REDIRECT_CACHE_LIFETIME,
         ),
     ],
diff --git a/config/autoload/redis.global.php b/config/autoload/redis.global.php
index f87d77f3..0133d1b1 100644
--- a/config/autoload/redis.global.php
+++ b/config/autoload/redis.global.php
@@ -5,7 +5,7 @@ declare(strict_types=1);
 use Shlinkio\Shlink\Core\Config\EnvVars;
 
 return (static function (): array {
-    $redisServers = EnvVars::REDIS_SERVERS()->loadFromEnv();
+    $redisServers = EnvVars::REDIS_SERVERS->loadFromEnv();
 
     return match ($redisServers) {
         null => [],
@@ -13,7 +13,7 @@ return (static function (): array {
             'cache' => [
                 'redis' => [
                     'servers' => $redisServers,
-                    'sentinel_service' => EnvVars::REDIS_SENTINEL_SERVICE()->loadFromEnv(),
+                    'sentinel_service' => EnvVars::REDIS_SENTINEL_SERVICE->loadFromEnv(),
                 ],
             ],
         ],
diff --git a/config/autoload/router.global.php b/config/autoload/router.global.php
index fd1f9525..8b5e856e 100644
--- a/config/autoload/router.global.php
+++ b/config/autoload/router.global.php
@@ -8,7 +8,7 @@ use Shlinkio\Shlink\Core\Config\EnvVars;
 return [
 
     'router' => [
-        'base_path' => EnvVars::BASE_PATH()->loadFromEnv(''),
+        'base_path' => EnvVars::BASE_PATH->loadFromEnv(''),
 
         'fastroute' => [
             FastRouteRouter::CONFIG_CACHE_ENABLED => true,
diff --git a/config/autoload/swoole.global.php b/config/autoload/swoole.global.php
index 987c967e..36cba24f 100644
--- a/config/autoload/swoole.global.php
+++ b/config/autoload/swoole.global.php
@@ -7,7 +7,7 @@ use Shlinkio\Shlink\Core\Config\EnvVars;
 use const Shlinkio\Shlink\MIN_TASK_WORKERS;
 
 return (static function (): array {
-    $taskWorkers = (int) EnvVars::TASK_WORKER_NUM()->loadFromEnv(16);
+    $taskWorkers = (int) EnvVars::TASK_WORKER_NUM->loadFromEnv(16);
 
     return [
 
@@ -17,11 +17,11 @@ return (static function (): array {
 
             'swoole-http-server' => [
                 'host' => '0.0.0.0',
-                'port' => (int) EnvVars::PORT()->loadFromEnv(8080),
+                'port' => (int) EnvVars::PORT->loadFromEnv(8080),
                 'process-name' => 'shlink',
 
                 'options' => [
-                    'worker_num' => (int) EnvVars::WEB_WORKER_NUM()->loadFromEnv(16),
+                    'worker_num' => (int) EnvVars::WEB_WORKER_NUM->loadFromEnv(16),
                     'task_worker_num' => max($taskWorkers, MIN_TASK_WORKERS),
                 ],
             ],
diff --git a/config/autoload/tracking.global.php b/config/autoload/tracking.global.php
index b2596830..0637301a 100644
--- a/config/autoload/tracking.global.php
+++ b/config/autoload/tracking.global.php
@@ -9,28 +9,28 @@ return [
     'tracking' => [
         // Tells if IP addresses should be anonymized before persisting, to fulfil data protection regulations
         // This applies only if IP address tracking is enabled
-        'anonymize_remote_addr' => (bool) EnvVars::ANONYMIZE_REMOTE_ADDR()->loadFromEnv(true),
+        'anonymize_remote_addr' => (bool) EnvVars::ANONYMIZE_REMOTE_ADDR->loadFromEnv(true),
 
         // Tells if visits to not-found URLs should be tracked. The disable_tracking option takes precedence
-        'track_orphan_visits' => (bool) EnvVars::TRACK_ORPHAN_VISITS()->loadFromEnv(true),
+        'track_orphan_visits' => (bool) EnvVars::TRACK_ORPHAN_VISITS->loadFromEnv(true),
 
         // A query param that, if provided, will disable tracking of one particular visit. Always takes precedence
-        'disable_track_param' => EnvVars::DISABLE_TRACK_PARAM()->loadFromEnv(),
+        'disable_track_param' => EnvVars::DISABLE_TRACK_PARAM->loadFromEnv(),
 
         // If true, visits will not be tracked at all
-        'disable_tracking' => (bool) EnvVars::DISABLE_TRACKING()->loadFromEnv(false),
+        'disable_tracking' => (bool) EnvVars::DISABLE_TRACKING->loadFromEnv(false),
 
         // If true, visits will be tracked, but neither the IP address, nor the location will be resolved
-        'disable_ip_tracking' => (bool) EnvVars::DISABLE_IP_TRACKING()->loadFromEnv(false),
+        'disable_ip_tracking' => (bool) EnvVars::DISABLE_IP_TRACKING->loadFromEnv(false),
 
         // If true, the referrer will not be tracked
-        'disable_referrer_tracking' => (bool) EnvVars::DISABLE_REFERRER_TRACKING()->loadFromEnv(false),
+        'disable_referrer_tracking' => (bool) EnvVars::DISABLE_REFERRER_TRACKING->loadFromEnv(false),
 
         // If true, the user agent will not be tracked
-        'disable_ua_tracking' => (bool) EnvVars::DISABLE_UA_TRACKING()->loadFromEnv(false),
+        'disable_ua_tracking' => (bool) EnvVars::DISABLE_UA_TRACKING->loadFromEnv(false),
 
         // A list of IP addresses, patterns or CIDR blocks from which tracking is disabled by default
-        'disable_tracking_from' => EnvVars::DISABLE_TRACKING_FROM()->loadFromEnv(),
+        'disable_tracking_from' => EnvVars::DISABLE_TRACKING_FROM->loadFromEnv(),
     ],
 
 ];
diff --git a/config/autoload/url-shortener.global.php b/config/autoload/url-shortener.global.php
index 58c12f05..bdd257d5 100644
--- a/config/autoload/url-shortener.global.php
+++ b/config/autoload/url-shortener.global.php
@@ -9,7 +9,7 @@ use const Shlinkio\Shlink\MIN_SHORT_CODES_LENGTH;
 
 return (static function (): array {
     $shortCodesLength = max(
-        (int) EnvVars::DEFAULT_SHORT_CODES_LENGTH()->loadFromEnv(DEFAULT_SHORT_CODES_LENGTH),
+        (int) EnvVars::DEFAULT_SHORT_CODES_LENGTH->loadFromEnv(DEFAULT_SHORT_CODES_LENGTH),
         MIN_SHORT_CODES_LENGTH,
     );
 
@@ -17,12 +17,12 @@ return (static function (): array {
 
         'url_shortener' => [
             'domain' => [ // TODO Refactor this structure to url_shortener.schema and url_shortener.default_domain
-                'schema' => ((bool) EnvVars::IS_HTTPS_ENABLED()->loadFromEnv(true)) ? 'https' : 'http',
-                'hostname' => EnvVars::DEFAULT_DOMAIN()->loadFromEnv(''),
+                'schema' => ((bool) EnvVars::IS_HTTPS_ENABLED->loadFromEnv(true)) ? 'https' : 'http',
+                'hostname' => EnvVars::DEFAULT_DOMAIN->loadFromEnv(''),
             ],
             'default_short_codes_length' => $shortCodesLength,
-            'auto_resolve_titles' => (bool) EnvVars::AUTO_RESOLVE_TITLES()->loadFromEnv(false),
-            'append_extra_path' => (bool) EnvVars::REDIRECT_APPEND_EXTRA_PATH()->loadFromEnv(false),
+            'auto_resolve_titles' => (bool) EnvVars::AUTO_RESOLVE_TITLES->loadFromEnv(false),
+            'append_extra_path' => (bool) EnvVars::REDIRECT_APPEND_EXTRA_PATH->loadFromEnv(false),
         ],
 
     ];
diff --git a/config/autoload/webhooks.global.php b/config/autoload/webhooks.global.php
index 5de7c53b..e72c4904 100644
--- a/config/autoload/webhooks.global.php
+++ b/config/autoload/webhooks.global.php
@@ -6,14 +6,14 @@ use Shlinkio\Shlink\Core\Config\EnvVars;
 
 // Deprecated. Webhooks are no longer supported. To be removed in Shlink 4.0.0
 return (static function (): array {
-    $webhooks = EnvVars::VISITS_WEBHOOKS()->loadFromEnv();
+    $webhooks = EnvVars::VISITS_WEBHOOKS->loadFromEnv();
 
     return [
 
         'visits_webhooks' => [
             'webhooks' => $webhooks === null ? [] : explode(',', $webhooks),
             'notify_orphan_visits_to_webhooks' =>
-                (bool) EnvVars::NOTIFY_ORPHAN_VISITS_TO_WEBHOOKS()->loadFromEnv(false),
+                (bool) EnvVars::NOTIFY_ORPHAN_VISITS_TO_WEBHOOKS->loadFromEnv(false),
         ],
 
     ];
diff --git a/config/container.php b/config/container.php
index 074502cd..6e95e84d 100644
--- a/config/container.php
+++ b/config/container.php
@@ -13,7 +13,7 @@ chdir(dirname(__DIR__));
 require 'vendor/autoload.php';
 
 // This is one of the first files loaded. Configure the timezone here
-date_default_timezone_set(EnvVars::TIMEZONE()->loadFromEnv(date_default_timezone_get()));
+date_default_timezone_set(EnvVars::TIMEZONE->loadFromEnv(date_default_timezone_get()));
 
 // This class alias tricks the ConfigAbstractFactory to return Lock\Factory instances even with a different service name
 // It needs to be placed here as individual config files will not be loaded once config is cached
diff --git a/module/Core/src/Config/EnvVars.php b/module/Core/src/Config/EnvVars.php
index 112b7599..e26e3097 100644
--- a/module/Core/src/Config/EnvVars.php
+++ b/module/Core/src/Config/EnvVars.php
@@ -2,155 +2,70 @@
 
 declare(strict_types=1);
 
+// phpcs:disable
+// TODO Enable coding style checks again once code sniffer 3.7 is released https://github.com/squizlabs/PHP_CodeSniffer/issues/3474
 namespace Shlinkio\Shlink\Core\Config;
 
-use ReflectionClass;
-use ReflectionClassConstant;
-use Shlinkio\Shlink\Core\Exception\InvalidArgumentException;
-
-use function array_values;
-use function Functional\contains;
 use function Shlinkio\Shlink\Config\env;
 
-// TODO Convert to enum after dropping PHP 8.0 support
-
-/**
- * @method static EnvVars DELETE_SHORT_URL_THRESHOLD()
- * @method static EnvVars DB_DRIVER()
- * @method static EnvVars DB_NAME()
- * @method static EnvVars DB_USER()
- * @method static EnvVars DB_PASSWORD()
- * @method static EnvVars DB_HOST()
- * @method static EnvVars DB_UNIX_SOCKET()
- * @method static EnvVars DB_PORT()
- * @method static EnvVars GEOLITE_LICENSE_KEY()
- * @method static EnvVars REDIS_SERVERS()
- * @method static EnvVars REDIS_SENTINEL_SERVICE()
- * @method static EnvVars MERCURE_PUBLIC_HUB_URL()
- * @method static EnvVars MERCURE_INTERNAL_HUB_URL()
- * @method static EnvVars MERCURE_JWT_SECRET()
- * @method static EnvVars DEFAULT_QR_CODE_SIZE()
- * @method static EnvVars DEFAULT_QR_CODE_MARGIN()
- * @method static EnvVars DEFAULT_QR_CODE_FORMAT()
- * @method static EnvVars DEFAULT_QR_CODE_ERROR_CORRECTION()
- * @method static EnvVars DEFAULT_QR_CODE_ROUND_BLOCK_SIZE()
- * @method static EnvVars RABBITMQ_ENABLED()
- * @method static EnvVars RABBITMQ_HOST()
- * @method static EnvVars RABBITMQ_PORT()
- * @method static EnvVars RABBITMQ_USER()
- * @method static EnvVars RABBITMQ_PASSWORD()
- * @method static EnvVars RABBITMQ_VHOST()
- * @method static EnvVars DEFAULT_INVALID_SHORT_URL_REDIRECT()
- * @method static EnvVars DEFAULT_REGULAR_404_REDIRECT()
- * @method static EnvVars DEFAULT_BASE_URL_REDIRECT()
- * @method static EnvVars REDIRECT_STATUS_CODE()
- * @method static EnvVars REDIRECT_CACHE_LIFETIME()
- * @method static EnvVars BASE_PATH()
- * @method static EnvVars PORT()
- * @method static EnvVars TASK_WORKER_NUM()
- * @method static EnvVars WEB_WORKER_NUM()
- * @method static EnvVars ANONYMIZE_REMOTE_ADDR()
- * @method static EnvVars TRACK_ORPHAN_VISITS()
- * @method static EnvVars DISABLE_TRACK_PARAM()
- * @method static EnvVars DISABLE_TRACKING()
- * @method static EnvVars DISABLE_IP_TRACKING()
- * @method static EnvVars DISABLE_REFERRER_TRACKING()
- * @method static EnvVars DISABLE_UA_TRACKING()
- * @method static EnvVars DISABLE_TRACKING_FROM()
- * @method static EnvVars DEFAULT_SHORT_CODES_LENGTH()
- * @method static EnvVars IS_HTTPS_ENABLED()
- * @method static EnvVars DEFAULT_DOMAIN()
- * @method static EnvVars AUTO_RESOLVE_TITLES()
- * @method static EnvVars REDIRECT_APPEND_EXTRA_PATH()
- * @method static EnvVars TIMEZONE()
- * @method static EnvVars VISITS_WEBHOOKS()
- * @method static EnvVars NOTIFY_ORPHAN_VISITS_TO_WEBHOOKS()
- */
-final class EnvVars
+enum EnvVars: string
 {
-    public const DELETE_SHORT_URL_THRESHOLD = 'DELETE_SHORT_URL_THRESHOLD';
-    public const DB_DRIVER = 'DB_DRIVER';
-    public const DB_NAME = 'DB_NAME';
-    public const DB_USER = 'DB_USER';
-    public const DB_PASSWORD = 'DB_PASSWORD';
-    public const DB_HOST = 'DB_HOST';
-    public const DB_UNIX_SOCKET = 'DB_UNIX_SOCKET';
-    public const DB_PORT = 'DB_PORT';
-    public const GEOLITE_LICENSE_KEY = 'GEOLITE_LICENSE_KEY';
-    public const REDIS_SERVERS = 'REDIS_SERVERS';
-    public const REDIS_SENTINEL_SERVICE = 'REDIS_SENTINEL_SERVICE';
-    public const MERCURE_PUBLIC_HUB_URL = 'MERCURE_PUBLIC_HUB_URL';
-    public const MERCURE_INTERNAL_HUB_URL = 'MERCURE_INTERNAL_HUB_URL';
-    public const MERCURE_JWT_SECRET = 'MERCURE_JWT_SECRET';
-    public const DEFAULT_QR_CODE_SIZE = 'DEFAULT_QR_CODE_SIZE';
-    public const DEFAULT_QR_CODE_MARGIN = 'DEFAULT_QR_CODE_MARGIN';
-    public const DEFAULT_QR_CODE_FORMAT = 'DEFAULT_QR_CODE_FORMAT';
-    public const DEFAULT_QR_CODE_ERROR_CORRECTION = 'DEFAULT_QR_CODE_ERROR_CORRECTION';
-    public const DEFAULT_QR_CODE_ROUND_BLOCK_SIZE = 'DEFAULT_QR_CODE_ROUND_BLOCK_SIZE';
-    public const RABBITMQ_ENABLED = 'RABBITMQ_ENABLED';
-    public const RABBITMQ_HOST = 'RABBITMQ_HOST';
-    public const RABBITMQ_PORT = 'RABBITMQ_PORT';
-    public const RABBITMQ_USER = 'RABBITMQ_USER';
-    public const RABBITMQ_PASSWORD = 'RABBITMQ_PASSWORD';
-    public const RABBITMQ_VHOST = 'RABBITMQ_VHOST';
-    public const DEFAULT_INVALID_SHORT_URL_REDIRECT = 'DEFAULT_INVALID_SHORT_URL_REDIRECT';
-    public const DEFAULT_REGULAR_404_REDIRECT = 'DEFAULT_REGULAR_404_REDIRECT';
-    public const DEFAULT_BASE_URL_REDIRECT = 'DEFAULT_BASE_URL_REDIRECT';
-    public const REDIRECT_STATUS_CODE = 'REDIRECT_STATUS_CODE';
-    public const REDIRECT_CACHE_LIFETIME = 'REDIRECT_CACHE_LIFETIME';
-    public const BASE_PATH = 'BASE_PATH';
-    public const PORT = 'PORT';
-    public const TASK_WORKER_NUM = 'TASK_WORKER_NUM';
-    public const WEB_WORKER_NUM = 'WEB_WORKER_NUM';
-    public const ANONYMIZE_REMOTE_ADDR = 'ANONYMIZE_REMOTE_ADDR';
-    public const TRACK_ORPHAN_VISITS = 'TRACK_ORPHAN_VISITS';
-    public const DISABLE_TRACK_PARAM = 'DISABLE_TRACK_PARAM';
-    public const DISABLE_TRACKING = 'DISABLE_TRACKING';
-    public const DISABLE_IP_TRACKING = 'DISABLE_IP_TRACKING';
-    public const DISABLE_REFERRER_TRACKING = 'DISABLE_REFERRER_TRACKING';
-    public const DISABLE_UA_TRACKING = 'DISABLE_UA_TRACKING';
-    public const DISABLE_TRACKING_FROM = 'DISABLE_TRACKING_FROM';
-    public const DEFAULT_SHORT_CODES_LENGTH = 'DEFAULT_SHORT_CODES_LENGTH';
-    public const IS_HTTPS_ENABLED = 'IS_HTTPS_ENABLED';
-    public const DEFAULT_DOMAIN = 'DEFAULT_DOMAIN';
-    public const AUTO_RESOLVE_TITLES = 'AUTO_RESOLVE_TITLES';
-    public const REDIRECT_APPEND_EXTRA_PATH = 'REDIRECT_APPEND_EXTRA_PATH';
-    public const TIMEZONE = 'TIMEZONE';
+    case DELETE_SHORT_URL_THRESHOLD = 'DELETE_SHORT_URL_THRESHOLD';
+    case DB_DRIVER = 'DB_DRIVER';
+    case DB_NAME = 'DB_NAME';
+    case DB_USER = 'DB_USER';
+    case DB_PASSWORD = 'DB_PASSWORD';
+    case DB_HOST = 'DB_HOST';
+    case DB_UNIX_SOCKET = 'DB_UNIX_SOCKET';
+    case DB_PORT = 'DB_PORT';
+    case GEOLITE_LICENSE_KEY = 'GEOLITE_LICENSE_KEY';
+    case REDIS_SERVERS = 'REDIS_SERVERS';
+    case REDIS_SENTINEL_SERVICE = 'REDIS_SENTINEL_SERVICE';
+    case MERCURE_PUBLIC_HUB_URL = 'MERCURE_PUBLIC_HUB_URL';
+    case MERCURE_INTERNAL_HUB_URL = 'MERCURE_INTERNAL_HUB_URL';
+    case MERCURE_JWT_SECRET = 'MERCURE_JWT_SECRET';
+    case DEFAULT_QR_CODE_SIZE = 'DEFAULT_QR_CODE_SIZE';
+    case DEFAULT_QR_CODE_MARGIN = 'DEFAULT_QR_CODE_MARGIN';
+    case DEFAULT_QR_CODE_FORMAT = 'DEFAULT_QR_CODE_FORMAT';
+    case DEFAULT_QR_CODE_ERROR_CORRECTION = 'DEFAULT_QR_CODE_ERROR_CORRECTION';
+    case DEFAULT_QR_CODE_ROUND_BLOCK_SIZE = 'DEFAULT_QR_CODE_ROUND_BLOCK_SIZE';
+    case RABBITMQ_ENABLED = 'RABBITMQ_ENABLED';
+    case RABBITMQ_HOST = 'RABBITMQ_HOST';
+    case RABBITMQ_PORT = 'RABBITMQ_PORT';
+    case RABBITMQ_USER = 'RABBITMQ_USER';
+    case RABBITMQ_PASSWORD = 'RABBITMQ_PASSWORD';
+    case RABBITMQ_VHOST = 'RABBITMQ_VHOST';
+    case DEFAULT_INVALID_SHORT_URL_REDIRECT = 'DEFAULT_INVALID_SHORT_URL_REDIRECT';
+    case DEFAULT_REGULAR_404_REDIRECT = 'DEFAULT_REGULAR_404_REDIRECT';
+    case DEFAULT_BASE_URL_REDIRECT = 'DEFAULT_BASE_URL_REDIRECT';
+    case REDIRECT_STATUS_CODE = 'REDIRECT_STATUS_CODE';
+    case REDIRECT_CACHE_LIFETIME = 'REDIRECT_CACHE_LIFETIME';
+    case BASE_PATH = 'BASE_PATH';
+    case PORT = 'PORT';
+    case TASK_WORKER_NUM = 'TASK_WORKER_NUM';
+    case WEB_WORKER_NUM = 'WEB_WORKER_NUM';
+    case ANONYMIZE_REMOTE_ADDR = 'ANONYMIZE_REMOTE_ADDR';
+    case TRACK_ORPHAN_VISITS = 'TRACK_ORPHAN_VISITS';
+    case DISABLE_TRACK_PARAM = 'DISABLE_TRACK_PARAM';
+    case DISABLE_TRACKING = 'DISABLE_TRACKING';
+    case DISABLE_IP_TRACKING = 'DISABLE_IP_TRACKING';
+    case DISABLE_REFERRER_TRACKING = 'DISABLE_REFERRER_TRACKING';
+    case DISABLE_UA_TRACKING = 'DISABLE_UA_TRACKING';
+    case DISABLE_TRACKING_FROM = 'DISABLE_TRACKING_FROM';
+    case DEFAULT_SHORT_CODES_LENGTH = 'DEFAULT_SHORT_CODES_LENGTH';
+    case IS_HTTPS_ENABLED = 'IS_HTTPS_ENABLED';
+    case DEFAULT_DOMAIN = 'DEFAULT_DOMAIN';
+    case AUTO_RESOLVE_TITLES = 'AUTO_RESOLVE_TITLES';
+    case REDIRECT_APPEND_EXTRA_PATH = 'REDIRECT_APPEND_EXTRA_PATH';
+    case TIMEZONE = 'TIMEZONE';
     /** @deprecated */
-    public const VISITS_WEBHOOKS = 'VISITS_WEBHOOKS';
+    case VISITS_WEBHOOKS = 'VISITS_WEBHOOKS';
     /** @deprecated */
-    public const NOTIFY_ORPHAN_VISITS_TO_WEBHOOKS = 'NOTIFY_ORPHAN_VISITS_TO_WEBHOOKS';
-
-    /**
-     * @return string[]
-     */
-    public static function cases(): array
-    {
-        static $constants;
-        if ($constants !== null) {
-            return $constants;
-        }
-
-        $ref = new ReflectionClass(self::class);
-        return $constants = array_values($ref->getConstants(ReflectionClassConstant::IS_PUBLIC));
-    }
-
-    private function __construct(private string $envVar)
-    {
-    }
-
-    public static function __callStatic(string $name, array $arguments): self
-    {
-        if (! contains(self::cases(), $name)) {
-            throw new InvalidArgumentException('Invalid env var: "' . $name . '"');
-        }
-
-        return new self($name);
-    }
+    case NOTIFY_ORPHAN_VISITS_TO_WEBHOOKS = 'NOTIFY_ORPHAN_VISITS_TO_WEBHOOKS';
 
     public function loadFromEnv(mixed $default = null): mixed
     {
-        return env($this->envVar, $default);
+        return env($this->value, $default);
     }
 
     public function existsInEnv(): bool
diff --git a/module/Core/src/Entity/Visit.php b/module/Core/src/Entity/Visit.php
index c509bcc3..31bfcdcd 100644
--- a/module/Core/src/Entity/Visit.php
+++ b/module/Core/src/Entity/Visit.php
@@ -17,6 +17,7 @@ use function Shlinkio\Shlink\Core\isCrawler;
 
 class Visit extends AbstractEntity implements JsonSerializable
 {
+    // TODO Convert to enum
     public const TYPE_VALID_SHORT_URL = 'valid_short_url';
     public const TYPE_IMPORTED = 'imported';
     public const TYPE_INVALID_SHORT_URL = 'invalid_short_url';
diff --git a/module/Core/src/Model/ShortUrlsParams.php b/module/Core/src/Model/ShortUrlsParams.php
index 95cf4df6..ca4c0954 100644
--- a/module/Core/src/Model/ShortUrlsParams.php
+++ b/module/Core/src/Model/ShortUrlsParams.php
@@ -15,6 +15,8 @@ final class ShortUrlsParams
 {
     public const ORDERABLE_FIELDS = ['longUrl', 'shortCode', 'dateCreated', 'title', 'visits'];
     public const DEFAULT_ITEMS_PER_PAGE = 10;
+
+    // TODO Convert to enum
     public const TAGS_MODE_ANY = 'any';
     public const TAGS_MODE_ALL = 'all';
 
diff --git a/module/Core/test/Config/EnvVarsTest.php b/module/Core/test/Config/EnvVarsTest.php
index 51a7a088..6d4b1394 100644
--- a/module/Core/test/Config/EnvVarsTest.php
+++ b/module/Core/test/Config/EnvVarsTest.php
@@ -6,7 +6,6 @@ namespace ShlinkioTest\Shlink\Core\Config;
 
 use PHPUnit\Framework\TestCase;
 use Shlinkio\Shlink\Core\Config\EnvVars;
-use Shlinkio\Shlink\Core\Exception\InvalidArgumentException;
 
 use function putenv;
 
@@ -14,92 +13,14 @@ class EnvVarsTest extends TestCase
 {
     protected function setUp(): void
     {
-        putenv(EnvVars::BASE_PATH . '=the_base_path');
-        putenv(EnvVars::DB_NAME . '=shlink');
+        putenv(EnvVars::BASE_PATH->value . '=the_base_path');
+        putenv(EnvVars::DB_NAME->value . '=shlink');
     }
 
     protected function tearDown(): void
     {
-        putenv(EnvVars::BASE_PATH . '=');
-        putenv(EnvVars::DB_NAME . '=');
-    }
-
-    /** @test */
-    public function casesReturnsTheSameListEveryTime(): void
-    {
-        $list = EnvVars::cases();
-        self::assertSame($list, EnvVars::cases());
-        self::assertSame([
-            EnvVars::DELETE_SHORT_URL_THRESHOLD,
-            EnvVars::DB_DRIVER,
-            EnvVars::DB_NAME,
-            EnvVars::DB_USER,
-            EnvVars::DB_PASSWORD,
-            EnvVars::DB_HOST,
-            EnvVars::DB_UNIX_SOCKET,
-            EnvVars::DB_PORT,
-            EnvVars::GEOLITE_LICENSE_KEY,
-            EnvVars::REDIS_SERVERS,
-            EnvVars::REDIS_SENTINEL_SERVICE,
-            EnvVars::MERCURE_PUBLIC_HUB_URL,
-            EnvVars::MERCURE_INTERNAL_HUB_URL,
-            EnvVars::MERCURE_JWT_SECRET,
-            EnvVars::DEFAULT_QR_CODE_SIZE,
-            EnvVars::DEFAULT_QR_CODE_MARGIN,
-            EnvVars::DEFAULT_QR_CODE_FORMAT,
-            EnvVars::DEFAULT_QR_CODE_ERROR_CORRECTION,
-            EnvVars::DEFAULT_QR_CODE_ROUND_BLOCK_SIZE,
-            EnvVars::RABBITMQ_ENABLED,
-            EnvVars::RABBITMQ_HOST,
-            EnvVars::RABBITMQ_PORT,
-            EnvVars::RABBITMQ_USER,
-            EnvVars::RABBITMQ_PASSWORD,
-            EnvVars::RABBITMQ_VHOST,
-            EnvVars::DEFAULT_INVALID_SHORT_URL_REDIRECT,
-            EnvVars::DEFAULT_REGULAR_404_REDIRECT,
-            EnvVars::DEFAULT_BASE_URL_REDIRECT,
-            EnvVars::REDIRECT_STATUS_CODE,
-            EnvVars::REDIRECT_CACHE_LIFETIME,
-            EnvVars::BASE_PATH,
-            EnvVars::PORT,
-            EnvVars::TASK_WORKER_NUM,
-            EnvVars::WEB_WORKER_NUM,
-            EnvVars::ANONYMIZE_REMOTE_ADDR,
-            EnvVars::TRACK_ORPHAN_VISITS,
-            EnvVars::DISABLE_TRACK_PARAM,
-            EnvVars::DISABLE_TRACKING,
-            EnvVars::DISABLE_IP_TRACKING,
-            EnvVars::DISABLE_REFERRER_TRACKING,
-            EnvVars::DISABLE_UA_TRACKING,
-            EnvVars::DISABLE_TRACKING_FROM,
-            EnvVars::DEFAULT_SHORT_CODES_LENGTH,
-            EnvVars::IS_HTTPS_ENABLED,
-            EnvVars::DEFAULT_DOMAIN,
-            EnvVars::AUTO_RESOLVE_TITLES,
-            EnvVars::REDIRECT_APPEND_EXTRA_PATH,
-            EnvVars::TIMEZONE,
-            EnvVars::VISITS_WEBHOOKS,
-            EnvVars::NOTIFY_ORPHAN_VISITS_TO_WEBHOOKS,
-        ], $list);
-    }
-
-    /**
-     * @test
-     * @dataProvider provideInvalidEnvVars
-     */
-    public function exceptionIsThrownWhenTryingToLoadInvalidEnvVar(string $envVar): void
-    {
-        $this->expectException(InvalidArgumentException::class);
-        $this->expectExceptionMessage('Invalid env var: "' . $envVar . '"');
-
-        EnvVars::{$envVar}();
-    }
-
-    public function provideInvalidEnvVars(): iterable
-    {
-        yield 'foo' => ['foo'];
-        yield 'bar' => ['bar'];
-        yield 'invalid' => ['invalid'];
+        putenv(EnvVars::BASE_PATH->value . '=');
+        putenv(EnvVars::DB_NAME->value . '=');
     }
 
     /**
@@ -113,10 +34,10 @@ class EnvVarsTest extends TestCase
 
     public function provideExistingEnvVars(): iterable
     {
-        yield 'DB_NAME' => [EnvVars::DB_NAME(), true];
-        yield 'BASE_PATH' => [EnvVars::BASE_PATH(), true];
-        yield 'DB_DRIVER' => [EnvVars::DB_DRIVER(), false];
-        yield 'DEFAULT_REGULAR_404_REDIRECT' => [EnvVars::DEFAULT_REGULAR_404_REDIRECT(), false];
+        yield 'DB_NAME' => [EnvVars::DB_NAME, true];
+        yield 'BASE_PATH' => [EnvVars::BASE_PATH, true];
+        yield 'DB_DRIVER' => [EnvVars::DB_DRIVER, false];
+        yield 'DEFAULT_REGULAR_404_REDIRECT' => [EnvVars::DEFAULT_REGULAR_404_REDIRECT, false];
     }
 
     /**
@@ -130,11 +51,11 @@ class EnvVarsTest extends TestCase
 
     public function provideEnvVarsValues(): iterable
     {
-        yield 'DB_NAME without default' => [EnvVars::DB_NAME(), 'shlink', null];
-        yield 'DB_NAME with default' => [EnvVars::DB_NAME(), 'shlink', 'foobar'];
-        yield 'BASE_PATH without default' => [EnvVars::BASE_PATH(), 'the_base_path', null];
-        yield 'BASE_PATH with default' => [EnvVars::BASE_PATH(), 'the_base_path', 'foobar'];
-        yield 'DB_DRIVER without default' => [EnvVars::DB_DRIVER(), null, null];
-        yield 'DB_DRIVER with default' => [EnvVars::DB_DRIVER(), 'foobar', 'foobar'];
+        yield 'DB_NAME without default' => [EnvVars::DB_NAME, 'shlink', null];
+        yield 'DB_NAME with default' => [EnvVars::DB_NAME, 'shlink', 'foobar'];
+        yield 'BASE_PATH without default' => [EnvVars::BASE_PATH, 'the_base_path', null];
+        yield 'BASE_PATH with default' => [EnvVars::BASE_PATH, 'the_base_path', 'foobar'];
+        yield 'DB_DRIVER without default' => [EnvVars::DB_DRIVER, null, null];
+        yield 'DB_DRIVER with default' => [EnvVars::DB_DRIVER, 'foobar', 'foobar'];
     }
 }
diff --git a/module/Rest/src/ApiKey/Role.php b/module/Rest/src/ApiKey/Role.php
index 557abd00..28291837 100644
--- a/module/Rest/src/ApiKey/Role.php
+++ b/module/Rest/src/ApiKey/Role.php
@@ -12,6 +12,7 @@ use Shlinkio\Shlink\Core\ShortUrl\Spec\BelongsToDomain;
 use Shlinkio\Shlink\Core\ShortUrl\Spec\BelongsToDomainInlined;
 use Shlinkio\Shlink\Rest\Entity\ApiKeyRole;
 
+// TODO Convert to enum
 class Role
 {
     public const AUTHORED_SHORT_URLS = 'AUTHORED_SHORT_URLS';