aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-06-25 03:20:08 +0900
committernsfisis <nsfisis@gmail.com>2026-06-25 03:33:09 +0900
commitc65cab710bb3d79db20862c9361d4dbbac8ac5d7 (patch)
treeb4d192237bbb8dbaa80166f1ffa24703e57243f4 /crates/shirabe
parentf73e9ab05f8d94937d2437919199d9d9681c8cde (diff)
downloadphp-shirabe-c65cab710bb3d79db20862c9361d4dbbac8ac5d7.tar.gz
php-shirabe-c65cab710bb3d79db20862c9361d4dbbac8ac5d7.tar.zst
php-shirabe-c65cab710bb3d79db20862c9361d4dbbac8ac5d7.zip
feat(php-shim): model $_ENV/$_SERVER as OsString snapshots
Rework the environment shim around getenv/putenv on the real environment and $_ENV/$_SERVER as startup snapshots, all over OsString. Migrate every caller off the old server()/server_argv() helpers and force the snapshots in main() before any putenv() runs. Document the porting rules in docs/dev/env-vars-porting.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe')
-rw-r--r--crates/shirabe/src/autoload/autoload_generator.rs7
-rw-r--r--crates/shirabe/src/command/init_command.rs52
-rw-r--r--crates/shirabe/src/console/application.rs28
-rw-r--r--crates/shirabe/src/main.rs7
-rw-r--r--crates/shirabe/src/util/platform.rs32
-rw-r--r--crates/shirabe/tests/command/init_command_test.rs14
-rw-r--r--crates/shirabe/tests/config_test.rs6
-rw-r--r--crates/shirabe/tests/util/ini_helper_test.rs10
8 files changed, 107 insertions, 49 deletions
diff --git a/crates/shirabe/src/autoload/autoload_generator.rs b/crates/shirabe/src/autoload/autoload_generator.rs
index 7b02841..ded7cee 100644
--- a/crates/shirabe/src/autoload/autoload_generator.rs
+++ b/crates/shirabe/src/autoload/autoload_generator.rs
@@ -148,7 +148,12 @@ impl AutoloadGenerator {
if self.run_scripts {
// set COMPOSER_DEV_MODE in case not set yet so it is available in the dump-autoload event listeners
- if shirabe_php_shim::server_get("COMPOSER_DEV_MODE").is_none() {
+ if shirabe_php_shim::PHP_SERVER
+ .lock()
+ .unwrap()
+ .get("COMPOSER_DEV_MODE")
+ .is_none()
+ {
Platform::put_env(
"COMPOSER_DEV_MODE",
if self.dev_mode.unwrap_or(false) {
diff --git a/crates/shirabe/src/command/init_command.rs b/crates/shirabe/src/command/init_command.rs
index 9683b89..557ebca 100644
--- a/crates/shirabe/src/command/init_command.rs
+++ b/crates/shirabe/src/command/init_command.rs
@@ -10,11 +10,11 @@ use shirabe_external_packages::symfony::console::input::ArrayInput;
use shirabe_external_packages::symfony::console::input::InputInterface;
use shirabe_external_packages::symfony::console::output::OutputInterface;
use shirabe_php_shim::{
- FILE_IGNORE_NEW_LINES, InvalidArgumentException, PHP_EOL, PhpMixed, array_filter, array_flip,
- array_flip_strings, array_intersect_key, array_keys, array_map, basename, empty, explode, file,
- file_exists, file_get_contents, file_put_contents, function_exists, get_current_user, implode,
- is_dir, is_string, preg_quote, realpath, server_get, sprintf, str_replace, strpos, strtolower,
- trim, ucwords,
+ FILE_IGNORE_NEW_LINES, InvalidArgumentException, PHP_EOL, PHP_SERVER, PhpMixed, array_filter,
+ array_flip, array_flip_strings, array_intersect_key, array_keys, array_map, basename, empty,
+ explode, file, file_exists, file_get_contents, file_put_contents, function_exists,
+ get_current_user, implode, is_dir, is_string, preg_quote, realpath, sprintf, str_replace,
+ strpos, strtolower, trim, ucwords,
};
use shirabe_spdx_licenses::SpdxLicenses;
use std::cell::RefCell;
@@ -723,7 +723,11 @@ impl Command for InitCommand {
.as_string()
.map(|s| s.to_string());
if license.is_none() {
- let default_license = server_get("COMPOSER_DEFAULT_LICENSE");
+ let default_license = PHP_SERVER
+ .lock()
+ .unwrap()
+ .get("COMPOSER_DEFAULT_LICENSE")
+ .map(|value| value.to_string_lossy().into_owned());
if !empty(
&default_license
.clone()
@@ -1163,7 +1167,21 @@ impl InitCommand {
let name = self.sanitize_package_name_component(&name);
let mut vendor = name.clone();
- let composer_default_vendor = server_get("COMPOSER_DEFAULT_VENDOR");
+ let composer_default_vendor = PHP_SERVER
+ .lock()
+ .unwrap()
+ .get("COMPOSER_DEFAULT_VENDOR")
+ .map(|value| value.to_string_lossy().into_owned());
+ let server_username = PHP_SERVER
+ .lock()
+ .unwrap()
+ .get("USERNAME")
+ .map(|value| value.to_string_lossy().into_owned());
+ let server_user = PHP_SERVER
+ .lock()
+ .unwrap()
+ .get("USER")
+ .map(|value| value.to_string_lossy().into_owned());
if !empty(
&composer_default_vendor
.clone()
@@ -1174,19 +1192,19 @@ impl InitCommand {
} else if git.contains_key("github.user") {
vendor = git.get("github.user").cloned().unwrap_or_default();
} else if !empty(
- &server_get("USERNAME")
+ &server_username
.clone()
.map(PhpMixed::String)
.unwrap_or(PhpMixed::Null),
) {
- vendor = server_get("USERNAME").unwrap_or_default();
+ vendor = server_username.unwrap_or_default();
} else if !empty(
- &server_get("USER")
+ &server_user
.clone()
.map(PhpMixed::String)
.unwrap_or(PhpMixed::Null),
) {
- vendor = server_get("USER").unwrap_or_default();
+ vendor = server_user.unwrap_or_default();
} else if !get_current_user().is_empty() {
vendor = get_current_user();
}
@@ -1200,7 +1218,11 @@ impl InitCommand {
let git = self.get_git_config();
let mut author_name: Option<String> = None;
- let composer_default_author = server_get("COMPOSER_DEFAULT_AUTHOR");
+ let composer_default_author = PHP_SERVER
+ .lock()
+ .unwrap()
+ .get("COMPOSER_DEFAULT_AUTHOR")
+ .map(|value| value.to_string_lossy().into_owned());
if !empty(
&composer_default_author
.clone()
@@ -1213,7 +1235,11 @@ impl InitCommand {
}
let mut author_email: Option<String> = None;
- let composer_default_email = server_get("COMPOSER_DEFAULT_EMAIL");
+ let composer_default_email = PHP_SERVER
+ .lock()
+ .unwrap()
+ .get("COMPOSER_DEFAULT_EMAIL")
+ .map(|value| value.to_string_lossy().into_owned());
if !empty(
&composer_default_email
.clone()
diff --git a/crates/shirabe/src/console/application.rs b/crates/shirabe/src/console/application.rs
index 251fc67..319e8dd 100644
--- a/crates/shirabe/src/console/application.rs
+++ b/crates/shirabe/src/console/application.rs
@@ -1402,7 +1402,10 @@ impl Application {
input.borrow_mut().set_interactive(false);
}
- let mut shell_verbosity = shirabe_php_shim::getenv("SHELL_VERBOSITY").unwrap_or_default();
+ let shell_verbosity = shirabe_php_shim::getenv("SHELL_VERBOSITY")
+ .unwrap_or_default()
+ .to_string_lossy()
+ .into_owned();
let shell_verbosity_int: i64 = shell_verbosity.parse().unwrap_or(0);
let mut shell_verbosity: i64 = shell_verbosity_int;
match shell_verbosity_int {
@@ -1500,10 +1503,16 @@ impl Application {
}
if shirabe_php_shim::function_exists("putenv") {
- shirabe_php_shim::putenv(&format!("SHELL_VERBOSITY={}", shell_verbosity));
+ unsafe { shirabe_php_shim::putenv("SHELL_VERBOSITY", shell_verbosity.to_string()) };
}
- shirabe_php_shim::env_set("SHELL_VERBOSITY", shell_verbosity.to_string());
- shirabe_php_shim::server_set("SHELL_VERBOSITY", shell_verbosity.to_string());
+ shirabe_php_shim::PHP_ENV
+ .lock()
+ .unwrap()
+ .put("SHELL_VERBOSITY".into(), shell_verbosity.to_string().into());
+ shirabe_php_shim::PHP_SERVER
+ .lock()
+ .unwrap()
+ .put("SHELL_VERBOSITY".into(), shell_verbosity.to_string().into());
let _ = &mut shell_verbosity;
@@ -2285,7 +2294,12 @@ impl ApplicationHandle {
{
io.write_error(&format!(
"<warning>Warning: This development build of Composer is over 60 days old. It is recommended to update it by running \"{} self-update\" to get the latest version.</warning>",
- shirabe_php_shim::server_get("PHP_SELF").unwrap_or_default(),
+ shirabe_php_shim::PHP_SERVER
+ .lock()
+ .unwrap()
+ .get("PHP_SELF")
+ .unwrap_or_default()
+ .to_string_lossy(),
));
}
@@ -2598,8 +2612,8 @@ impl ApplicationHandle {
let app = application.borrow();
(app.terminal.get_height(), app.terminal.get_width())
};
- shirabe_php_shim::putenv(&format!("LINES={}", height));
- shirabe_php_shim::putenv(&format!("COLUMNS={}", width));
+ unsafe { shirabe_php_shim::putenv("LINES", height.to_string()) };
+ unsafe { shirabe_php_shim::putenv("COLUMNS", width.to_string()) };
}
let input: std::rc::Rc<std::cell::RefCell<dyn InputInterface>> = match input {
diff --git a/crates/shirabe/src/main.rs b/crates/shirabe/src/main.rs
index 3e3623d..538d89d 100644
--- a/crates/shirabe/src/main.rs
+++ b/crates/shirabe/src/main.rs
@@ -1,8 +1,13 @@
//! ref: composer/bin/composer
-use shirabe_php_shim::run_shutdown_functions;
+use shirabe_php_shim::{PHP_ENV, PHP_SERVER, run_shutdown_functions};
fn main() {
+ // Take the $_ENV / $_SERVER snapshots before any putenv() mutates the real environment.
+ // See `docs/dev/env-vars-porting.md` for details.
+ std::sync::LazyLock::force(&PHP_ENV);
+ std::sync::LazyLock::force(&PHP_SERVER);
+
let result = shirabe::run(std::env::args().collect());
run_shutdown_functions();
let mut exit_code = match result {
diff --git a/crates/shirabe/src/util/platform.rs b/crates/shirabe/src/util/platform.rs
index 72fe5f9..889da6e 100644
--- a/crates/shirabe/src/util/platform.rs
+++ b/crates/shirabe/src/util/platform.rs
@@ -5,11 +5,11 @@ use std::sync::Mutex;
use anyhow::Result;
use shirabe_external_packages::composer::pcre::Preg;
use shirabe_php_shim::{
- PhpMixed, PhpResource, RuntimeException, defined, env_contains_key, env_get, env_set,
- env_unset, file_exists, file_get_contents, fstat, function_exists, getcwd, getenv, in_array,
- ini_get, is_array, is_readable, mb_strlen, php_os_family, posix_geteuid, posix_getpwuid,
- posix_getuid, posix_isatty, putenv, realpath, server_contains_key, server_get, server_set,
- server_unset, stream_isatty, stripos, strlen, strtoupper, substr, usleep,
+ PHP_ENV, PHP_SERVER, PhpMixed, PhpResource, RuntimeException, defined, file_exists,
+ file_get_contents, fstat, function_exists, getcwd, getenv, in_array, ini_get, is_array,
+ is_readable, mb_strlen, php_os_family, posix_geteuid, posix_getpwuid, posix_getuid,
+ posix_isatty, putenv, putenv_clear, realpath, stream_isatty, stripos, strlen, strtoupper,
+ substr, usleep,
};
use crate::util::ProcessExecutor;
@@ -66,28 +66,28 @@ impl Platform {
///
/// @return string|false
pub fn get_env(name: &str) -> Option<String> {
- if server_contains_key(name) {
- return Some(server_get(name).unwrap_or_default());
+ if let Some(value) = PHP_SERVER.lock().unwrap().get(name) {
+ return Some(value.to_string_lossy().into_owned());
}
- if env_contains_key(name) {
- return Some(env_get(name).unwrap_or_default());
+ if let Some(value) = PHP_ENV.lock().unwrap().get(name) {
+ return Some(value.to_string_lossy().into_owned());
}
- getenv(name)
+ getenv(name).map(|value| value.to_string_lossy().into_owned())
}
/// putenv() equivalent but updates the runtime global variables too
pub fn put_env(name: &str, value: &str) {
- putenv(&format!("{}={}", name, value));
- server_set(name, value.to_string());
- env_set(name, value.to_string());
+ unsafe { putenv(name, value) };
+ PHP_SERVER.lock().unwrap().put(name.into(), value.into());
+ PHP_ENV.lock().unwrap().put(name.into(), value.into());
}
/// putenv('X') equivalent but updates the runtime global variables too
pub fn clear_env(name: &str) {
- putenv(name);
- server_unset(name);
- env_unset(name);
+ unsafe { putenv_clear(name) };
+ PHP_SERVER.lock().unwrap().clear(name);
+ PHP_ENV.lock().unwrap().clear(name);
}
/// Parses tildes and environment variables in paths.
diff --git a/crates/shirabe/tests/command/init_command_test.rs b/crates/shirabe/tests/command/init_command_test.rs
index 1c2fca6..0c38bf4 100644
--- a/crates/shirabe/tests/command/init_command_test.rs
+++ b/crates/shirabe/tests/command/init_command_test.rs
@@ -4,12 +4,13 @@ use crate::test_case::{RunOptions, get_application_tester, init_temp_composer};
use serial_test::serial;
use shirabe::command::init_command::InitCommand;
use shirabe::json::JsonFile;
-use shirabe_php_shim::{PhpMixed, server_set, server_unset};
+use shirabe_php_shim::{PHP_SERVER, PhpMixed};
use tempfile::TempDir;
fn set_up() {
- server_set("COMPOSER_DEFAULT_AUTHOR", "John Smith".to_string());
- server_set("COMPOSER_DEFAULT_EMAIL", "john@example.com".to_string());
+ let mut server = PHP_SERVER.lock().unwrap();
+ server.put("COMPOSER_DEFAULT_AUTHOR".into(), "John Smith".into());
+ server.put("COMPOSER_DEFAULT_EMAIL".into(), "john@example.com".into());
}
/// const DEFAULT_AUTHORS in PHP.
@@ -497,12 +498,15 @@ fn test_run_guess_name_from_dir_sanitizes_dir() {
std::fs::create_dir(dir_name).unwrap();
std::env::set_current_dir(dir_name).unwrap();
- server_set("COMPOSER_DEFAULT_VENDOR", ".vendorName".to_string());
+ PHP_SERVER
+ .lock()
+ .unwrap()
+ .put("COMPOSER_DEFAULT_VENDOR".into(), ".vendorName".into());
let mut app_tester = get_application_tester();
let result = app_tester.run(non_interactive_input(vec![]), RunOptions::default());
- server_unset("COMPOSER_DEFAULT_VENDOR");
+ PHP_SERVER.lock().unwrap().clear("COMPOSER_DEFAULT_VENDOR");
result.unwrap();
assert_eq!(0, app_tester.get_status_code());
diff --git a/crates/shirabe/tests/config_test.rs b/crates/shirabe/tests/config_test.rs
index 9c2f80b..df29281 100644
--- a/crates/shirabe/tests/config_test.rs
+++ b/crates/shirabe/tests/config_test.rs
@@ -1,6 +1,7 @@
//! ref: composer/tests/Composer/Test/ConfigTest.php
use indexmap::IndexMap;
+use serial_test::serial;
use shirabe::advisory::Auditor;
use shirabe::config::Config;
use shirabe::util::Platform;
@@ -509,6 +510,7 @@ fn test_disable_tls_can_be_overridden() {
}
#[test]
+#[serial]
fn test_process_timeout() {
Platform::put_env("COMPOSER_PROCESS_TIMEOUT", "0");
let config = Config::new(true, None);
@@ -520,6 +522,7 @@ fn test_process_timeout() {
#[ignore]
#[test]
+#[serial]
fn test_htaccess_protect() {
Platform::put_env("COMPOSER_HTACCESS_PROTECT", "0");
let config = Config::new(true, None);
@@ -530,6 +533,7 @@ fn test_htaccess_protect() {
}
#[test]
+#[serial]
fn test_get_source_of_value() {
Platform::clear_env("COMPOSER_PROCESS_TIMEOUT");
@@ -552,6 +556,7 @@ fn test_get_source_of_value() {
}
#[test]
+#[serial]
fn test_get_source_of_value_env_variables() {
Platform::put_env("COMPOSER_HTACCESS_PROTECT", "0");
let mut config = Config::new(true, None);
@@ -562,6 +567,7 @@ fn test_get_source_of_value_env_variables() {
}
#[test]
+#[serial]
fn test_audit() {
let mut config = Config::new(true, None);
let result = config.get("audit");
diff --git a/crates/shirabe/tests/util/ini_helper_test.rs b/crates/shirabe/tests/util/ini_helper_test.rs
index f511d76..d1e2071 100644
--- a/crates/shirabe/tests/util/ini_helper_test.rs
+++ b/crates/shirabe/tests/util/ini_helper_test.rs
@@ -12,7 +12,8 @@ fn set_up() -> TearDown {
// XdebugHandler is a unit struct with no name-registration API, so this
// step is a no-op here.
// Save current state
- let env_original = getenv("COMPOSER_ORIGINAL_INIS");
+ let env_original =
+ getenv("COMPOSER_ORIGINAL_INIS").map(|value| value.to_string_lossy().into_owned());
TearDown { env_original }
}
@@ -20,7 +21,7 @@ fn set_up() -> TearDown {
fn tear_down(env_original: &Option<String>) {
// Restore original state
if let Some(env_original) = env_original {
- putenv(&format!("COMPOSER_ORIGINAL_INIS={env_original}"));
+ unsafe { putenv("COMPOSER_ORIGINAL_INIS", env_original) };
} else {
Platform::clear_env("COMPOSER_ORIGINAL_INIS");
}
@@ -38,10 +39,7 @@ impl Drop for TearDown {
}
fn set_env(paths: &[&str]) {
- putenv(&format!(
- "COMPOSER_ORIGINAL_INIS={}",
- paths.join(PATH_SEPARATOR)
- ));
+ unsafe { putenv("COMPOSER_ORIGINAL_INIS", paths.join(PATH_SEPARATOR)) };
}
#[test]