From c65cab710bb3d79db20862c9361d4dbbac8ac5d7 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Thu, 25 Jun 2026 03:20:08 +0900 Subject: 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) --- crates/shirabe-php-shim/src/env.rs | 144 +++++++++++++++++-------------------- 1 file changed, 67 insertions(+), 77 deletions(-) (limited to 'crates/shirabe-php-shim') diff --git a/crates/shirabe-php-shim/src/env.rs b/crates/shirabe-php-shim/src/env.rs index 62dc666..cf0c659 100644 --- a/crates/shirabe-php-shim/src/env.rs +++ b/crates/shirabe-php-shim/src/env.rs @@ -1,100 +1,90 @@ -use crate::PhpMixed; -use indexmap::IndexMap; +//! Porting rules for PHP environment variables: see `docs/dev/env-vars-porting.md`. -pub fn getenv(_name: &str) -> Option { - std::env::var(_name).ok() +pub fn getenv_all() -> std::env::VarsOs { + std::env::vars_os() } -// TODO(phase-c): only the simple `^(\w+)(=(.+))?$` form is supported. -pub fn putenv(setting: &str) -> bool { - let is_word = - |s: &str| !s.is_empty() && s.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'_'); - // A setting without `=` deletes the variable, mirroring PHP's putenv('NAME'). - match setting.split_once('=') { - Some((name, value)) => { - if !is_word(name) { - panic!("putenv: unsupported setting format: {setting:?}"); - } - unsafe { - std::env::set_var(name, value); - } - } - None => { - if !is_word(setting) { - panic!("putenv: unsupported setting format: {setting:?}"); - } - unsafe { - std::env::remove_var(setting); - } - } - } - true +pub fn getenv>(key: K) -> Option { + std::env::var_os(key) } -/// PHP superglobal $_SERVER access. In the CLI SAPI $_SERVER is populated from -/// the environment, which is the only source modeled here. -pub fn server_get(name: &str) -> Option { - // TODO: is var_os() better? - std::env::var(name).ok() +pub unsafe fn putenv, V: AsRef>(key: K, value: V) { + // TODO: validate key and value format to avoid panic? + unsafe { std::env::set_var(key, value) } } -// TODO(php-runtime): modify the real PHP's $_SERVER. -pub fn server_set(_name: &str, _value: String) {} - -// TODO(php-runtime): modify the real PHP's $_SERVER. -pub fn server_unset(_name: &str) {} - -pub fn server_contains_key(name: &str) -> bool { - std::env::var_os(name).is_some() +pub unsafe fn putenv_clear>(key: K) { + // TODO: validate key and value format to avoid panic? + unsafe { std::env::remove_var(key) } } -/// PHP superglobal $_ENV access. -pub fn env_get(name: &str) -> Option { - // TODO: is var_os() better? - std::env::var(name).ok() +pub struct Superglobal { + vars: indexmap::IndexMap, } -// TODO(php-runtime): modify the real PHP's $_ENV. -pub fn env_set(_name: &str, _value: String) {} +pub struct SuperglobalServer(Superglobal); -// TODO(php-runtime): modify the real PHP's $_ENV. -pub fn env_unset(_name: &str) {} +impl Superglobal { + fn from_env_vars() -> Self { + let vars = std::env::vars_os().collect(); + Self { vars } + } -pub fn env_contains_key(name: &str) -> bool { - std::env::var_os(name).is_some() -} + pub fn get_all(&self) -> impl Iterator + '_ { + self.vars.iter().map(|(k, v)| (k.clone(), v.clone())) + } -/// PHP `getenv()` with no argument: all environment variables. -pub fn getenv_all() -> IndexMap { - todo!() -} + pub fn get>(&self, key: K) -> Option { + self.vars.get(key.as_ref()).cloned() + } -/// PHP superglobal `$_ENV`. -pub fn php_env() -> IndexMap { - todo!() -} + pub fn put(&mut self, key: std::ffi::OsString, value: std::ffi::OsString) { + self.vars.insert(key, value); + } -/// PHP superglobal `$_SERVER`. -pub fn php_server() -> IndexMap { - todo!() + pub fn clear>(&mut self, key: K) { + self.vars.shift_remove(key.as_ref()); + } } -pub fn server(key: &str) -> String { - if key == "PHP_SELF" { - return server_php_self(); +impl SuperglobalServer { + fn from_env_vars() -> Self { + Self(Superglobal::from_env_vars()) } - server_get(key).unwrap_or_default() -} -pub fn server_argv() -> Vec { - std::env::args().collect() -} + pub fn get_all(&self) -> impl Iterator + '_ { + self.0.get_all() + } -pub fn server_php_self() -> String { - // CLI SAPI: $_SERVER['PHP_SELF'] is the path of the executed script, i.e. argv[0]. - std::env::args().next().unwrap_or_default() -} + pub fn get>(&self, key: K) -> Option { + self.0.get(key) + } + + pub fn put(&mut self, key: std::ffi::OsString, value: std::ffi::OsString) { + self.0.put(key, value) + } -pub fn server_shell() -> Option { - todo!() + pub fn clear>(&mut self, key: K) { + self.0.clear(key) + } + + pub fn argv(&self) -> std::env::ArgsOs { + std::env::args_os() + } + + pub fn php_self(&self) -> Option { + self.argv().next() + } } + +/// PHP superglobal $_SERVER. $_SERVER is a snapshot at startup. Modifying it does not affect the +/// real environment variables, while putenv() does. +/// TODO(php-runtime): modify the real PHP's $_SERVER. +pub static PHP_SERVER: std::sync::LazyLock> = + std::sync::LazyLock::new(|| std::sync::Mutex::new(SuperglobalServer::from_env_vars())); + +/// PHP superglobal $_ENV. $_ENV is a snapshot at startup. Modifying it does not affect the real +/// environment variables, while putenv() does. +/// TODO(php-runtime): modify the real PHP's $_ENV. +pub static PHP_ENV: std::sync::LazyLock> = + std::sync::LazyLock::new(|| std::sync::Mutex::new(Superglobal::from_env_vars())); -- cgit v1.3.1