diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-08-16 14:18:41 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-08-16 14:35:48 +0900 |
| commit | aa2124fe5d0c96078c034a6e4044b7e81acdf692 (patch) | |
| tree | c9b1597fef555220676f6a5ae554dc81579a4269 /crates/shirabe-php-shim | |
| parent | b4ab3df2ec85fbe477d7721344a8cd3630b437a1 (diff) | |
| download | php-shirabe-aa2124fe5d0c96078c034a6e4044b7e81acdf692.tar.gz php-shirabe-aa2124fe5d0c96078c034a6e4044b7e81acdf692.tar.zst php-shirabe-aa2124fe5d0c96078c034a6e4044b7e81acdf692.zip | |
feat(php-rpc): replay Rust-side env writes into the PHP worker
The worker is a long-lived child holding the environment it was handed
at spawn, so `@putenv`, the bin dir the event dispatcher prepends to
PATH, and COMPOSER_DEV_MODE never reached the PHP code running in it.
The shim now journals every write to the three storages PHP exposes, and
the outermost rpc_call replays the entries the worker has not seen yet
through __shirabe_sync_env. Replaying the writes rather than pushing a
whole snapshot keeps the worker's own $_SERVER entries intact.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe-php-shim')
| -rw-r--r-- | crates/shirabe-php-shim/src/env.rs | 57 |
1 files changed, 51 insertions, 6 deletions
diff --git a/crates/shirabe-php-shim/src/env.rs b/crates/shirabe-php-shim/src/env.rs index 8facfb4a..861c0243 100644 --- a/crates/shirabe-php-shim/src/env.rs +++ b/crates/shirabe-php-shim/src/env.rs @@ -15,6 +15,7 @@ pub fn getenv<K: AsRef<std::ffi::OsStr>>(key: K) -> Option<std::ffi::OsString> { /// duration of this call. pub unsafe fn putenv<K: AsRef<std::ffi::OsStr>, V: AsRef<std::ffi::OsStr>>(key: K, value: V) { // TODO(php-semantics): validate key and value format to avoid panic? + record(EnvStorageKind::Process, key.as_ref(), Some(value.as_ref())); unsafe { std::env::set_var(key, value) } } @@ -25,19 +26,59 @@ pub unsafe fn putenv<K: AsRef<std::ffi::OsStr>, V: AsRef<std::ffi::OsStr>>(key: /// duration of this call. pub unsafe fn putenv_clear<K: AsRef<std::ffi::OsStr>>(key: K) { // TODO(php-semantics): validate key and value format to avoid panic? + record(EnvStorageKind::Process, key.as_ref(), None); unsafe { std::env::remove_var(key) } } +/// Which of the three environment storages a recorded mutation writes to. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum EnvStorageKind { + /// The real process environment, as `putenv()` writes it. + Process, + /// The `$_ENV` superglobal. + Env, + /// The `$_SERVER` superglobal. + Server, +} + +/// One recorded write. `value` is `None` for an unset. +#[derive(Debug, Clone)] +pub struct EnvMutation { + pub storage: EnvStorageKind, + pub key: std::ffi::OsString, + pub value: Option<std::ffi::OsString>, +} + +static ENV_MUTATIONS: std::sync::Mutex<Vec<EnvMutation>> = std::sync::Mutex::new(Vec::new()); + +fn record(storage: EnvStorageKind, key: &std::ffi::OsStr, value: Option<&std::ffi::OsStr>) { + ENV_MUTATIONS.lock().unwrap().push(EnvMutation { + storage, + key: key.to_os_string(), + value: value.map(|value| value.to_os_string()), + }); +} + +/// The mutations recorded after the first `cursor` ones, and the cursor that follows them. +/// +/// A separate PHP runtime holds the environment it was handed when it started; replaying these +/// writes in order is what brings its three storages back in line with this process's. +pub fn env_mutations_since(cursor: usize) -> (usize, Vec<EnvMutation>) { + let mutations = ENV_MUTATIONS.lock().unwrap(); + (mutations.len(), mutations[cursor..].to_vec()) +} + pub struct Superglobal { + storage: EnvStorageKind, vars: indexmap::IndexMap<std::ffi::OsString, std::ffi::OsString>, } pub struct SuperglobalServer(Superglobal); impl Superglobal { - fn from_env_vars() -> Self { + fn from_env_vars(storage: EnvStorageKind) -> Self { let vars = std::env::vars_os().collect(); - Self { vars } + Self { storage, vars } } pub fn get_all(&self) -> impl Iterator<Item = (std::ffi::OsString, std::ffi::OsString)> + '_ { @@ -49,17 +90,19 @@ impl Superglobal { } pub fn put(&mut self, key: std::ffi::OsString, value: std::ffi::OsString) { + record(self.storage, &key, Some(&value)); self.vars.insert(key, value); } pub fn clear<K: AsRef<std::ffi::OsStr>>(&mut self, key: K) { + record(self.storage, key.as_ref(), None); self.vars.shift_remove(key.as_ref()); } } impl SuperglobalServer { fn from_env_vars() -> Self { - Self(Superglobal::from_env_vars()) + Self(Superglobal::from_env_vars(EnvStorageKind::Server)) } pub fn get_all(&self) -> impl Iterator<Item = (std::ffi::OsString, std::ffi::OsString)> + '_ { @@ -89,12 +132,14 @@ impl SuperglobalServer { /// 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. +/// TODO(php-runtime): a write PHP code makes to its own $_SERVER is not reflected back here. pub static PHP_SERVER: std::sync::LazyLock<std::sync::Mutex<SuperglobalServer>> = 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. +/// TODO(php-runtime): a write PHP code makes to its own $_ENV is not reflected back here. pub static PHP_ENV: std::sync::LazyLock<std::sync::Mutex<Superglobal>> = - std::sync::LazyLock::new(|| std::sync::Mutex::new(Superglobal::from_env_vars())); + std::sync::LazyLock::new(|| { + std::sync::Mutex::new(Superglobal::from_env_vars(EnvStorageKind::Env)) + }); |
