From b9cb1e69e6f20fa17522aec68a137d79f075ae5d Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Mon, 16 Oct 2017 16:43:12 +1300 Subject: [PATCH] BUG Replace phpdotenv with thread-safe replacement --- composer.json | 2 +- .../03_Environment_Management.md | 31 ++++--- docs/en/04_Changelogs/4.0.0.md | 2 + src/Control/Email/Email.php | 3 +- src/Core/Cache/ManifestCacheFactory.php | 3 +- src/Core/Config/CoreConfigFactory.php | 6 +- src/Core/CoreKernel.php | 26 +++--- src/Core/Environment.php | 71 +++++++++++++++- src/Core/EnvironmentLoader.php | 47 +++++++++++ src/Core/Injector/Injector.php | 6 +- src/Core/TempFolder.php | 6 +- src/Dev/Install/InstallConfig.php | 19 +++-- src/ORM/Connect/TempDatabase.php | 5 +- src/ORM/DB.php | 3 +- src/Security/BasicAuth.php | 5 +- src/Security/DefaultAdminService.php | 9 +- src/includes/constants.php | 25 +++--- tests/bootstrap/environment.php | 35 ++++---- tests/php/Core/EnvironmentLoaderTest.php | 83 +++++++++++++++++++ tests/php/Core/EnvironmentTest.php | 56 +++++++++++++ tests/php/Core/EnvironmentTest/test.env | 13 +++ tests/php/Core/EnvironmentTest/test2.env | 6 ++ tests/php/Core/EnvironmentTest/test3.env | 4 + .../php/Core/Manifest/ConfigManifestTest.php | 13 +-- tests/php/ORM/DBTest.php | 3 +- 25 files changed, 387 insertions(+), 95 deletions(-) create mode 100644 src/Core/EnvironmentLoader.php create mode 100644 tests/php/Core/EnvironmentLoaderTest.php create mode 100644 tests/php/Core/EnvironmentTest.php create mode 100644 tests/php/Core/EnvironmentTest/test.env create mode 100644 tests/php/Core/EnvironmentTest/test2.env create mode 100644 tests/php/Core/EnvironmentTest/test3.env diff --git a/composer.json b/composer.json index d96cd8cdd..794a9c9e4 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,7 @@ "symfony/config": "^3.2", "symfony/translation": "^2.8", "symfony/yaml": "~3.2", - "vlucas/phpdotenv": "^2.4", + "m1/env": "^2.1", "php": ">=5.6.0", "ext-intl": "*", "ext-mbstring": "*", diff --git a/docs/en/00_Getting_Started/03_Environment_Management.md b/docs/en/00_Getting_Started/03_Environment_Management.md index b6f25e9d5..9a880ba33 100644 --- a/docs/en/00_Getting_Started/03_Environment_Management.md +++ b/docs/en/00_Getting_Started/03_Environment_Management.md @@ -7,8 +7,8 @@ server. For each of these environments we may require slightly different configurations for our servers. This could be our debug level, caching backends, or - of course - sensitive information such as database credentials. -To solve this problem of setting variables per environment we use environment variables with the help of the -[PHPDotEnv](https://github.com/vlucas/phpdotenv) library by Vance Lucas. +To manage environment variables, as well as other server globals, the [api:SilverStripe\Core\Environment] class +provides a set of APIs and helpers. ## Security considerations @@ -32,13 +32,18 @@ You can set "real" environment variables using Apache. Please ## How to access the environment variables -Accessing the environment varaibles is easy and can be done using the `getenv` method or in the `$_ENV` and `$_SERVER` -super-globals: +Accessing the environment varaibles should be done via the `Environment::getEnv()` method ```php -getenv('SS_DATABASE_CLASS'); -$_ENV['SS_DATABASE_CLASS']; -$_SERVER['SS_DATABASE_CLASS']; +use SilverStripe\Core\Environment; +Environment::getEnv('SS_DATABASE_CLASS'); +``` + +Individual settings can be assigned via `Environment::setEnv()` or `Environment::putEnv()` methods. + +```php +use SilverStripe\Core\Environment; +Environment::setEnv('API_KEY', 'AABBCCDDEEFF012345'); ``` ## Including an extra `.env` file @@ -46,12 +51,14 @@ $_SERVER['SS_DATABASE_CLASS']; Sometimes it may be useful to include an extra `.env` file - on a shared local development environment where all database credentials could be the same. To do this, you can add this snippet to your `mysite/_config.php` file: +Note that by default variables cannot be overloaded from this file; Existing values will be preferred +over values in this file. + ```php -try { - (new \Dotenv\Dotenv('/path/to/env/'))->load(); -} catch (\Dotenv\Exception\InvalidPathException $e) { - // no file found -} +use SilverStripe\Core\EnvironmentLoader; +$env = BASE_PATH . '/mysite/.env'; +$loader = new EnvironmentLoader(); +$loader->loadFile($env); ``` ## Core environment variables diff --git a/docs/en/04_Changelogs/4.0.0.md b/docs/en/04_Changelogs/4.0.0.md index 8c1bdce2e..ddc8f44bd 100644 --- a/docs/en/04_Changelogs/4.0.0.md +++ b/docs/en/04_Changelogs/4.0.0.md @@ -500,6 +500,8 @@ SS_BASE_URL="//localhost/" The global values `$database` and `$databaseConfig` have been deprecated, as has `ConfigureFromEnv.php` which is no longer necessary. +To access environment variables you can use the `SilverStripe\Core\Environment::getEnv()` method. + See [Environment Management docs](/getting-started/environment_management/) for full details. #### Replace usages of Object class diff --git a/src/Control/Email/Email.php b/src/Control/Email/Email.php index 0f0997f92..ffa0dbfd6 100644 --- a/src/Control/Email/Email.php +++ b/src/Control/Email/Email.php @@ -5,6 +5,7 @@ namespace SilverStripe\Control\Email; use SilverStripe\Control\Director; use SilverStripe\Control\HTTP; use SilverStripe\Core\Convert; +use SilverStripe\Core\Environment; use SilverStripe\Core\Injector\Injector; use SilverStripe\ORM\FieldType\DBDatetime; use SilverStripe\ORM\FieldType\DBField; @@ -159,7 +160,7 @@ class Email extends ViewableData $normalised[$name] = null; } } - $extra = getenv($env); + $extra = Environment::getEnv($env); if ($extra) { $normalised[$extra] = null; } diff --git a/src/Core/Cache/ManifestCacheFactory.php b/src/Core/Cache/ManifestCacheFactory.php index 53298e0ce..d296f95f8 100644 --- a/src/Core/Cache/ManifestCacheFactory.php +++ b/src/Core/Cache/ManifestCacheFactory.php @@ -11,6 +11,7 @@ use Psr\Log\LoggerInterface; use Psr\SimpleCache\CacheInterface; use ReflectionClass; use SilverStripe\Control\Director; +use SilverStripe\Core\Environment; /** * Assists with building of manifest cache prior to config being available @@ -43,7 +44,7 @@ class ManifestCacheFactory extends DefaultCacheFactory public function create($service, array $params = array()) { // Override default cache generation with SS_MANIFESTCACHE - $cacheClass = getenv('SS_MANIFESTCACHE'); + $cacheClass = Environment::getEnv('SS_MANIFESTCACHE'); if (!$cacheClass) { return parent::create($service, $params); } diff --git a/src/Core/Config/CoreConfigFactory.php b/src/Core/Config/CoreConfigFactory.php index 9ab016616..4a0cc7073 100644 --- a/src/Core/Config/CoreConfigFactory.php +++ b/src/Core/Config/CoreConfigFactory.php @@ -11,6 +11,7 @@ use SilverStripe\Config\Transformer\YamlTransformer; use SilverStripe\Core\Cache\CacheFactory; use SilverStripe\Core\Config\Middleware\ExtensionMiddleware; use SilverStripe\Core\Config\Middleware\InheritanceMiddleware; +use SilverStripe\Core\Environment; use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Kernel; use SilverStripe\Core\Manifest\ClassLoader; @@ -141,11 +142,12 @@ class CoreConfigFactory // Add default rules $envvarset = function ($var, $value = null) { - if (getenv($var) === false) { + $actual = Environment::getEnv($var); + if ($actual === false) { return false; } if ($value) { - return getenv($var) === $value; + return $actual === $value; } return true; }; diff --git a/src/Core/CoreKernel.php b/src/Core/CoreKernel.php index 086cfe7e6..951f78ef6 100644 --- a/src/Core/CoreKernel.php +++ b/src/Core/CoreKernel.php @@ -137,7 +137,7 @@ class CoreKernel implements Kernel } // Check getenv - if ($env = getenv('SS_ENVIRONMENT_TYPE')) { + if ($env = Environment::getEnv('SS_ENVIRONMENT_TYPE')) { return $env; } @@ -328,32 +328,32 @@ class CoreKernel implements Kernel { /** @skipUpgrade */ $databaseConfig = [ - "type" => getenv('SS_DATABASE_CLASS') ?: 'MySQLDatabase', - "server" => getenv('SS_DATABASE_SERVER') ?: 'localhost', - "username" => getenv('SS_DATABASE_USERNAME') ?: null, - "password" => getenv('SS_DATABASE_PASSWORD') ?: null, + "type" => Environment::getEnv('SS_DATABASE_CLASS') ?: 'MySQLDatabase', + "server" => Environment::getEnv('SS_DATABASE_SERVER') ?: 'localhost', + "username" => Environment::getEnv('SS_DATABASE_USERNAME') ?: null, + "password" => Environment::getEnv('SS_DATABASE_PASSWORD') ?: null, ]; // Set the port if called for - $dbPort = getenv('SS_DATABASE_PORT'); + $dbPort = Environment::getEnv('SS_DATABASE_PORT'); if ($dbPort) { $databaseConfig['port'] = $dbPort; } // Set the timezone if called for - $dbTZ = getenv('SS_DATABASE_TIMEZONE'); + $dbTZ = Environment::getEnv('SS_DATABASE_TIMEZONE'); if ($dbTZ) { $databaseConfig['timezone'] = $dbTZ; } // For schema enabled drivers: - $dbSchema = getenv('SS_DATABASE_SCHEMA'); + $dbSchema = Environment::getEnv('SS_DATABASE_SCHEMA'); if ($dbSchema) { $databaseConfig["schema"] = $dbSchema; } // For SQlite3 memory databases (mainly for testing purposes) - $dbMemory = getenv('SS_DATABASE_MEMORY'); + $dbMemory = Environment::getEnv('SS_DATABASE_MEMORY'); if ($dbMemory) { $databaseConfig["memory"] = $dbMemory; } @@ -368,7 +368,7 @@ class CoreKernel implements Kernel */ protected function getDatabasePrefix() { - return getenv('SS_DATABASE_PREFIX') ?: ''; + return Environment::getEnv('SS_DATABASE_PREFIX') ?: ''; } /** @@ -392,14 +392,14 @@ class CoreKernel implements Kernel } // Check environment - $database = getenv('SS_DATABASE_NAME'); + $database = Environment::getEnv('SS_DATABASE_NAME'); if ($database) { return $this->getDatabasePrefix() . $database; } // Auto-detect name - $chooseName = getenv('SS_DATABASE_CHOOSE_NAME'); + $chooseName = Environment::getEnv('SS_DATABASE_CHOOSE_NAME'); if ($chooseName) { // Find directory to build name from @@ -529,7 +529,7 @@ class CoreKernel implements Kernel $errorHandler->start(); // Register error log file - $errorLog = getenv('SS_ERROR_LOG'); + $errorLog = Environment::getEnv('SS_ERROR_LOG'); if ($errorLog) { $logger = Injector::inst()->get(LoggerInterface::class); if ($logger instanceof Logger) { diff --git a/src/Core/Environment.php b/src/Core/Environment.php index 5f46afa12..6d5f1f34b 100644 --- a/src/Core/Environment.php +++ b/src/Core/Environment.php @@ -6,6 +6,11 @@ namespace SilverStripe\Core; * Consolidates access and modification of PHP global variables and settings. * This class should be used sparingly, and only if information cannot be obtained * from a current {@link HTTPRequest} object. + * + * Acts as the primary store for environment variables, including those loaded + * from .env files. Applications should use Environment::getEnv() instead of php's + * `getenv` in order to include `.env` configuration, as the system's actual + * environment variables are treated immutably. */ class Environment { @@ -23,6 +28,13 @@ class Environment */ protected static $timeLimitMax = null; + /** + * Local overrides for all $_ENV var protected from cross-process operations + * + * @var array + */ + protected static $env = []; + /** * Extract env vars prior to modification * @@ -31,7 +43,7 @@ class Environment public static function getVariables() { // Suppress return by-ref - return array_merge($GLOBALS, []); + return array_merge($GLOBALS, [ 'env' => static::$env ]); } /** @@ -41,8 +53,14 @@ class Environment */ public static function setVariables(array $vars) { - foreach ($vars as $key => $value) { - $GLOBALS[$key] = $value; + foreach ($vars as $varName => $varValue) { + if ($varName === 'env') { + continue; + } + $GLOBALS[$varName] = $varValue; + } + if (array_key_exists('env', $vars)) { + static::$env = $vars['env']; } } @@ -151,4 +169,51 @@ class Environment { return static::$timeLimitMax; } + + /** + * Get value of environment variable + * + * @param string $name + * @return mixed Value of the environment variable, or false if not set + */ + public static function getEnv($name) + { + switch (true) { + case array_key_exists($name, static::$env): + return static::$env[$name]; + case array_key_exists($name, $_ENV): + return $_ENV[$name]; + case array_key_exists($name, $_SERVER): + return $_SERVER[$name]; + default: + return getenv($name); + } + } + + /** + * Set environment variable using php.ini syntax. + * Acts as a process-isolated version of putenv() + * Note: This will be parsed via parse_ini_string() which handles quoted values + * + * @param string $string Setting to assign in KEY=VALUE or KEY="VALUE" syntax + */ + public static function putEnv($string) + { + // Parse name-value pairs + $envVars = parse_ini_string($string) ?: []; + foreach ($envVars as $name => $value) { + self::setEnv($name, $value); + } + } + + /** + * Set environment variable via $name / $value pair + * + * @param string $name + * @param string $value + */ + public static function setEnv($name, $value) + { + static::$env[$name] = $value; + } } diff --git a/src/Core/EnvironmentLoader.php b/src/Core/EnvironmentLoader.php new file mode 100644 index 000000000..6ec8333f7 --- /dev/null +++ b/src/Core/EnvironmentLoader.php @@ -0,0 +1,47 @@ + $value) { + // Conditionally prevent overloading + if (!$overload) { + $existing = Environment::getEnv($name); + if ($existing !== false) { + $result[$name] = $existing; + continue; + } + } + + // Overload or create var + Environment::setEnv($name, $value); + $result[$name] = $value; + } + return $result; + } +} diff --git a/src/Core/Injector/Injector.php b/src/Core/Injector/Injector.php index 804533f66..8c4c940d3 100644 --- a/src/Core/Injector/Injector.php +++ b/src/Core/Injector/Injector.php @@ -11,6 +11,7 @@ use ReflectionObject; use ReflectionProperty; use SilverStripe\Core\ClassInfo; use SilverStripe\Core\Config\Config; +use SilverStripe\Core\Environment; use SilverStripe\Dev\Deprecation; /** @@ -523,8 +524,9 @@ class Injector implements ContainerInterface // Evaluate constants surrounded by back ticks if (preg_match('/^`(?[^`]+)`$/', $value, $matches)) { - if (getenv($matches['name']) !== false) { - $value = getenv($matches['name']); + $envValue = Environment::getEnv($matches['name']); + if ($envValue !== false) { + $value = $envValue; } elseif (defined($matches['name'])) { $value = constant($matches['name']); } else { diff --git a/src/Core/TempFolder.php b/src/Core/TempFolder.php index e2fef722e..87846459c 100644 --- a/src/Core/TempFolder.php +++ b/src/Core/TempFolder.php @@ -36,12 +36,12 @@ class TempFolder */ public static function getTempFolderUsername() { - $user = getenv('APACHE_RUN_USER'); + $user = Environment::getEnv('APACHE_RUN_USER'); if (!$user) { - $user = getenv('USER'); + $user = Environment::getEnv('USER'); } if (!$user) { - $user = getenv('USERNAME'); + $user = Environment::getEnv('USERNAME'); } if (!$user && function_exists('posix_getpwuid') && function_exists('posix_getuid')) { $userDetails = posix_getpwuid(posix_getuid()); diff --git a/src/Dev/Install/InstallConfig.php b/src/Dev/Install/InstallConfig.php index 6be632b15..6d6b10088 100644 --- a/src/Dev/Install/InstallConfig.php +++ b/src/Dev/Install/InstallConfig.php @@ -2,6 +2,8 @@ namespace SilverStripe\Dev\Install; +use SilverStripe\Core\Environment; + /** * Provides environment settings from the current request + environment * @@ -42,10 +44,10 @@ class InstallConfig // Guess database config return [ 'type' => $this->getDatabaseClass($databaseClasses), - 'server' => getenv('SS_DATABASE_SERVER') ?: 'localhost', - 'username' => getenv('SS_DATABASE_USERNAME') ?: 'root', - 'password' => getenv('SS_DATABASE_PASSWORD') ?: '', - 'database' => getenv('SS_DATABASE_NAME') ?: 'SS_mysite', + 'server' => Environment::getEnv('SS_DATABASE_SERVER') ?: 'localhost', + 'username' => Environment::getEnv('SS_DATABASE_USERNAME') ?: 'root', + 'password' => Environment::getEnv('SS_DATABASE_PASSWORD') ?: '', + 'database' => Environment::getEnv('SS_DATABASE_NAME') ?: 'SS_mysite', ]; } @@ -62,8 +64,8 @@ class InstallConfig } return [ - 'username' => getenv('SS_DEFAULT_ADMIN_USERNAME') ?: 'admin', - 'password' => getenv('SS_DEFAULT_ADMIN_PASSWORD') ?: '', + 'username' => Environment::getEnv('SS_DEFAULT_ADMIN_USERNAME') ?: 'admin', + 'password' => Environment::getEnv('SS_DEFAULT_ADMIN_PASSWORD') ?: '', ]; } @@ -114,8 +116,9 @@ class InstallConfig */ protected function getDatabaseClass($databaseClasses) { - if (getenv('SS_DATABASE_CLASS')) { - return getenv('SS_DATABASE_CLASS'); + $envDatabase = Environment::getEnv('SS_DATABASE_CLASS'); + if ($envDatabase) { + return $envDatabase; } // Check supported versions diff --git a/src/ORM/Connect/TempDatabase.php b/src/ORM/Connect/TempDatabase.php index f35165799..c41f98f78 100644 --- a/src/ORM/Connect/TempDatabase.php +++ b/src/ORM/Connect/TempDatabase.php @@ -4,6 +4,7 @@ namespace SilverStripe\ORM\Connect; use Exception; use SilverStripe\Core\ClassInfo; +use SilverStripe\Core\Environment; use SilverStripe\Core\Injector\Injectable; use SilverStripe\Core\Injector\Injector; use SilverStripe\Dev\TestOnly; @@ -40,7 +41,7 @@ class TempDatabase */ protected function isDBTemp($name) { - $prefix = getenv('SS_DATABASE_PREFIX') ?: 'ss_'; + $prefix = Environment::getEnv('SS_DATABASE_PREFIX') ?: 'ss_'; $result = preg_match( sprintf('/^%stmpdb_[0-9]+_[0-9]+$/i', preg_quote($prefix, '/')), $name @@ -133,7 +134,7 @@ class TempDatabase // Create a temporary database, and force the connection to use UTC for time $dbConn = $this->getConn(); - $prefix = getenv('SS_DATABASE_PREFIX') ?: 'ss_'; + $prefix = Environment::getEnv('SS_DATABASE_PREFIX') ?: 'ss_'; do { $dbname = strtolower(sprintf('%stmpdb_%s_%s', $prefix, time(), rand(1000000, 9999999))); } while ($dbConn->databaseExists($dbname)); diff --git a/src/ORM/DB.php b/src/ORM/DB.php index 9f57b9eb8..77d7783e5 100644 --- a/src/ORM/DB.php +++ b/src/ORM/DB.php @@ -8,6 +8,7 @@ use SilverStripe\Control\Director; use SilverStripe\Control\HTTPRequest; use SilverStripe\Core\Config\Config; use SilverStripe\Core\Convert; +use SilverStripe\Core\Environment; use SilverStripe\Core\Injector\Injector; use SilverStripe\Dev\Deprecation; use SilverStripe\ORM\Connect\Database; @@ -265,7 +266,7 @@ class DB return false; } - $prefix = getenv('SS_DATABASE_PREFIX') ?: 'ss_'; + $prefix = Environment::getEnv('SS_DATABASE_PREFIX') ?: 'ss_'; $pattern = strtolower(sprintf('/^%stmpdb\d{7}$/', $prefix)); return (bool)preg_match($pattern, $name); } diff --git a/src/Security/BasicAuth.php b/src/Security/BasicAuth.php index 6b737deb3..c0df0a800 100644 --- a/src/Security/BasicAuth.php +++ b/src/Security/BasicAuth.php @@ -8,6 +8,7 @@ use SilverStripe\Control\HTTPRequest; use SilverStripe\Control\HTTPResponse; use SilverStripe\Control\HTTPResponse_Exception; use SilverStripe\Core\Config\Configurable; +use SilverStripe\Core\Environment; use SilverStripe\ORM\Connect\DatabaseException; use SilverStripe\Security\MemberAuthenticator\MemberAuthenticator; @@ -224,9 +225,9 @@ class BasicAuth // Check if site is protected if ($config->get('entire_site_protected')) { $permissionCode = $config->get('entire_site_protected_code'); - } elseif (getenv(self::USE_BASIC_AUTH)) { + } elseif (Environment::getEnv(self::USE_BASIC_AUTH)) { // Convert legacy 1 / true to ADMIN permissions - $permissionCode = getenv(self::USE_BASIC_AUTH); + $permissionCode = Environment::getEnv(self::USE_BASIC_AUTH); if (!is_string($permissionCode) || is_numeric($permissionCode)) { $permissionCode = self::AUTH_PERMISSION; } diff --git a/src/Security/DefaultAdminService.php b/src/Security/DefaultAdminService.php index 0b13824f0..22d4dd50c 100644 --- a/src/Security/DefaultAdminService.php +++ b/src/Security/DefaultAdminService.php @@ -5,6 +5,7 @@ namespace SilverStripe\Security; use BadMethodCallException; use InvalidArgumentException; use SilverStripe\Core\Config\Configurable; +use SilverStripe\Core\Environment; use SilverStripe\Core\Extensible; use SilverStripe\Core\Injector\Injectable; @@ -74,7 +75,7 @@ class DefaultAdminService "No default admin configured. Please call hasDefaultAdmin() before getting default admin username" ); } - return static::$default_username ?: getenv('SS_DEFAULT_ADMIN_USERNAME'); + return static::$default_username ?: Environment::getEnv('SS_DEFAULT_ADMIN_USERNAME'); } /** @@ -88,7 +89,7 @@ class DefaultAdminService "No default admin configured. Please call hasDefaultAdmin() before getting default admin password" ); } - return static::$default_password ?: getenv('SS_DEFAULT_ADMIN_PASSWORD'); + return static::$default_password ?: Environment::getEnv('SS_DEFAULT_ADMIN_PASSWORD'); } /** @@ -100,8 +101,8 @@ class DefaultAdminService { // Check environment if not explicitly set if (!isset(static::$has_default_admin)) { - return !empty(getenv('SS_DEFAULT_ADMIN_USERNAME')) - && !empty(getenv('SS_DEFAULT_ADMIN_PASSWORD')); + return !empty(Environment::getEnv('SS_DEFAULT_ADMIN_USERNAME')) + && !empty(Environment::getEnv('SS_DEFAULT_ADMIN_PASSWORD')); } return static::$has_default_admin; } diff --git a/src/includes/constants.php b/src/includes/constants.php index 669830501..61592cadf 100644 --- a/src/includes/constants.php +++ b/src/includes/constants.php @@ -1,7 +1,7 @@ load(); - } catch (InvalidPathException $e) { - // no .env found - no big deal - continue; + // Stop searching after first `.env` file is loaded + $dotEnvFile = $path . DIRECTORY_SEPARATOR . '.env'; + if ($loader->loadFile($dotEnvFile)) { + break; } - break; } }); } // Validate SS_BASE_URL is absolute -if (getenv('SS_BASE_URL') && !preg_match('#^(\w+:)?//.*#', getenv('SS_BASE_URL'))) { +if (Environment::getEnv('SS_BASE_URL') && !preg_match('#^(\w+:)?//.*#', Environment::getEnv('SS_BASE_URL'))) { call_user_func(function () { - $base = getenv('SS_BASE_URL'); + $base = Environment::getEnv('SS_BASE_URL'); user_error( "SS_BASE_URL should be an absolute url with protocol " . "(http://$base) or without protocol (//$base)", @@ -83,14 +82,14 @@ if (getenv('SS_BASE_URL') && !preg_match('#^(\w+:)?//.*#', getenv('SS_BASE_URL') ); // Treat as protocol-less absolute url $base = '//' . $base; - putenv("SS_BASE_URL=$base"); + Environment::setEnv('SS_BASE_URL', $base); }); } if (!defined('BASE_URL')) { define('BASE_URL', call_user_func(function () { // Prefer explicitly provided SS_BASE_URL - $base = getenv('SS_BASE_URL'); + $base = Environment::getEnv('SS_BASE_URL'); if ($base) { // Strip relative path from SS_BASE_URL return rtrim(parse_url($base, PHP_URL_PATH), '/'); diff --git a/tests/bootstrap/environment.php b/tests/bootstrap/environment.php index c131b4b2d..24d15f9d2 100644 --- a/tests/bootstrap/environment.php +++ b/tests/bootstrap/environment.php @@ -2,34 +2,35 @@ // Bootstrap environment variables -use Dotenv\Loader; - +use SilverStripe\Core\Environment; /** @skipUpgrade */ -if (!getenv('SS_DATABASE_CLASS') && !getenv('SS_DATABASE_USERNAME')) { - $loader = new Loader(null); +if (!Environment::getEnv('SS_DATABASE_CLASS') && !Environment::getEnv('SS_DATABASE_USERNAME')) { // The default settings let us define the database config via environment vars // Database connection, including PDO and legacy ORM support - switch (getenv('DB')) { + switch (Environment::getEnv('DB')) { case "PGSQL"; - $loader->setEnvironmentVariable('SS_DATABASE_CLASS', getenv('PDO') ? 'PostgrePDODatabase' : 'PostgreSQLDatabase'); - $loader->setEnvironmentVariable('SS_DATABASE_USERNAME', 'postgres'); - $loader->setEnvironmentVariable('SS_DATABASE_PASSWORD', ''); + $pgDatabaseClass = Environment::getEnv('PDO') ? 'PostgrePDODatabase' : 'PostgreSQLDatabase'; + Environment::setEnv('SS_DATABASE_CLASS', $pgDatabaseClass); + Environment::setEnv('SS_DATABASE_USERNAME', 'postgres'); + Environment::setEnv('SS_DATABASE_PASSWORD', ''); break; case "SQLITE": - $loader->setEnvironmentVariable('SS_DATABASE_CLASS', getenv('PDO') ? 'SQLite3PDODatabase' : 'SQLite3Database'); - $loader->setEnvironmentVariable('SS_DATABASE_USERNAME', 'root'); - $loader->setEnvironmentVariable('SS_DATABASE_PASSWORD', ''); - $loader->setEnvironmentVariable('SS_SQLITE_DATABASE_PATH', ':memory:'); + $sqliteDatabaseClass = Environment::getEnv('PDO') ? 'SQLite3PDODatabase' : 'SQLite3Database'; + Environment::setEnv('SS_DATABASE_CLASS', $sqliteDatabaseClass); + Environment::setEnv('SS_DATABASE_USERNAME', 'root'); + Environment::setEnv('SS_DATABASE_PASSWORD', ''); + Environment::setEnv('SS_SQLITE_DATABASE_PATH', ':memory:'); break; default: - $loader->setEnvironmentVariable('SS_DATABASE_CLASS', getenv('PDO') ? 'MySQLPDODatabase' : 'MySQLDatabase'); - $loader->setEnvironmentVariable('SS_DATABASE_USERNAME', 'root'); - $loader->setEnvironmentVariable('SS_DATABASE_PASSWORD', ''); + $mysqlDatabaseClass = Environment::getEnv('PDO') ? 'MySQLPDODatabase' : 'MySQLDatabase'; + Environment::setEnv('SS_DATABASE_CLASS', $mysqlDatabaseClass); + Environment::setEnv('SS_DATABASE_USERNAME', 'root'); + Environment::setEnv('SS_DATABASE_PASSWORD', ''); } - $loader->setEnvironmentVariable('SS_DATABASE_SERVER', '127.0.0.1'); - $loader->setEnvironmentVariable('SS_DATABASE_CHOOSE_NAME', true); + Environment::setEnv('SS_DATABASE_CHOOSE_NAME', 'true'); + Environment::setEnv('SS_DATABASE_SERVER', '127.0.0.1'); } diff --git a/tests/php/Core/EnvironmentLoaderTest.php b/tests/php/Core/EnvironmentLoaderTest.php new file mode 100644 index 000000000..143b56113 --- /dev/null +++ b/tests/php/Core/EnvironmentLoaderTest.php @@ -0,0 +1,83 @@ +assertNull($loader->loadFile(__DIR__ . '/EnvironmentTest/nofile.env')); + + // Initial load + $vars = $loader->loadFile(__DIR__ . '/EnvironmentTest/test.env'); + $this->assertCount(7, $vars); + $this->assertEquals('first', $vars['TEST_ENV_FIRST']); + $this->assertEquals('first', Environment::getEnv('TEST_ENV_FIRST')); + $this->assertEquals('start "#notcomment end', $vars['TEST_ENV_SECOND']); + $this->assertEquals('start "#notcomment end', Environment::getEnv('TEST_ENV_SECOND')); + $this->assertEquals(3, $vars['TEST_ENV_THIRD']); + $this->assertEquals(3, Environment::getEnv('TEST_ENV_THIRD')); + $this->assertEquals(true, $vars['TEST_ENV_FOURTH']); + $this->assertEquals(true, Environment::getEnv('TEST_ENV_FOURTH')); + $this->assertEquals('not#comment', $vars['TEST_ENV_FIFTH']); + $this->assertEquals('not#comment', Environment::getEnv('TEST_ENV_FIFTH')); + $this->assertEquals('not#comment', $vars['TEST_ENV_SIXTH']); + $this->assertEquals('not#comment', Environment::getEnv('TEST_ENV_SIXTH')); + $this->assertEquals('', $vars['TEST_ENV_SEVENTH']); + $this->assertEquals('', Environment::getEnv('TEST_ENV_SEVENTH')); + } + + public function testOverloading() + { + $loader = new EnvironmentLoader(); + + // No file + $loader->loadFile(__DIR__ . '/EnvironmentTest/test.env'); + + // Ensure default behaviour doesn't overload + $vars2 = $loader->loadFile(__DIR__ . '/EnvironmentTest/test2.env'); + $this->assertEquals( + [ + 'TEST_ENV_FIRST' => 'first', + 'TEST_ENV_SECOND' => 'start "#notcomment end', + 'TEST_ENV_NEWVAR' => 'first-overloaded', + 'TEST_ENV_NEWVAR2' => 'second-file', + ], + $vars2 + ); + $this->assertEquals('first', Environment::getEnv('TEST_ENV_FIRST')); + + // Test overload = true + $vars2 = $loader->loadFile(__DIR__ . '/EnvironmentTest/test2.env', true); + $this->assertEquals( + [ + 'TEST_ENV_FIRST' => 'first-overloaded', + 'TEST_ENV_SECOND' => 'first-overloaded', + 'TEST_ENV_NEWVAR' => 'first-overloaded', + 'TEST_ENV_NEWVAR2' => 'second-file', + ], + $vars2 + ); + $this->assertEquals('first-overloaded', Environment::getEnv('TEST_ENV_FIRST')); + } + + public function testInterpolation() + { + $loader = new EnvironmentLoader(); + $vars = $loader->loadFile(__DIR__ . '/EnvironmentTest/test3.env'); + $this->assertEquals( + [ + 'TEST_ENV_INT_ONE' => 'some var', + 'TEST_ENV_INT_TWO' => 'some var', + ], + $vars + ); + } +} diff --git a/tests/php/Core/EnvironmentTest.php b/tests/php/Core/EnvironmentTest.php new file mode 100644 index 000000000..220f47b58 --- /dev/null +++ b/tests/php/Core/EnvironmentTest.php @@ -0,0 +1,56 @@ +assertEquals($value, Environment::getEnv($var)); + } + + /** + * @dataProvider providerTestPutEnv + */ + public function testSetEnv($put, $var, $value) + { + Environment::setEnv($var, $value); + $this->assertEquals($value, Environment::getEnv($var)); + } + + public function testRestoreEnv() + { + // Set and backup original vars + Environment::putEnv('_ENVTEST_RESTORED=initial'); + $vars = Environment::getVariables(); + $this->assertEquals('initial', Environment::getEnv('_ENVTEST_RESTORED')); + + // Modify enironment + Environment::putEnv('_ENVTEST_RESTORED=new'); + $this->assertEquals('initial', $vars['env']['_ENVTEST_RESTORED']); + $this->assertEquals('new', Environment::getEnv('_ENVTEST_RESTORED')); + + // Restore + Environment::setVariables($vars); + $this->assertEquals('initial', Environment::getEnv('_ENVTEST_RESTORED')); + } +} diff --git a/tests/php/Core/EnvironmentTest/test.env b/tests/php/Core/EnvironmentTest/test.env new file mode 100644 index 000000000..035e5159f --- /dev/null +++ b/tests/php/Core/EnvironmentTest/test.env @@ -0,0 +1,13 @@ +# Test file + +TEST_ENV_FIRST="first" +TEST_ENV_SECOND="start \"#notcomment end"#Comment +#Comment +TEST_ENV_THIRD = 3 #comment + +TEST_ENV_FOURTH=true #comment +TEST_ENV_FIFTH = "not#comment"#comment +TEST_ENV_SIXTH="not#comment" + +TEST_ENV_SEVENTH="" #comment + diff --git a/tests/php/Core/EnvironmentTest/test2.env b/tests/php/Core/EnvironmentTest/test2.env new file mode 100644 index 000000000..b751a5162 --- /dev/null +++ b/tests/php/Core/EnvironmentTest/test2.env @@ -0,0 +1,6 @@ +# Test file + +TEST_ENV_FIRST="first-overloaded" +TEST_ENV_SECOND="${TEST_ENV_FIRST}" +TEST_ENV_NEWVAR="${TEST_ENV_FIRST}" +TEST_ENV_NEWVAR2="second-file" diff --git a/tests/php/Core/EnvironmentTest/test3.env b/tests/php/Core/EnvironmentTest/test3.env new file mode 100644 index 000000000..92a4ed0df --- /dev/null +++ b/tests/php/Core/EnvironmentTest/test3.env @@ -0,0 +1,4 @@ +# Test file + +TEST_ENV_INT_ONE="some var" +TEST_ENV_INT_TWO="${TEST_ENV_INT_ONE}" diff --git a/tests/php/Core/Manifest/ConfigManifestTest.php b/tests/php/Core/Manifest/ConfigManifestTest.php index 32b8483d0..d7868b08a 100644 --- a/tests/php/Core/Manifest/ConfigManifestTest.php +++ b/tests/php/Core/Manifest/ConfigManifestTest.php @@ -2,9 +2,9 @@ namespace SilverStripe\Core\Tests\Manifest; -use Dotenv\Loader; use SilverStripe\Config\Collections\MemoryConfigCollection; use SilverStripe\Core\Config\CoreConfigFactory; +use SilverStripe\Core\Environment; use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Kernel; use SilverStripe\Core\Manifest\ModuleLoader; @@ -100,8 +100,7 @@ class ConfigManifestTest extends SapphireTest public function testEnvVarSetRules() { - $loader = new Loader(null); - $loader->setEnvironmentVariable('ENVVARSET_FOO', 1); + Environment::setEnv('ENVVARSET_FOO', '1'); $config = $this->getConfigFixtureValue('EnvVarSet'); $this->assertEquals( @@ -137,9 +136,7 @@ class ConfigManifestTest extends SapphireTest public function testEnvOrConstantMatchesValueRules() { - $loader = new Loader(null); - - $loader->setEnvironmentVariable('CONSTANTMATCHESVALUE_FOO', 'Foo'); + Environment::setEnv('CONSTANTMATCHESVALUE_FOO', 'Foo'); define('CONSTANTMATCHESVALUE_BAR', 'Bar'); $config = $this->getConfigFixtureValue('EnvOrConstantMatchesValue'); @@ -194,9 +191,7 @@ class ConfigManifestTest extends SapphireTest public function testMultipleRules() { - $loader = new Loader(null); - - $loader->setEnvironmentVariable('MULTIPLERULES_ENVVARIABLESET', 1); + Environment::setEnv('MULTIPLERULES_ENVVARIABLESET', '1'); define('MULTIPLERULES_DEFINEDCONSTANT', 'defined'); $config = $this->getConfigFixtureValue('MultipleRules'); diff --git a/tests/php/ORM/DBTest.php b/tests/php/ORM/DBTest.php index 8cb20163b..f14d19bcf 100644 --- a/tests/php/ORM/DBTest.php +++ b/tests/php/ORM/DBTest.php @@ -3,6 +3,7 @@ namespace SilverStripe\ORM\Tests; use SilverStripe\Control\Director; +use SilverStripe\Core\Environment; use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Kernel; use SilverStripe\ORM\DB; @@ -15,7 +16,7 @@ class DBTest extends SapphireTest { /** @var Kernel $kernel */ $kernel = Injector::inst()->get(Kernel::class); - $prefix = getenv('SS_DATABASE_PREFIX') ?: 'ss_'; + $prefix = Environment::getEnv('SS_DATABASE_PREFIX') ?: 'ss_'; $kernel->setEnvironment(Kernel::DEV); $this->assertTrue(DB::valid_alternative_database_name($prefix.'tmpdb1234567'));