BUG Replace phpdotenv with thread-safe replacement

This commit is contained in:
Damian Mooyman 2017-10-16 16:43:12 +13:00 committed by Sam Minnée
parent b20bfd28a6
commit b9cb1e69e6
25 changed files with 387 additions and 95 deletions

View File

@ -35,7 +35,7 @@
"symfony/config": "^3.2", "symfony/config": "^3.2",
"symfony/translation": "^2.8", "symfony/translation": "^2.8",
"symfony/yaml": "~3.2", "symfony/yaml": "~3.2",
"vlucas/phpdotenv": "^2.4", "m1/env": "^2.1",
"php": ">=5.6.0", "php": ">=5.6.0",
"ext-intl": "*", "ext-intl": "*",
"ext-mbstring": "*", "ext-mbstring": "*",

View File

@ -7,8 +7,8 @@ server.
For each of these environments we may require slightly different configurations for our servers. This could be our debug 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. 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 To manage environment variables, as well as other server globals, the [api:SilverStripe\Core\Environment] class
[PHPDotEnv](https://github.com/vlucas/phpdotenv) library by Vance Lucas. provides a set of APIs and helpers.
## Security considerations ## Security considerations
@ -32,13 +32,18 @@ You can set "real" environment variables using Apache. Please
## How to access the environment variables ## 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` Accessing the environment varaibles should be done via the `Environment::getEnv()` method
super-globals:
```php ```php
getenv('SS_DATABASE_CLASS'); use SilverStripe\Core\Environment;
$_ENV['SS_DATABASE_CLASS']; Environment::getEnv('SS_DATABASE_CLASS');
$_SERVER['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 ## 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 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: 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 ```php
try { use SilverStripe\Core\EnvironmentLoader;
(new \Dotenv\Dotenv('/path/to/env/'))->load(); $env = BASE_PATH . '/mysite/.env';
} catch (\Dotenv\Exception\InvalidPathException $e) { $loader = new EnvironmentLoader();
// no file found $loader->loadFile($env);
}
``` ```
## Core environment variables ## Core environment variables

View File

@ -500,6 +500,8 @@ SS_BASE_URL="//localhost/"
The global values `$database` and `$databaseConfig` have been deprecated, as has `ConfigureFromEnv.php` The global values `$database` and `$databaseConfig` have been deprecated, as has `ConfigureFromEnv.php`
which is no longer necessary. 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. See [Environment Management docs](/getting-started/environment_management/) for full details.
#### Replace usages of Object class #### Replace usages of Object class

View File

@ -5,6 +5,7 @@ namespace SilverStripe\Control\Email;
use SilverStripe\Control\Director; use SilverStripe\Control\Director;
use SilverStripe\Control\HTTP; use SilverStripe\Control\HTTP;
use SilverStripe\Core\Convert; use SilverStripe\Core\Convert;
use SilverStripe\Core\Environment;
use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Injector\Injector;
use SilverStripe\ORM\FieldType\DBDatetime; use SilverStripe\ORM\FieldType\DBDatetime;
use SilverStripe\ORM\FieldType\DBField; use SilverStripe\ORM\FieldType\DBField;
@ -159,7 +160,7 @@ class Email extends ViewableData
$normalised[$name] = null; $normalised[$name] = null;
} }
} }
$extra = getenv($env); $extra = Environment::getEnv($env);
if ($extra) { if ($extra) {
$normalised[$extra] = null; $normalised[$extra] = null;
} }

View File

@ -11,6 +11,7 @@ use Psr\Log\LoggerInterface;
use Psr\SimpleCache\CacheInterface; use Psr\SimpleCache\CacheInterface;
use ReflectionClass; use ReflectionClass;
use SilverStripe\Control\Director; use SilverStripe\Control\Director;
use SilverStripe\Core\Environment;
/** /**
* Assists with building of manifest cache prior to config being available * 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()) public function create($service, array $params = array())
{ {
// Override default cache generation with SS_MANIFESTCACHE // Override default cache generation with SS_MANIFESTCACHE
$cacheClass = getenv('SS_MANIFESTCACHE'); $cacheClass = Environment::getEnv('SS_MANIFESTCACHE');
if (!$cacheClass) { if (!$cacheClass) {
return parent::create($service, $params); return parent::create($service, $params);
} }

View File

@ -11,6 +11,7 @@ use SilverStripe\Config\Transformer\YamlTransformer;
use SilverStripe\Core\Cache\CacheFactory; use SilverStripe\Core\Cache\CacheFactory;
use SilverStripe\Core\Config\Middleware\ExtensionMiddleware; use SilverStripe\Core\Config\Middleware\ExtensionMiddleware;
use SilverStripe\Core\Config\Middleware\InheritanceMiddleware; use SilverStripe\Core\Config\Middleware\InheritanceMiddleware;
use SilverStripe\Core\Environment;
use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Injector\Injector;
use SilverStripe\Core\Kernel; use SilverStripe\Core\Kernel;
use SilverStripe\Core\Manifest\ClassLoader; use SilverStripe\Core\Manifest\ClassLoader;
@ -141,11 +142,12 @@ class CoreConfigFactory
// Add default rules // Add default rules
$envvarset = function ($var, $value = null) { $envvarset = function ($var, $value = null) {
if (getenv($var) === false) { $actual = Environment::getEnv($var);
if ($actual === false) {
return false; return false;
} }
if ($value) { if ($value) {
return getenv($var) === $value; return $actual === $value;
} }
return true; return true;
}; };

View File

@ -137,7 +137,7 @@ class CoreKernel implements Kernel
} }
// Check getenv // Check getenv
if ($env = getenv('SS_ENVIRONMENT_TYPE')) { if ($env = Environment::getEnv('SS_ENVIRONMENT_TYPE')) {
return $env; return $env;
} }
@ -328,32 +328,32 @@ class CoreKernel implements Kernel
{ {
/** @skipUpgrade */ /** @skipUpgrade */
$databaseConfig = [ $databaseConfig = [
"type" => getenv('SS_DATABASE_CLASS') ?: 'MySQLDatabase', "type" => Environment::getEnv('SS_DATABASE_CLASS') ?: 'MySQLDatabase',
"server" => getenv('SS_DATABASE_SERVER') ?: 'localhost', "server" => Environment::getEnv('SS_DATABASE_SERVER') ?: 'localhost',
"username" => getenv('SS_DATABASE_USERNAME') ?: null, "username" => Environment::getEnv('SS_DATABASE_USERNAME') ?: null,
"password" => getenv('SS_DATABASE_PASSWORD') ?: null, "password" => Environment::getEnv('SS_DATABASE_PASSWORD') ?: null,
]; ];
// Set the port if called for // Set the port if called for
$dbPort = getenv('SS_DATABASE_PORT'); $dbPort = Environment::getEnv('SS_DATABASE_PORT');
if ($dbPort) { if ($dbPort) {
$databaseConfig['port'] = $dbPort; $databaseConfig['port'] = $dbPort;
} }
// Set the timezone if called for // Set the timezone if called for
$dbTZ = getenv('SS_DATABASE_TIMEZONE'); $dbTZ = Environment::getEnv('SS_DATABASE_TIMEZONE');
if ($dbTZ) { if ($dbTZ) {
$databaseConfig['timezone'] = $dbTZ; $databaseConfig['timezone'] = $dbTZ;
} }
// For schema enabled drivers: // For schema enabled drivers:
$dbSchema = getenv('SS_DATABASE_SCHEMA'); $dbSchema = Environment::getEnv('SS_DATABASE_SCHEMA');
if ($dbSchema) { if ($dbSchema) {
$databaseConfig["schema"] = $dbSchema; $databaseConfig["schema"] = $dbSchema;
} }
// For SQlite3 memory databases (mainly for testing purposes) // For SQlite3 memory databases (mainly for testing purposes)
$dbMemory = getenv('SS_DATABASE_MEMORY'); $dbMemory = Environment::getEnv('SS_DATABASE_MEMORY');
if ($dbMemory) { if ($dbMemory) {
$databaseConfig["memory"] = $dbMemory; $databaseConfig["memory"] = $dbMemory;
} }
@ -368,7 +368,7 @@ class CoreKernel implements Kernel
*/ */
protected function getDatabasePrefix() protected function getDatabasePrefix()
{ {
return getenv('SS_DATABASE_PREFIX') ?: ''; return Environment::getEnv('SS_DATABASE_PREFIX') ?: '';
} }
/** /**
@ -392,14 +392,14 @@ class CoreKernel implements Kernel
} }
// Check environment // Check environment
$database = getenv('SS_DATABASE_NAME'); $database = Environment::getEnv('SS_DATABASE_NAME');
if ($database) { if ($database) {
return $this->getDatabasePrefix() . $database; return $this->getDatabasePrefix() . $database;
} }
// Auto-detect name // Auto-detect name
$chooseName = getenv('SS_DATABASE_CHOOSE_NAME'); $chooseName = Environment::getEnv('SS_DATABASE_CHOOSE_NAME');
if ($chooseName) { if ($chooseName) {
// Find directory to build name from // Find directory to build name from
@ -529,7 +529,7 @@ class CoreKernel implements Kernel
$errorHandler->start(); $errorHandler->start();
// Register error log file // Register error log file
$errorLog = getenv('SS_ERROR_LOG'); $errorLog = Environment::getEnv('SS_ERROR_LOG');
if ($errorLog) { if ($errorLog) {
$logger = Injector::inst()->get(LoggerInterface::class); $logger = Injector::inst()->get(LoggerInterface::class);
if ($logger instanceof Logger) { if ($logger instanceof Logger) {

View File

@ -6,6 +6,11 @@ namespace SilverStripe\Core;
* Consolidates access and modification of PHP global variables and settings. * Consolidates access and modification of PHP global variables and settings.
* This class should be used sparingly, and only if information cannot be obtained * This class should be used sparingly, and only if information cannot be obtained
* from a current {@link HTTPRequest} object. * 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 class Environment
{ {
@ -23,6 +28,13 @@ class Environment
*/ */
protected static $timeLimitMax = null; 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 * Extract env vars prior to modification
* *
@ -31,7 +43,7 @@ class Environment
public static function getVariables() public static function getVariables()
{ {
// Suppress return by-ref // 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) public static function setVariables(array $vars)
{ {
foreach ($vars as $key => $value) { foreach ($vars as $varName => $varValue) {
$GLOBALS[$key] = $value; 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; 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;
}
} }

View File

@ -0,0 +1,47 @@
<?php
namespace SilverStripe\Core;
use M1\Env\Parser;
/**
* Loads environment variables from .env files
* Loosely based on https://github.com/vlucas/phpdotenv/blob/master/src/Loader.php
*/
class EnvironmentLoader
{
/**
* Load environment variables from .env file
*
* @param string $path Path to the file
* @param bool $overload Set to true to allow vars to overload. Recommended to leave false.
* @return array|null List of values parsed as an associative array, or null if not loaded
* If overloading, this list will reflect the final state for all variables
*/
public function loadFile($path, $overload = false)
{
// Not readable
if (!file_exists($path) || !is_readable($path)) {
return null;
}
// Parse and cleanup content
$result = [];
$variables = Parser::parse(file_get_contents($path));
foreach ($variables as $name => $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;
}
}

View File

@ -11,6 +11,7 @@ use ReflectionObject;
use ReflectionProperty; use ReflectionProperty;
use SilverStripe\Core\ClassInfo; use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Config\Config; use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Environment;
use SilverStripe\Dev\Deprecation; use SilverStripe\Dev\Deprecation;
/** /**
@ -523,8 +524,9 @@ class Injector implements ContainerInterface
// Evaluate constants surrounded by back ticks // Evaluate constants surrounded by back ticks
if (preg_match('/^`(?<name>[^`]+)`$/', $value, $matches)) { if (preg_match('/^`(?<name>[^`]+)`$/', $value, $matches)) {
if (getenv($matches['name']) !== false) { $envValue = Environment::getEnv($matches['name']);
$value = getenv($matches['name']); if ($envValue !== false) {
$value = $envValue;
} elseif (defined($matches['name'])) { } elseif (defined($matches['name'])) {
$value = constant($matches['name']); $value = constant($matches['name']);
} else { } else {

View File

@ -36,12 +36,12 @@ class TempFolder
*/ */
public static function getTempFolderUsername() public static function getTempFolderUsername()
{ {
$user = getenv('APACHE_RUN_USER'); $user = Environment::getEnv('APACHE_RUN_USER');
if (!$user) { if (!$user) {
$user = getenv('USER'); $user = Environment::getEnv('USER');
} }
if (!$user) { if (!$user) {
$user = getenv('USERNAME'); $user = Environment::getEnv('USERNAME');
} }
if (!$user && function_exists('posix_getpwuid') && function_exists('posix_getuid')) { if (!$user && function_exists('posix_getpwuid') && function_exists('posix_getuid')) {
$userDetails = posix_getpwuid(posix_getuid()); $userDetails = posix_getpwuid(posix_getuid());

View File

@ -2,6 +2,8 @@
namespace SilverStripe\Dev\Install; namespace SilverStripe\Dev\Install;
use SilverStripe\Core\Environment;
/** /**
* Provides environment settings from the current request + environment * Provides environment settings from the current request + environment
* *
@ -42,10 +44,10 @@ class InstallConfig
// Guess database config // Guess database config
return [ return [
'type' => $this->getDatabaseClass($databaseClasses), 'type' => $this->getDatabaseClass($databaseClasses),
'server' => getenv('SS_DATABASE_SERVER') ?: 'localhost', 'server' => Environment::getEnv('SS_DATABASE_SERVER') ?: 'localhost',
'username' => getenv('SS_DATABASE_USERNAME') ?: 'root', 'username' => Environment::getEnv('SS_DATABASE_USERNAME') ?: 'root',
'password' => getenv('SS_DATABASE_PASSWORD') ?: '', 'password' => Environment::getEnv('SS_DATABASE_PASSWORD') ?: '',
'database' => getenv('SS_DATABASE_NAME') ?: 'SS_mysite', 'database' => Environment::getEnv('SS_DATABASE_NAME') ?: 'SS_mysite',
]; ];
} }
@ -62,8 +64,8 @@ class InstallConfig
} }
return [ return [
'username' => getenv('SS_DEFAULT_ADMIN_USERNAME') ?: 'admin', 'username' => Environment::getEnv('SS_DEFAULT_ADMIN_USERNAME') ?: 'admin',
'password' => getenv('SS_DEFAULT_ADMIN_PASSWORD') ?: '', 'password' => Environment::getEnv('SS_DEFAULT_ADMIN_PASSWORD') ?: '',
]; ];
} }
@ -114,8 +116,9 @@ class InstallConfig
*/ */
protected function getDatabaseClass($databaseClasses) protected function getDatabaseClass($databaseClasses)
{ {
if (getenv('SS_DATABASE_CLASS')) { $envDatabase = Environment::getEnv('SS_DATABASE_CLASS');
return getenv('SS_DATABASE_CLASS'); if ($envDatabase) {
return $envDatabase;
} }
// Check supported versions // Check supported versions

View File

@ -4,6 +4,7 @@ namespace SilverStripe\ORM\Connect;
use Exception; use Exception;
use SilverStripe\Core\ClassInfo; use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Environment;
use SilverStripe\Core\Injector\Injectable; use SilverStripe\Core\Injector\Injectable;
use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\TestOnly; use SilverStripe\Dev\TestOnly;
@ -40,7 +41,7 @@ class TempDatabase
*/ */
protected function isDBTemp($name) protected function isDBTemp($name)
{ {
$prefix = getenv('SS_DATABASE_PREFIX') ?: 'ss_'; $prefix = Environment::getEnv('SS_DATABASE_PREFIX') ?: 'ss_';
$result = preg_match( $result = preg_match(
sprintf('/^%stmpdb_[0-9]+_[0-9]+$/i', preg_quote($prefix, '/')), sprintf('/^%stmpdb_[0-9]+_[0-9]+$/i', preg_quote($prefix, '/')),
$name $name
@ -133,7 +134,7 @@ class TempDatabase
// Create a temporary database, and force the connection to use UTC for time // Create a temporary database, and force the connection to use UTC for time
$dbConn = $this->getConn(); $dbConn = $this->getConn();
$prefix = getenv('SS_DATABASE_PREFIX') ?: 'ss_'; $prefix = Environment::getEnv('SS_DATABASE_PREFIX') ?: 'ss_';
do { do {
$dbname = strtolower(sprintf('%stmpdb_%s_%s', $prefix, time(), rand(1000000, 9999999))); $dbname = strtolower(sprintf('%stmpdb_%s_%s', $prefix, time(), rand(1000000, 9999999)));
} while ($dbConn->databaseExists($dbname)); } while ($dbConn->databaseExists($dbname));

View File

@ -8,6 +8,7 @@ use SilverStripe\Control\Director;
use SilverStripe\Control\HTTPRequest; use SilverStripe\Control\HTTPRequest;
use SilverStripe\Core\Config\Config; use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Convert; use SilverStripe\Core\Convert;
use SilverStripe\Core\Environment;
use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\Deprecation; use SilverStripe\Dev\Deprecation;
use SilverStripe\ORM\Connect\Database; use SilverStripe\ORM\Connect\Database;
@ -265,7 +266,7 @@ class DB
return false; return false;
} }
$prefix = getenv('SS_DATABASE_PREFIX') ?: 'ss_'; $prefix = Environment::getEnv('SS_DATABASE_PREFIX') ?: 'ss_';
$pattern = strtolower(sprintf('/^%stmpdb\d{7}$/', $prefix)); $pattern = strtolower(sprintf('/^%stmpdb\d{7}$/', $prefix));
return (bool)preg_match($pattern, $name); return (bool)preg_match($pattern, $name);
} }

View File

@ -8,6 +8,7 @@ use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse; use SilverStripe\Control\HTTPResponse;
use SilverStripe\Control\HTTPResponse_Exception; use SilverStripe\Control\HTTPResponse_Exception;
use SilverStripe\Core\Config\Configurable; use SilverStripe\Core\Config\Configurable;
use SilverStripe\Core\Environment;
use SilverStripe\ORM\Connect\DatabaseException; use SilverStripe\ORM\Connect\DatabaseException;
use SilverStripe\Security\MemberAuthenticator\MemberAuthenticator; use SilverStripe\Security\MemberAuthenticator\MemberAuthenticator;
@ -224,9 +225,9 @@ class BasicAuth
// Check if site is protected // Check if site is protected
if ($config->get('entire_site_protected')) { if ($config->get('entire_site_protected')) {
$permissionCode = $config->get('entire_site_protected_code'); $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 // 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)) { if (!is_string($permissionCode) || is_numeric($permissionCode)) {
$permissionCode = self::AUTH_PERMISSION; $permissionCode = self::AUTH_PERMISSION;
} }

View File

@ -5,6 +5,7 @@ namespace SilverStripe\Security;
use BadMethodCallException; use BadMethodCallException;
use InvalidArgumentException; use InvalidArgumentException;
use SilverStripe\Core\Config\Configurable; use SilverStripe\Core\Config\Configurable;
use SilverStripe\Core\Environment;
use SilverStripe\Core\Extensible; use SilverStripe\Core\Extensible;
use SilverStripe\Core\Injector\Injectable; use SilverStripe\Core\Injector\Injectable;
@ -74,7 +75,7 @@ class DefaultAdminService
"No default admin configured. Please call hasDefaultAdmin() before getting default admin username" "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" "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 // Check environment if not explicitly set
if (!isset(static::$has_default_admin)) { if (!isset(static::$has_default_admin)) {
return !empty(getenv('SS_DEFAULT_ADMIN_USERNAME')) return !empty(Environment::getEnv('SS_DEFAULT_ADMIN_USERNAME'))
&& !empty(getenv('SS_DEFAULT_ADMIN_PASSWORD')); && !empty(Environment::getEnv('SS_DEFAULT_ADMIN_PASSWORD'));
} }
return static::$has_default_admin; return static::$has_default_admin;
} }

View File

@ -1,7 +1,7 @@
<?php <?php
use Dotenv\Dotenv; use SilverStripe\Core\Environment;
use Dotenv\Exception\InvalidPathException; use SilverStripe\Core\EnvironmentLoader;
use SilverStripe\Core\TempFolder; use SilverStripe\Core\TempFolder;
/** /**
@ -58,24 +58,23 @@ if (!defined('BASE_PATH')) {
} }
// Allow a first class env var to be set that disables .env file loading // Allow a first class env var to be set that disables .env file loading
if (!getenv('SS_IGNORE_DOT_ENV')) { if (!Environment::getEnv('SS_IGNORE_DOT_ENV')) {
call_user_func(function () { call_user_func(function () {
$loader = new EnvironmentLoader();
foreach ([BASE_PATH, dirname(BASE_PATH)] as $path) { foreach ([BASE_PATH, dirname(BASE_PATH)] as $path) {
try { // Stop searching after first `.env` file is loaded
(new Dotenv($path))->load(); $dotEnvFile = $path . DIRECTORY_SEPARATOR . '.env';
} catch (InvalidPathException $e) { if ($loader->loadFile($dotEnvFile)) {
// no .env found - no big deal break;
continue;
} }
break;
} }
}); });
} }
// Validate SS_BASE_URL is absolute // 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 () { call_user_func(function () {
$base = getenv('SS_BASE_URL'); $base = Environment::getEnv('SS_BASE_URL');
user_error( user_error(
"SS_BASE_URL should be an absolute url with protocol " "SS_BASE_URL should be an absolute url with protocol "
. "(http://$base) or without protocol (//$base)", . "(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 // Treat as protocol-less absolute url
$base = '//' . $base; $base = '//' . $base;
putenv("SS_BASE_URL=$base"); Environment::setEnv('SS_BASE_URL', $base);
}); });
} }
if (!defined('BASE_URL')) { if (!defined('BASE_URL')) {
define('BASE_URL', call_user_func(function () { define('BASE_URL', call_user_func(function () {
// Prefer explicitly provided SS_BASE_URL // Prefer explicitly provided SS_BASE_URL
$base = getenv('SS_BASE_URL'); $base = Environment::getEnv('SS_BASE_URL');
if ($base) { if ($base) {
// Strip relative path from SS_BASE_URL // Strip relative path from SS_BASE_URL
return rtrim(parse_url($base, PHP_URL_PATH), '/'); return rtrim(parse_url($base, PHP_URL_PATH), '/');

View File

@ -2,34 +2,35 @@
// Bootstrap environment variables // Bootstrap environment variables
use Dotenv\Loader; use SilverStripe\Core\Environment;
/** @skipUpgrade */ /** @skipUpgrade */
if (!getenv('SS_DATABASE_CLASS') && !getenv('SS_DATABASE_USERNAME')) { if (!Environment::getEnv('SS_DATABASE_CLASS') && !Environment::getEnv('SS_DATABASE_USERNAME')) {
$loader = new Loader(null);
// The default settings let us define the database config via environment vars // The default settings let us define the database config via environment vars
// Database connection, including PDO and legacy ORM support // Database connection, including PDO and legacy ORM support
switch (getenv('DB')) { switch (Environment::getEnv('DB')) {
case "PGSQL"; case "PGSQL";
$loader->setEnvironmentVariable('SS_DATABASE_CLASS', getenv('PDO') ? 'PostgrePDODatabase' : 'PostgreSQLDatabase'); $pgDatabaseClass = Environment::getEnv('PDO') ? 'PostgrePDODatabase' : 'PostgreSQLDatabase';
$loader->setEnvironmentVariable('SS_DATABASE_USERNAME', 'postgres'); Environment::setEnv('SS_DATABASE_CLASS', $pgDatabaseClass);
$loader->setEnvironmentVariable('SS_DATABASE_PASSWORD', ''); Environment::setEnv('SS_DATABASE_USERNAME', 'postgres');
Environment::setEnv('SS_DATABASE_PASSWORD', '');
break; break;
case "SQLITE": case "SQLITE":
$loader->setEnvironmentVariable('SS_DATABASE_CLASS', getenv('PDO') ? 'SQLite3PDODatabase' : 'SQLite3Database'); $sqliteDatabaseClass = Environment::getEnv('PDO') ? 'SQLite3PDODatabase' : 'SQLite3Database';
$loader->setEnvironmentVariable('SS_DATABASE_USERNAME', 'root'); Environment::setEnv('SS_DATABASE_CLASS', $sqliteDatabaseClass);
$loader->setEnvironmentVariable('SS_DATABASE_PASSWORD', ''); Environment::setEnv('SS_DATABASE_USERNAME', 'root');
$loader->setEnvironmentVariable('SS_SQLITE_DATABASE_PATH', ':memory:'); Environment::setEnv('SS_DATABASE_PASSWORD', '');
Environment::setEnv('SS_SQLITE_DATABASE_PATH', ':memory:');
break; break;
default: default:
$loader->setEnvironmentVariable('SS_DATABASE_CLASS', getenv('PDO') ? 'MySQLPDODatabase' : 'MySQLDatabase'); $mysqlDatabaseClass = Environment::getEnv('PDO') ? 'MySQLPDODatabase' : 'MySQLDatabase';
$loader->setEnvironmentVariable('SS_DATABASE_USERNAME', 'root'); Environment::setEnv('SS_DATABASE_CLASS', $mysqlDatabaseClass);
$loader->setEnvironmentVariable('SS_DATABASE_PASSWORD', ''); Environment::setEnv('SS_DATABASE_USERNAME', 'root');
Environment::setEnv('SS_DATABASE_PASSWORD', '');
} }
$loader->setEnvironmentVariable('SS_DATABASE_SERVER', '127.0.0.1'); Environment::setEnv('SS_DATABASE_CHOOSE_NAME', 'true');
$loader->setEnvironmentVariable('SS_DATABASE_CHOOSE_NAME', true); Environment::setEnv('SS_DATABASE_SERVER', '127.0.0.1');
} }

View File

@ -0,0 +1,83 @@
<?php
namespace SilverStripe\Core\Tests;
use SilverStripe\Core\Environment;
use SilverStripe\Core\EnvironmentLoader;
use SilverStripe\Dev\SapphireTest;
class EnvironmentLoaderTest extends SapphireTest
{
public function testStripComments()
{
$loader = new EnvironmentLoader();
// No file
$this->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
);
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace SilverStripe\Core\Tests;
use SilverStripe\Core\Environment;
use SilverStripe\Dev\SapphireTest;
class EnvironmentTest extends SapphireTest
{
public function providerTestPutEnv()
{
return [
['_ENVTEST_BOOL=true', '_ENVTEST_BOOL', true],
['_ENVTEST_BOOL_QUOTED="true"', '_ENVTEST_BOOL_QUOTED', 'true'],
['_ENVTEST_NUMBER=1', '_ENVTEST_NUMBER', 1],
['_ENVTEST_NUMBER_QUOTED="1"', '_ENVTEST_NUMBER_QUOTED', '1'],
['_ENVTEST_NUMBER_SPECIAL="value=4"', '_ENVTEST_NUMBER_SPECIAL', 'value=4'],
['_ENVTEST_BLANK', '_ENVTEST_BLANK', false],
];
}
/**
* @dataProvider providerTestPutenv
*/
public function testPutEnv($put, $var, $value)
{
Environment::putEnv($put);
$this->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'));
}
}

View File

@ -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

View File

@ -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"

View File

@ -0,0 +1,4 @@
# Test file
TEST_ENV_INT_ONE="some var"
TEST_ENV_INT_TWO="${TEST_ENV_INT_ONE}"

View File

@ -2,9 +2,9 @@
namespace SilverStripe\Core\Tests\Manifest; namespace SilverStripe\Core\Tests\Manifest;
use Dotenv\Loader;
use SilverStripe\Config\Collections\MemoryConfigCollection; use SilverStripe\Config\Collections\MemoryConfigCollection;
use SilverStripe\Core\Config\CoreConfigFactory; use SilverStripe\Core\Config\CoreConfigFactory;
use SilverStripe\Core\Environment;
use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Injector\Injector;
use SilverStripe\Core\Kernel; use SilverStripe\Core\Kernel;
use SilverStripe\Core\Manifest\ModuleLoader; use SilverStripe\Core\Manifest\ModuleLoader;
@ -100,8 +100,7 @@ class ConfigManifestTest extends SapphireTest
public function testEnvVarSetRules() public function testEnvVarSetRules()
{ {
$loader = new Loader(null); Environment::setEnv('ENVVARSET_FOO', '1');
$loader->setEnvironmentVariable('ENVVARSET_FOO', 1);
$config = $this->getConfigFixtureValue('EnvVarSet'); $config = $this->getConfigFixtureValue('EnvVarSet');
$this->assertEquals( $this->assertEquals(
@ -137,9 +136,7 @@ class ConfigManifestTest extends SapphireTest
public function testEnvOrConstantMatchesValueRules() public function testEnvOrConstantMatchesValueRules()
{ {
$loader = new Loader(null); Environment::setEnv('CONSTANTMATCHESVALUE_FOO', 'Foo');
$loader->setEnvironmentVariable('CONSTANTMATCHESVALUE_FOO', 'Foo');
define('CONSTANTMATCHESVALUE_BAR', 'Bar'); define('CONSTANTMATCHESVALUE_BAR', 'Bar');
$config = $this->getConfigFixtureValue('EnvOrConstantMatchesValue'); $config = $this->getConfigFixtureValue('EnvOrConstantMatchesValue');
@ -194,9 +191,7 @@ class ConfigManifestTest extends SapphireTest
public function testMultipleRules() public function testMultipleRules()
{ {
$loader = new Loader(null); Environment::setEnv('MULTIPLERULES_ENVVARIABLESET', '1');
$loader->setEnvironmentVariable('MULTIPLERULES_ENVVARIABLESET', 1);
define('MULTIPLERULES_DEFINEDCONSTANT', 'defined'); define('MULTIPLERULES_DEFINEDCONSTANT', 'defined');
$config = $this->getConfigFixtureValue('MultipleRules'); $config = $this->getConfigFixtureValue('MultipleRules');

View File

@ -3,6 +3,7 @@
namespace SilverStripe\ORM\Tests; namespace SilverStripe\ORM\Tests;
use SilverStripe\Control\Director; use SilverStripe\Control\Director;
use SilverStripe\Core\Environment;
use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Injector\Injector;
use SilverStripe\Core\Kernel; use SilverStripe\Core\Kernel;
use SilverStripe\ORM\DB; use SilverStripe\ORM\DB;
@ -15,7 +16,7 @@ class DBTest extends SapphireTest
{ {
/** @var Kernel $kernel */ /** @var Kernel $kernel */
$kernel = Injector::inst()->get(Kernel::class); $kernel = Injector::inst()->get(Kernel::class);
$prefix = getenv('SS_DATABASE_PREFIX') ?: 'ss_'; $prefix = Environment::getEnv('SS_DATABASE_PREFIX') ?: 'ss_';
$kernel->setEnvironment(Kernel::DEV); $kernel->setEnvironment(Kernel::DEV);
$this->assertTrue(DB::valid_alternative_database_name($prefix.'tmpdb1234567')); $this->assertTrue(DB::valid_alternative_database_name($prefix.'tmpdb1234567'));