From fbacb9767ef93d95bbeaf8a940d0f18824177ab7 Mon Sep 17 00:00:00 2001 From: Dave Reid Date: Thu, 17 Jul 2025 09:32:08 -0500 Subject: [PATCH 1/7] Added method for processing JSON file to environent variables. --- src/Environment.php | 50 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/src/Environment.php b/src/Environment.php index eaf291a..95e6727 100644 --- a/src/Environment.php +++ b/src/Environment.php @@ -23,6 +23,8 @@ class Environment { + protected static array $cache = []; + /** * The currently supported environment classes. */ @@ -87,11 +89,24 @@ public static function __callStatic(string $name, array $arguments) */ public static function get(string $name): string|bool { - static $cache = []; - if (!array_key_exists($name, $cache)) { - $cache[$name] = getenv($name); + if (!array_key_exists($name, static::$cache)) { + static::$cache[$name] = getenv($name); } - return $cache[$name]; + return static::$cache[$name]; + } + + /** + * Set an environment variable. + * + * @param string $name + * The name of the environment variable to set. + * @param string $value + * The value of the environment variable to set. + */ + public static function put(string $name, string $value): void + { + putenv("$name=$value"); + static::$cache[$name] = $value; } /** @@ -180,4 +195,31 @@ public static function getComposerLockFilename(): string $filename = static::getComposerFilename(); return pathinfo($filename, PATHINFO_FILENAME) . '.lock'; } + + /** + * Process a file that contains environment variables to the current environment. + * + * @param string $file + * The path to the JSON file. + */ + public static function processEnvironmentFileJson(string $file): void + { + if (is_file($file)) { + $contents = @file_get_contents($file); + if ($contents === FALSE) { + throw new \RuntimeException("Unable to read environment file $file."); + } + + $values = json_decode($contents, TRUE, 512, JSON_THROW_ON_ERROR); + + // We only support key value secrets that are strings. + $values = array_filter($values, function ($value, $key) { + return is_string($key) && is_string($value); + }, ARRAY_FILTER_USE_BOTH); + + foreach ($values as $name => $value) { + Environment::put($name, $value); + } + } + } } From 3b171f40673b6d9a9054049d6ae59fdb0f5d5f34 Mon Sep 17 00:00:00 2001 From: Dave Reid Date: Fri, 29 Aug 2025 10:19:42 -0500 Subject: [PATCH 2/7] Update Environment.php --- src/Environment.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Environment.php b/src/Environment.php index 7faff9a..344284e 100644 --- a/src/Environment.php +++ b/src/Environment.php @@ -195,7 +195,7 @@ public static function getComposerLockFilename(): string $filename = static::getComposerFilename(); return pathinfo($filename, PATHINFO_FILENAME) . '.lock'; } - + /** * Get the current host name. * From 2fdc9a9f5d9a1dfc36a18895b098f290d7d86a22 Mon Sep 17 00:00:00 2001 From: Dave Reid Date: Wed, 2 Sep 2026 22:34:32 -0500 Subject: [PATCH 3/7] Change Environment::put to Environment::set --- src/Environment.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Environment.php b/src/Environment.php index 6d983bd..debaa1e 100644 --- a/src/Environment.php +++ b/src/Environment.php @@ -319,7 +319,7 @@ public static function processEnvironmentFileJson(string $file): void }, ARRAY_FILTER_USE_BOTH); foreach ($values as $name => $value) { - Environment::put($name, $value); + Environment::set($name, $value); } } } From 8d4707d90de1b936f63bde03b2696d05f4cd5424 Mon Sep 17 00:00:00 2001 From: Dave Reid Date: Wed, 2 Sep 2026 22:41:01 -0500 Subject: [PATCH 4/7] Rework processEnvironmentFileJson() and limit depth to 1. --- src/Environment.php | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/Environment.php b/src/Environment.php index debaa1e..59a9feb 100644 --- a/src/Environment.php +++ b/src/Environment.php @@ -302,25 +302,32 @@ public static function enforceDomain(string $domain): void * * @param string $file * The path to the JSON file. + * + * @throws \RuntimeException + * If the file does not exist or is not readable. + * @throws \JsonException + * If the file contents is not valid JSON. */ public static function processEnvironmentFileJson(string $file): void { - if (is_file($file)) { - $contents = @file_get_contents($file); - if ($contents === FALSE) { - throw new \RuntimeException("Unable to read environment file $file."); - } + if (!is_file($file) || !is_readable($file)) { + throw new \RuntimeException("Environment file $file does not exist or is not readable."); + } - $values = json_decode($contents, TRUE, 512, JSON_THROW_ON_ERROR); + $contents = @file_get_contents($file); + if ($contents === FALSE) { + throw new \RuntimeException("Environment file $file was unable to be read."); + } - // We only support key value secrets that are strings. - $values = array_filter($values, function ($value, $key) { - return is_string($key) && is_string($value); - }, ARRAY_FILTER_USE_BOTH); + $values = json_decode($contents, TRUE, 1, JSON_THROW_ON_ERROR); - foreach ($values as $name => $value) { - Environment::set($name, $value); - } + // We only support key value secrets that are strings. + $values = array_filter($values, static function ($value, $key) { + return is_string($key) && is_string($value); + }, ARRAY_FILTER_USE_BOTH); + + foreach ($values as $name => $value) { + static::set($name, $value); } } } From 3691744c0b2ad7073d0cf4d5f5bb118c9d8146aa Mon Sep 17 00:00:00 2001 From: Dave Reid Date: Wed, 2 Sep 2026 22:44:13 -0500 Subject: [PATCH 5/7] Fix merge conflicts. --- src/Environment.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Environment.php b/src/Environment.php index 59a9feb..124a85e 100644 --- a/src/Environment.php +++ b/src/Environment.php @@ -23,8 +23,6 @@ class Environment { - protected static array $cache = []; - /** * Static cache of environment variables. * From 582f05e44c0a26a078ede66216898e3081f0db72 Mon Sep 17 00:00:00 2001 From: Dave Reid Date: Wed, 2 Sep 2026 23:00:00 -0500 Subject: [PATCH 6/7] Add test coverage. --- src/Environment.php | 6 +++-- tests/fixtures/invalid.json | 1 + tests/fixtures/nested.json | 5 ++++ tests/fixtures/valid.json | 5 ++++ tests/src/EnvironmentTest.php | 50 +++++++++++++++++++++++++++++++++++ 5 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 tests/fixtures/invalid.json create mode 100644 tests/fixtures/nested.json create mode 100644 tests/fixtures/valid.json diff --git a/src/Environment.php b/src/Environment.php index 124a85e..49ff8af 100644 --- a/src/Environment.php +++ b/src/Environment.php @@ -313,11 +313,13 @@ public static function processEnvironmentFileJson(string $file): void } $contents = @file_get_contents($file); - if ($contents === FALSE) { + if ($contents === false) { throw new \RuntimeException("Environment file $file was unable to be read."); } - $values = json_decode($contents, TRUE, 1, JSON_THROW_ON_ERROR); + // The depth is set intentionally low because we do not expect deeply + // nested JSON. + $values = json_decode($contents, true, 2, JSON_THROW_ON_ERROR); // We only support key value secrets that are strings. $values = array_filter($values, static function ($value, $key) { diff --git a/tests/fixtures/invalid.json b/tests/fixtures/invalid.json new file mode 100644 index 0000000..6920164 --- /dev/null +++ b/tests/fixtures/invalid.json @@ -0,0 +1 @@ +this is an invalid json file diff --git a/tests/fixtures/nested.json b/tests/fixtures/nested.json new file mode 100644 index 0000000..d56dd09 --- /dev/null +++ b/tests/fixtures/nested.json @@ -0,0 +1,5 @@ +{ + "first_level": { + "second_level": "second_level_value" + } +} diff --git a/tests/fixtures/valid.json b/tests/fixtures/valid.json new file mode 100644 index 0000000..5c10941 --- /dev/null +++ b/tests/fixtures/valid.json @@ -0,0 +1,5 @@ +{ + "JSON_UPPER_KEY": "upper", + "json_lower_key": "lower", + "invalid_value": 1 +} diff --git a/tests/src/EnvironmentTest.php b/tests/src/EnvironmentTest.php index 05835e7..31599c9 100644 --- a/tests/src/EnvironmentTest.php +++ b/tests/src/EnvironmentTest.php @@ -659,4 +659,54 @@ public static function providerEnvironment(): array ], ]; } + + /** + * Test processing JSON files into environment variables. + * + * @dataProvider providerProcessEnvironmentFileJson + */ + #[DataProvider('providerProcessEnvironmentFileJson')] + public function testProcessEnvironmentFileJson(string $file, array|string $expectedValues): void + { + if (is_string($expectedValues)) { + $this->expectException($expectedValues); + } + Environment::processEnvironmentFileJson($file); + + foreach ($expectedValues as $name => $value) { + $this->assertSame($value, getenv($name)); + $this->assertSame($value, Environment::get($name)); + } + } + + /** + * Data provider for ::testProcessEnvironmentFileJson. + */ + public static function providerProcessEnvironmentFileJson(): array + { + $fixtureDirectory = dirname(__DIR__) . '/fixtures/'; + return [ + 'valid-file' => [ + $fixtureDirectory . 'valid.json', + [ + 'JSON_UPPER_KEY' => 'upper', + 'json_lower_key' => 'lower', + 'JSON_LOWER_KEY' => false, + 'invalid_value' => false, + ], + ], + 'invalid-file' => [ + $fixtureDirectory . 'invalid.json', + \JsonException::class, + ], + 'nested-fail' => [ + $fixtureDirectory . 'nested.json', + \JsonException::class, + ], + 'missing-file' => [ + $fixtureDirectory . 'missing.json', + \RuntimeException::class, + ], + ]; + } } From 44e1fd02e4855507b6aa505f6c9f5d13dc0fa622 Mon Sep 17 00:00:00 2001 From: Dave Reid Date: Thu, 3 Sep 2026 11:55:31 -0500 Subject: [PATCH 7/7] Ignore invalid.json from jsonlint. --- grumphp.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/grumphp.yml b/grumphp.yml index f6bc461..2340bf0 100644 --- a/grumphp.yml +++ b/grumphp.yml @@ -6,7 +6,9 @@ grumphp: tasks: phplint: ~ yamllint: ~ - jsonlint: ~ + jsonlint: + ignore_patterns: + - /^tests\/fixtures\/invalid\.json/ composer: ~ composer_normalize: ~ phpcs: ~