diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-08-07 20:51:59 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-08-07 20:51:59 +0900 |
| commit | ae365645730b95d5b01b0df7382b91668d5fa24e (patch) | |
| tree | 962e3abb51132097ff11a79d453e058945ae699c /crates/shirabe/src/platform | |
| parent | f749a47804cd296a3059cd3f8079c62dbaa5fdc0 (diff) | |
| download | php-shirabe-ae365645730b95d5b01b0df7382b91668d5fa24e.tar.gz php-shirabe-ae365645730b95d5b01b0df7382b91668d5fa24e.tar.zst php-shirabe-ae365645730b95d5b01b0df7382b91668d5fa24e.zip | |
refactor(platform-repository): fetch the PHP runtime in one RPC call
The RuntimeInterface seam asked the worker one question at a time: a round
trip per loaded extension, per ReflectionExtension::info() output and per
constant, so a single `show --platform` cost 70 to 100 of them. A `platform`
dispatch entry now answers all of it as one PHP array, which shirabe-php-rpc
decodes into a OnceLock-cached PlatformInfo, the way the diagnose command
already works.
Composer\Platform\Runtime therefore has no Rust counterpart any more. Its
work belongs to the running interpreter, and invoke()/construct() could only
be ported as a whitelist that panicked on anything unlisted; it is ported as
PHP into the worker instead, and PlatformRepository reads the answers off
PlatformInfo. Accessors panic on a name the payload does not carry, so the
worker and its consumers cannot drift apart unnoticed.
The tests describe the runtime as payload data where they used to mock the
seam, with the datasets unchanged. The one loss is the call-count assertion
of test_inet_pton_regression: the payload reports the result of
`@inet_pton('::')` rather than answering a call.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/src/platform')
| -rw-r--r-- | crates/shirabe/src/platform/runtime.rs | 237 |
1 files changed, 0 insertions, 237 deletions
diff --git a/crates/shirabe/src/platform/runtime.rs b/crates/shirabe/src/platform/runtime.rs deleted file mode 100644 index 265f007c..00000000 --- a/crates/shirabe/src/platform/runtime.rs +++ /dev/null @@ -1,237 +0,0 @@ -//! ref: composer/src/Composer/Platform/Runtime.php - -use indexmap::IndexMap; -use shirabe_external_packages::composer::pcre::{CaptureKey, Preg}; -use shirabe_php_rpc::{PhpThrow, PluginValue}; -use shirabe_php_shim::{ - PhpMixed, RuntimeException, function_exists, html_entity_decode, implode, ltrim, php_regex, - strip_tags, trim, -}; - -/// Seam over the PHP runtime so PlatformRepository can be tested against mocked -/// extension/constant/function probes. PHP has no such interface (the test mocks the -/// concrete `Composer\Platform\Runtime` directly); it is introduced here to keep the -/// consumer dependent only on trait methods. -pub trait RuntimeInterface: std::fmt::Debug { - fn has_constant(&self, constant_name: &str, class: Option<String>) -> bool; - fn get_constant(&self, constant_name: &str, class: Option<String>) -> PhpMixed; - /// `callable` carries the PHP callable spec (a function name string or a - /// `[class, method]` list), matching PHP `invoke($callable, $arguments)`. - fn invoke(&self, callable: PhpMixed, arguments: Vec<PhpMixed>) -> PhpMixed; - fn has_class(&self, class: &str) -> bool; - fn construct(&self, class: &str, arguments: Vec<PhpMixed>) -> anyhow::Result<PhpMixed>; - fn get_extensions(&self) -> Vec<String>; - fn get_extension_version(&self, extension: &str) -> String; - fn get_extension_info(&self, extension: &str) -> anyhow::Result<String>; -} - -#[derive(Debug)] -pub struct Runtime; - -impl RuntimeInterface for Runtime { - fn has_constant(&self, constant_name: &str, class: Option<String>) -> bool { - 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 { - shirabe_php_rpc::get_constant(<rim( - &format!("{}::{}", class.as_deref().unwrap_or(""), constant_name), - Some(":"), - )) - } - - fn invoke(&self, callable: PhpMixed, arguments: Vec<PhpMixed>) -> PhpMixed { - // PHP: return $callable(...$arguments); - // Only the specific dynamic callables PlatformRepository actually reaches are - // wired through php-rpc; arbitrary PHP callables are still unsupported. - match (&callable, arguments.as_slice()) { - (PhpMixed::String(name), [PhpMixed::String(arg)]) if name == "inet_pton" => { - shirabe_php_rpc::inet_pton(arg) - } - (PhpMixed::String(name), []) if name == "curl_version" => { - let mut version = IndexMap::new(); - if let Some(v) = shirabe_php_rpc::curl_version() { - version.insert("version".to_string(), PhpMixed::String(v)); - } - PhpMixed::Array(version) - } - (PhpMixed::List(spec), _) => match class_callable(spec) { - ("ResourceBundle", "create") => resource_bundle_create(arguments), - ("IntlChar", "getUnicodeVersion") => { - php_value(shirabe_php_rpc::call_static_method( - "IntlChar", - "getUnicodeVersion", - Vec::new(), - None, - )) - } - (class, method) => panic!( - "the PHP callable `{class}::{method}` is not wired through the runtime seam" - ), - }, - _ => panic!("the PHP callable {callable:?} is not wired through the runtime seam"), - } - } - - fn has_class(&self, class: &str) -> bool { - shirabe_php_rpc::class_exists(class) - } - - fn construct(&self, class: &str, arguments: Vec<PhpMixed>) -> anyhow::Result<PhpMixed> { - match class { - "Imagick" => imagick_version(arguments), - other => Err(anyhow::anyhow!(RuntimeException { - message: format!("the PHP class `{other}` is not wired through the runtime seam"), - code: 0, - })), - } - } - - fn get_extensions(&self) -> Vec<String> { - shirabe_php_rpc::get_loaded_extensions() - } - - fn get_extension_version(&self, extension: &str) -> String { - shirabe_php_rpc::phpversion(extension).unwrap_or_else(|| "0".to_string()) - } - - fn get_extension_info(&self, extension: &str) -> anyhow::Result<String> { - Ok(shirabe_php_rpc::get_extension_info(extension)) - } -} - -/// The `[class, method]` pair of a PHP callable given in array form. -fn class_callable(spec: &[PhpMixed]) -> (&str, &str) { - match spec { - [PhpMixed::String(class), PhpMixed::String(method)] => (class, method), - other => panic!("a PHP callable given as an array must be [class, method], got {other:?}"), - } -} - -/// Unwraps an RPC outcome whose failure means the runtime probe itself is broken, not that the -/// probed extension is absent. -fn php_value(outcome: anyhow::Result<Result<PluginValue, PhpThrow>>) -> PhpMixed { - match outcome { - Ok(Ok(value)) => value - .to_php_mixed() - .expect("a runtime probe answers with plain values"), - Ok(Err(throw)) => panic!("the PHP runtime probe failed: {}", throw.message), - Err(e) => panic!("the PHP runtime probe could not be sent: {e:#}"), - } -} - -/// PHP `ResourceBundle::create(...)`, whose result the caller reads `->get('Version')` off. -/// A live PHP object has no `PhpMixed` counterpart, so that entry crosses in its place. -fn resource_bundle_create(arguments: Vec<PhpMixed>) -> PhpMixed { - let bundle = match php_handle(shirabe_php_rpc::call_static_method( - "ResourceBundle", - "create", - arguments.iter().map(PluginValue::from_php_mixed).collect(), - None, - )) { - Some(phandle) => phandle, - // PHP returns null when the bundle cannot be opened. - None => return PhpMixed::Null, - }; - let version = php_value(shirabe_php_rpc::call_php_method( - bundle, - "get", - vec![PluginValue::string("Version")], - None, - )); - let _ = shirabe_php_rpc::release_php_handle(bundle); - PhpMixed::Object(IndexMap::from([("Version".to_string(), version)])) -} - -/// PHP `(new Imagick())->getVersion()`, reported as the entries the caller reads. -fn imagick_version(arguments: Vec<PhpMixed>) -> anyhow::Result<PhpMixed> { - let imagick = php_handle(shirabe_php_rpc::new_object( - "Imagick", - arguments.iter().map(PluginValue::from_php_mixed).collect(), - None, - )) - .ok_or_else(|| { - anyhow::anyhow!(RuntimeException { - message: "`new Imagick` did not answer with an object".to_string(), - code: 0, - }) - })?; - let version = php_value(shirabe_php_rpc::call_php_method( - imagick, - "getVersion", - Vec::new(), - None, - )); - let _ = shirabe_php_rpc::release_php_handle(imagick); - Ok(version) -} - -/// The handle of a PHP-side object an RPC answered with, or `None` when it answered with null. -fn php_handle(outcome: anyhow::Result<Result<PluginValue, PhpThrow>>) -> Option<u64> { - match outcome { - Ok(Ok(PluginValue::PhpHandle(handle))) => Some(handle.phandle), - Ok(Ok(PluginValue::Null)) => None, - Ok(Ok(other)) => panic!("the PHP runtime probe answered with {other:?}, not an object"), - Ok(Err(throw)) => panic!("the PHP runtime probe failed: {}", throw.message), - Err(e) => panic!("the PHP runtime probe could not be sent: {e:#}"), - } -} - -impl Runtime { - pub fn has_function(&self, f: &str) -> bool { - function_exists(f) - } - - pub fn parse_html_extension_info(html: &str) -> String { - let mut result: Vec<String> = vec![]; - - let mut matches: IndexMap<CaptureKey, String> = IndexMap::new(); - if Preg::match3( - php_regex!(r"~<h2>\s*<a[^>]*>([^<]+)</a>\s*</h2>~i"), - html, - Some(&mut matches), - ) { - result.push(trim( - &html_entity_decode( - matches - .get(&CaptureKey::ByIndex(1)) - .map(|s| s.as_str()) - .unwrap_or(""), - ), - None, - )); - result.push(String::new()); - } - - let mut matches: IndexMap<CaptureKey, Vec<String>> = IndexMap::new(); - if Preg::match_all3( - php_regex!( - r#"~<tr>\s*<td class="e">\s*(.*?)\s*</td>\s*<td class="v">\s*(.*?)\s*</td>\s*</tr>~is"# - ), - html, - Some(&mut matches), - ) > 0 - { - let group1 = matches - .get(&CaptureKey::ByIndex(1)) - .cloned() - .unwrap_or_default(); - let group2 = matches - .get(&CaptureKey::ByIndex(2)) - .cloned() - .unwrap_or_default(); - let count = std::cmp::min(group1.len(), group2.len()); - - for i in 0..count { - let key = trim(&html_entity_decode(&strip_tags(&group1[i])), None); - let value = trim(&html_entity_decode(&strip_tags(&group2[i])), None); - result.push(format!("{} => {}", key, value)); - } - } - - implode("\n", &result) - } -} |
