Skip to content

Commit

Permalink
BUG Replace phpdotenv with thread-safe replacement
Browse files Browse the repository at this point in the history
  • Loading branch information
Damian Mooyman committed Oct 16, 2017
1 parent e9e7bd6 commit 8e6bb43
Show file tree
Hide file tree
Showing 21 changed files with 294 additions and 95 deletions.
1 change: 0 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
"symfony/config": "^3.2",
"symfony/translation": "^2.8",
"symfony/yaml": "~3.2",
"vlucas/phpdotenv": "^2.4",
"php": ">=5.6.0",
"ext-intl": "*",
"ext-mbstring": "*",
Expand Down
27 changes: 15 additions & 12 deletions docs/en/00_Getting_Started/03_Environment_Management.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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::putenv()` method.

```php
use SilverStripe\Core\Environment;
Environment::putenv('API_KEY="AABBCCDDEEFF012345"');
```

## Including an extra `.env` file
Expand All @@ -47,11 +52,9 @@ Sometimes it may be useful to include an extra `.env` file - on a shared local d
database credentials could be the same. To do this, you can add this snippet to your `mysite/_config.php` file:

```php
try {
(new \Dotenv\Dotenv('/path/to/env/'))->load();
} catch (\Dotenv\Exception\InvalidPathException $e) {
// no file found
}
use SilverStripe\Core\Environment;
$env = BASE_PATH . '/mysite/.env';
Environment::putenvFromFile($env);
```

## Core environment variables
Expand Down
2 changes: 2 additions & 0 deletions docs/en/04_Changelogs/4.0.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,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
Expand Down
3 changes: 2 additions & 1 deletion src/Control/Email/Email.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
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\View\Requirements;
Expand Down Expand Up @@ -155,7 +156,7 @@ protected static function mergeConfiguredEmails($config, $env)
$normalised[$name] = null;
}
}
$extra = getenv($env);
$extra = Environment::getEnv($env);
if ($extra) {
$normalised[$extra] = null;
}
Expand Down
3 changes: 2 additions & 1 deletion src/Core/Cache/ManifestCacheFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
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
Expand Down Expand Up @@ -43,7 +44,7 @@ public function __construct(array $args = [], LoggerInterface $logger = null)
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);
}
Expand Down
6 changes: 4 additions & 2 deletions src/Core/Config/CoreConfigFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
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;
Expand Down Expand Up @@ -141,11 +142,12 @@ public function buildYamlTransformerForPath($dirs)

// 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;
};
Expand Down
26 changes: 13 additions & 13 deletions src/Core/CoreKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ public function getEnvironment()
}

// Check getenv
if ($env = getenv('SS_ENVIRONMENT_TYPE')) {
if ($env = Environment::getEnv('SS_ENVIRONMENT_TYPE')) {
return $env;
}

Expand Down Expand Up @@ -328,32 +328,32 @@ protected function getDatabaseConfig()
{
/** @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;
}
Expand All @@ -368,7 +368,7 @@ protected function getDatabaseConfig()
*/
protected function getDatabasePrefix()
{
return getenv('SS_DATABASE_PREFIX') ?: '';
return Environment::getEnv('SS_DATABASE_PREFIX') ?: '';
}

/**
Expand All @@ -392,14 +392,14 @@ protected function getDatabaseName()
}

// 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
Expand Down Expand Up @@ -529,7 +529,7 @@ protected function bootErrorHandling()
$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) {
Expand Down
110 changes: 107 additions & 3 deletions src/Core/Environment.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,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
*
Expand All @@ -31,7 +38,9 @@ class Environment
public static function getVariables()
{
// Suppress return by-ref
return array_merge($GLOBALS, []);
return array_merge($GLOBALS, [
'_ENV' => array_merge($_ENV, static::$env),
]);
}

/**
Expand All @@ -41,8 +50,14 @@ public static function getVariables()
*/
public static function setVariables(array $vars)
{
foreach ($vars as $key => $value) {
$GLOBALS[$key] = $value;
foreach ($vars as $varName => $varValue) {
$GLOBALS[$varName] = $varValue;
}
// Safely reload environment variables
if (array_key_exists('_ENV', $vars)) {
foreach ($vars['_ENV'] as $name => $value) {
static::setEnv($name, $value);
}
}
}

Expand Down Expand Up @@ -151,4 +166,93 @@ public static function getTimeLimitMax()
{
return static::$timeLimitMax;
}

/**
* Get value of environment variable
*
* @param string $name
* @return mixed Value of the environment variable, or null 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);
}
}

/**
* 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 List of values parsed as an associative array
*/
public static function putEnvFromFile($path, $overload = false)
{
// Parse and cleanup content
$contents = implode(
PHP_EOL,
array_filter(array_map(
function ($line) {
// Strip all # comments, including start or end of string, but not in quoted strings
// '#comment', 'var=thing#comment', or 'var="not#comment"#comment
return preg_replace('/((^[^"]*)|(^[^"]+("[^"]*){2}))#.*$/', '$1', $line);
},
explode(PHP_EOL, file_get_contents($path))
))
);

// Pass to parse_ini_string
$variables = parse_ini_string($contents) ?: [];
foreach ($variables as $name => $value) {
// Conditionally prevent overloading
if ($overload || static::getEnv($name) === false) {
static::setEnv($name, $value);
}
}
return $variables;
}

/**
* Set environment variable via $name / $value pair
*
* @param string $name
* @param string $value
*/
public static function setEnv($name, $value)
{
// If PHP is running as an Apache module and an existing
// Apache environment variable exists, overwrite it
if (function_exists('apache_getenv') && function_exists('apache_setenv') && apache_getenv($name)) {
apache_setenv($name, $value);
}

putenv("{$name}={$value}"); // Don't quote value for putenv()
$_ENV[$name] = $value;
$_SERVER[$name] = $value;
static::$env[$name] = $value;
}
}
6 changes: 4 additions & 2 deletions src/Core/Injector/Injector.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use ReflectionProperty;
use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Environment;
use SilverStripe\Dev\Deprecation;

/**
Expand Down Expand Up @@ -523,8 +524,9 @@ public function convertServiceProperty($value)

// Evaluate constants surrounded by back ticks
if (preg_match('/^`(?<name>[^`]+)`$/', $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 {
Expand Down
6 changes: 3 additions & 3 deletions src/Core/TempFolder.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ public static function getTempFolder($base)
*/
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());
Expand Down
Loading

0 comments on commit 8e6bb43

Please sign in to comment.