Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUG Replace phpdotenv with thread-safe replacement #7484

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "*",
Expand Down
31 changes: 19 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,26 +32,33 @@ 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

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
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 @@ -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
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\ORM\FieldType\DBField;
Expand Down Expand Up @@ -159,7 +160,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
71 changes: 68 additions & 3 deletions src/Core/Environment.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
* 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
{
Expand All @@ -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
*
Expand All @@ -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 ]);
}

/**
Expand All @@ -41,8 +53,14 @@ public static function getVariables()
*/
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'];
}
}

Expand Down Expand Up @@ -151,4 +169,51 @@ public static function getTimeLimitMax()
{
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;
}
}
47 changes: 47 additions & 0 deletions src/Core/EnvironmentLoader.php
Original file line number Diff line number Diff line change
@@ -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;
}
}
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
Loading