Skip to content

Commit 55fe66c

Browse files
committed
Load environment variables from live process environment if thread-safe
1 parent 6500f7e commit 55fe66c

2 files changed

Lines changed: 70 additions & 3 deletions

File tree

src/Container.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ class Container
1313
/** @var array<string,object|callable():(object|scalar|null)|scalar|null>|ContainerInterface */
1414
private $container;
1515

16+
/** @var bool */
17+
private $useProcessEnv;
18+
1619
/** @param array<string,callable():(object|scalar|null) | object | scalar | null>|ContainerInterface $loader */
1720
public function __construct($loader = [])
1821
{
@@ -32,6 +35,9 @@ public function __construct($loader = [])
3235
}
3336
}
3437
$this->container = $loader;
38+
39+
// prefer reading environment from `$_ENV` and `$_SERVER`, only fall back to `getenv()` in thread-safe environments
40+
$this->useProcessEnv = \ZEND_THREAD_SAFE === false || \in_array(\PHP_SAPI, ['cli', 'cli-server', 'cgi-fcgi', 'fpm-fcgi'], true);
3541
}
3642

3743
/** @return mixed */
@@ -296,7 +302,7 @@ private function loadParameter(\ReflectionParameter $parameter, int $depth, bool
296302

297303
private function hasVariable(string $name): bool
298304
{
299-
return (\is_array($this->container) && \array_key_exists($name, $this->container)) || (isset($_ENV[$name]) || (\is_string($_SERVER[$name] ?? null)) && \preg_match('/^[A-Z][A-Z0-9_]+$/', $name));
305+
return (\is_array($this->container) && \array_key_exists($name, $this->container)) || (isset($_ENV[$name]) || (\is_string($_SERVER[$name] ?? null) || ($this->useProcessEnv && \getenv($name) !== false)) && \preg_match('/^[A-Z][A-Z0-9_]+$/', $name));
300306
}
301307

302308
/**
@@ -332,9 +338,12 @@ private function loadVariable(string $name, string $type, bool $nullable, int $d
332338
} elseif (isset($_ENV[$name])) {
333339
assert(\is_string($_ENV[$name]));
334340
$value = $_ENV[$name];
335-
} else {
336-
assert(\is_string($_SERVER[$name] ?? null));
341+
} elseif (isset($_SERVER[$name])) {
342+
assert(\is_string($_SERVER[$name]));
337343
$value = $_SERVER[$name];
344+
} else {
345+
$value = \getenv($name);
346+
assert($this->useProcessEnv && $value !== false);
338347
}
339348

340349
assert(\is_object($value) || \is_scalar($value) || $value === null);

tests/ContainerTest.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2061,6 +2061,17 @@ public function testGetEnvReturnsStringFromGlobalServerIfNotSetInMap(): void
20612061
$this->assertEquals('bar', $ret);
20622062
}
20632063

2064+
public function testGetEnvReturnsStringFromProcessEnvIfNotSetInMap(): void
2065+
{
2066+
$container = new Container([]);
2067+
2068+
putenv('X_FOO=bar');
2069+
$ret = $container->getEnv('X_FOO');
2070+
putenv('X_FOO');
2071+
2072+
$this->assertEquals('bar', $ret);
2073+
}
2074+
20642075
public function testGetEnvReturnsStringFromGlobalEnvBeforeServerIfNotSetInMap(): void
20652076
{
20662077
$container = new Container([]);
@@ -2073,6 +2084,19 @@ public function testGetEnvReturnsStringFromGlobalEnvBeforeServerIfNotSetInMap():
20732084
$this->assertEquals('foo', $ret);
20742085
}
20752086

2087+
public function testGetEnvReturnsStringFromGlobalEnvBeforeProcessEnvIfNotSetInMap(): void
2088+
{
2089+
$container = new Container([]);
2090+
2091+
$_ENV['X_FOO'] = 'foo';
2092+
putenv('X_FOO=bar');
2093+
$ret = $container->getEnv('X_FOO');
2094+
unset($_ENV['X_FOO']);
2095+
putenv('X_FOO');
2096+
2097+
$this->assertEquals('foo', $ret);
2098+
}
2099+
20762100
public function testGetEnvReturnsStringFromPsrContainer(): void
20772101
{
20782102
$psr = $this->createMock(ContainerInterface::class);
@@ -2097,6 +2121,22 @@ public function testGetEnvReturnsNullIfPsrContainerHasNoEntry(): void
20972121
$this->assertNull($container->getEnv('X_FOO'));
20982122
}
20992123

2124+
public function testGetEnvReturnsStringFromProcessEnvIfPsrContainerHasNoEntry(): void
2125+
{
2126+
$psr = $this->createMock(ContainerInterface::class);
2127+
$psr->expects($this->atLeastOnce())->method('has')->with('X_FOO')->willReturn(false);
2128+
$psr->expects($this->never())->method('get');
2129+
2130+
assert($psr instanceof ContainerInterface);
2131+
$container = new Container($psr);
2132+
2133+
putenv('X_FOO=bar');
2134+
$ret = $container->getEnv('X_FOO');
2135+
putenv('X_FOO');
2136+
2137+
$this->assertEquals('bar', $ret);
2138+
}
2139+
21002140
public function testGetEnvReturnsStringFromGlobalEnvIfPsrContainerHasNoEntry(): void
21012141
{
21022142
$psr = $this->createMock(ContainerInterface::class);
@@ -2146,6 +2186,24 @@ public function testGetEnvReturnsStringFromGlobalEnvBeforeServerIfPsrContainerHa
21462186
$this->assertEquals('foo', $ret);
21472187
}
21482188

2189+
public function testGetEnvReturnsStringFromGlobalEnvBeforeProcessEnvIfPsrContainerHasNoEntry(): void
2190+
{
2191+
$psr = $this->createMock(ContainerInterface::class);
2192+
$psr->expects($this->atLeastOnce())->method('has')->with('X_FOO')->willReturn(false);
2193+
$psr->expects($this->never())->method('get');
2194+
2195+
assert($psr instanceof ContainerInterface);
2196+
$container = new Container($psr);
2197+
2198+
$_ENV['X_FOO'] = 'foo';
2199+
putenv('X_FOO=bar');
2200+
$ret = $container->getEnv('X_FOO');
2201+
unset($_ENV['X_FOO']);
2202+
putenv('X_FOO');
2203+
2204+
$this->assertEquals('foo', $ret);
2205+
}
2206+
21492207
public function testGetEnvThrowsIfMapContainsInvalidType(): void
21502208
{
21512209
$container = new Container([

0 commit comments

Comments
 (0)