diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-07-02 05:12:39 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-07-02 05:12:39 +0900 |
| commit | 7fd7df848cdbbd8792b0043799018d51408458fc (patch) | |
| tree | 28aeeef87c3d86a662ed1328b53b1a2865df2c6d /crates | |
| parent | eab3a31c5750013c53c0eb02adc976d6757dc9f7 (diff) | |
| download | php-shirabe-7fd7df848cdbbd8792b0043799018d51408458fc.tar.gz php-shirabe-7fd7df848cdbbd8792b0043799018d51408458fc.tar.zst php-shirabe-7fd7df848cdbbd8792b0043799018d51408458fc.zip | |
feat(php-rpc): implement Runtime::has_constant/get_constant via php-rpc
Runtime::hasConstant/getConstant need a real PHP interpreter's defined()/
constant() to answer platform requirement checks (e.g. PHP_ZTS, PHP_INT_SIZE),
which the shim can't provide since Rust constants aren't queryable by string.
Extend shirabe-php-rpc's protocol to carry one string argument and return the
full PHP scalar range, add defined/constant dispatch entries to the worker,
and wire Runtime and get_php_version/get_php_binary onto them.
Diffstat (limited to 'crates')
| -rw-r--r-- | crates/shirabe-php-rpc/Cargo.toml | 1 | ||||
| -rw-r--r-- | crates/shirabe-php-rpc/php/worker.php | 12 | ||||
| -rw-r--r-- | crates/shirabe-php-rpc/src/lib.rs | 125 | ||||
| -rw-r--r-- | crates/shirabe/src/platform/runtime.rs | 8 |
4 files changed, 131 insertions, 15 deletions
diff --git a/crates/shirabe-php-rpc/Cargo.toml b/crates/shirabe-php-rpc/Cargo.toml index 692dda9..115381c 100644 --- a/crates/shirabe-php-rpc/Cargo.toml +++ b/crates/shirabe-php-rpc/Cargo.toml @@ -5,6 +5,7 @@ edition.workspace = true [dependencies] shirabe-external-packages.workspace = true +shirabe-php-shim.workspace = true tempfile.workspace = true [lints] diff --git a/crates/shirabe-php-rpc/php/worker.php b/crates/shirabe-php-rpc/php/worker.php index 214817a..58ba973 100644 --- a/crates/shirabe-php-rpc/php/worker.php +++ b/crates/shirabe-php-rpc/php/worker.php @@ -7,8 +7,8 @@ if ($client === false) { exit(1); } $dispatch = [ - 'get_php_version' => static fn() => PHP_VERSION, - 'get_php_binary' => static fn() => PHP_BINARY, + 'defined' => static fn($name) => defined($name), + 'constant' => static fn($name) => defined($name) ? constant($name) : null, ]; $read_exact = static function ($conn, int $len): ?string { $buf = ''; @@ -31,7 +31,13 @@ while (true) { if ($name === null) { break; } - $result = isset($dispatch[$name]) ? ($dispatch[$name])() : null; + $sep = strpos($name, "\0"); + $arg = null; + if ($sep !== false) { + $arg = substr($name, $sep + 1); + $name = substr($name, 0, $sep); + } + $result = isset($dispatch[$name]) ? ($dispatch[$name])($arg) : null; $payload = serialize($result); fwrite($client, pack('P', strlen($payload)) . $payload); } diff --git a/crates/shirabe-php-rpc/src/lib.rs b/crates/shirabe-php-rpc/src/lib.rs index d55381b..04583fa 100644 --- a/crates/shirabe-php-rpc/src/lib.rs +++ b/crates/shirabe-php-rpc/src/lib.rs @@ -1,6 +1,7 @@ //! Rust-to-PHP RPC over a Unix domain socket. See `docs/dev/php-rpc.md`. use shirabe_external_packages::symfony::process::PhpExecutableFinder; +use shirabe_php_shim::PhpMixed; use std::io::{Read as _, Write as _}; use std::os::unix::net::{UnixListener, UnixStream}; use std::sync::{LazyLock, Mutex}; @@ -8,12 +9,28 @@ use std::time::{Duration, Instant}; /// PHP `\PHP_VERSION`. pub fn get_php_version() -> String { - call("get_php_version").unwrap_or_default() + match get_constant("PHP_VERSION") { + PhpMixed::String(s) => s, + _ => String::new(), + } } /// PHP `\PHP_BINARY`. pub fn get_php_binary() -> String { - call("get_php_binary").unwrap_or_default() + match get_constant("PHP_BINARY") { + PhpMixed::String(s) => s, + _ => String::new(), + } +} + +/// PHP `defined($name)`. +pub fn has_constant(name: &str) -> bool { + matches!(call("defined", name), Some(PhpMixed::Bool(true))) +} + +/// PHP `constant($name)`. +pub fn get_constant(name: &str) -> PhpMixed { + call("constant", name).unwrap_or(PhpMixed::Null) } const GLUE_SCRIPT: &str = include_str!("../php/worker.php"); @@ -27,18 +44,21 @@ struct Worker { } impl Worker { - fn request(&mut self, name: &str) -> Option<String> { - write_frame(&mut self.stream, name.as_bytes()).ok()?; - let payload = read_frame(&mut self.stream).ok()?; - parse_serialized_string(&payload) + fn request(&mut self, name: &str, arg: &str) -> Option<Vec<u8>> { + let mut payload = name.as_bytes().to_vec(); + payload.push(0); + payload.extend_from_slice(arg.as_bytes()); + write_frame(&mut self.stream, &payload).ok()?; + read_frame(&mut self.stream).ok() } } static WORKER: LazyLock<Mutex<Option<Worker>>> = LazyLock::new(|| Mutex::new(spawn_worker())); -fn call(name: &str) -> Option<String> { +fn call(name: &str, arg: &str) -> Option<PhpMixed> { let mut guard = WORKER.lock().ok()?; - guard.as_mut()?.request(name) + let payload = guard.as_mut()?.request(name, arg)?; + parse_serialized_scalar(&payload) } fn spawn_worker() -> Option<Worker> { @@ -108,6 +128,33 @@ fn parse_serialized_string(payload: &[u8]) -> Option<String> { Some(String::from_utf8_lossy(bytes).into_owned()) } +/// Parse any of PHP's scalar/null `serialize()` forms: `N;`, `b:0/1;`, `i:<n>;`, `d:<f>;`, +/// `s:<len>:"<bytes>";`. +fn parse_serialized_scalar(payload: &[u8]) -> Option<PhpMixed> { + if payload == b"N;" { + return Some(PhpMixed::Null); + } + if let Some(rest) = payload.strip_prefix(b"b:") { + return match rest.strip_suffix(b";")? { + b"0" => Some(PhpMixed::Bool(false)), + b"1" => Some(PhpMixed::Bool(true)), + _ => None, + }; + } + if let Some(rest) = payload.strip_prefix(b"i:") { + let s = std::str::from_utf8(rest.strip_suffix(b";")?).ok()?; + return s.parse().ok().map(PhpMixed::Int); + } + if let Some(rest) = payload.strip_prefix(b"d:") { + let s = std::str::from_utf8(rest.strip_suffix(b";")?).ok()?; + return s.parse().ok().map(PhpMixed::Float); + } + if payload.starts_with(b"s:") { + return parse_serialized_string(payload).map(PhpMixed::String); + } + None +} + #[cfg(test)] mod tests { use super::*; @@ -151,6 +198,50 @@ mod tests { } #[test] + fn parses_scalar_null() { + assert_eq!(parse_serialized_scalar(b"N;"), Some(PhpMixed::Null)); + } + + #[test] + fn parses_scalar_bool() { + assert_eq!( + parse_serialized_scalar(b"b:0;"), + Some(PhpMixed::Bool(false)) + ); + assert_eq!(parse_serialized_scalar(b"b:1;"), Some(PhpMixed::Bool(true))); + } + + #[test] + fn parses_scalar_int() { + assert_eq!(parse_serialized_scalar(b"i:8;"), Some(PhpMixed::Int(8))); + assert_eq!(parse_serialized_scalar(b"i:-1;"), Some(PhpMixed::Int(-1))); + } + + #[test] + fn parses_scalar_float() { + assert_eq!( + parse_serialized_scalar(b"d:1.5;"), + Some(PhpMixed::Float(1.5)) + ); + } + + #[test] + fn parses_scalar_string() { + assert_eq!( + parse_serialized_scalar(b"s:5:\"8.5.7\";"), + Some(PhpMixed::String("8.5.7".to_string())), + ); + } + + #[test] + fn rejects_malformed_scalar() { + assert_eq!(parse_serialized_scalar(b"b:2;"), None); + assert_eq!(parse_serialized_scalar(b"i:x;"), None); + assert_eq!(parse_serialized_scalar(b"d:x;"), None); + assert_eq!(parse_serialized_scalar(b"garbage"), None); + } + + #[test] fn frame_roundtrip() { let (mut a, mut b) = UnixStream::pair().unwrap(); write_frame(&mut a, b"get_php_version").unwrap(); @@ -189,4 +280,22 @@ mod tests { "binary should exist: {binary}", ); } + + #[test] + fn queries_constants_when_php_available() { + if PhpExecutableFinder::new().find(false).is_none() { + // No PHP in this environment; the worker cannot start. + return; + } + + assert!(has_constant("PHP_VERSION")); + assert!(!has_constant("SHIRABE_DOES_NOT_EXIST_XYZ")); + + assert_eq!(get_constant("PHP_INT_SIZE"), PhpMixed::Int(8)); + assert_eq!(get_constant("SHIRABE_DOES_NOT_EXIST_XYZ"), PhpMixed::Null); + match get_constant("PHP_VERSION") { + PhpMixed::String(s) => assert!(!s.is_empty(), "expected a non-empty PHP_VERSION"), + other => panic!("expected a string, got {other:?}"), + } + } } diff --git a/crates/shirabe/src/platform/runtime.rs b/crates/shirabe/src/platform/runtime.rs index 255aa9d..e4ff64a 100644 --- a/crates/shirabe/src/platform/runtime.rs +++ b/crates/shirabe/src/platform/runtime.rs @@ -3,8 +3,8 @@ use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::{CaptureKey, Preg}; use shirabe_php_shim::{ - PhpMixed, class_exists, constant, defined, function_exists, get_loaded_extensions, - html_entity_decode, implode, instantiate_class, ltrim, phpversion, strip_tags, trim, + PhpMixed, class_exists, function_exists, get_loaded_extensions, html_entity_decode, implode, + instantiate_class, ltrim, phpversion, strip_tags, trim, }; /// Seam over the PHP runtime so PlatformRepository can be tested against mocked @@ -29,14 +29,14 @@ pub struct Runtime; impl RuntimeInterface for Runtime { fn has_constant(&self, constant_name: &str, class: Option<String>) -> bool { - defined(<rim( + shirabe_php_rpc::has_constant(<rim( &format!("{}::{}", class.as_deref().unwrap_or(""), constant_name), Some(":"), )) } fn get_constant(&self, constant_name: &str, class: Option<String>) -> PhpMixed { - constant(<rim( + shirabe_php_rpc::get_constant(<rim( &format!("{}::{}", class.as_deref().unwrap_or(""), constant_name), Some(":"), )) |
