aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe-php-rpc/src
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-08-30 23:01:25 +0900
committernsfisis <nsfisis@gmail.com>2026-08-30 23:09:56 +0900
commitd3bc3354c9705dfc6dc5e9b9adb5eb64d41e4c49 (patch)
treea745ecc3403104d5a34f29964362e239e8f5675b /crates/shirabe-php-rpc/src
parent057f3b8de26293319e265c1d86d9a1153124f3c7 (diff)
downloadphp-shirabe-d3bc3354c9705dfc6dc5e9b9adb5eb64d41e4c49.tar.gz
php-shirabe-d3bc3354c9705dfc6dc5e9b9adb5eb64d41e4c49.tar.zst
php-shirabe-d3bc3354c9705dfc6dc5e9b9adb5eb64d41e4c49.zip
feat(plugin): serve ProcessExecutor as a proxy stub
Composer reaches this class two ways: the object graph hands one out through Composer::getLoop()->getProcessExecutor(), and plugins write `new ProcessExecutor($io)` freely. Both bind to a Rust-side entity, so the timeout the run shares -- seeded from process-timeout and rewritten while the run is in flight -- has one value instead of one per world, and the executor can still be passed to the classes that take one (`new Filesystem($process)`). Three things the stub generator was missing came with it: - By-ref parameters. The call carries their positions and the answer carries what each holds afterwards; a position the answer omits was never assigned to, which is what PHP does with an untouched by-ref parameter. ProcessExecutor::execute is the only one on a proxied class. - Argument arity, reproduced where the real body reads func_num_args(). execute($cmd) forwards the child's output and execute($cmd, $out) captures it, and nothing but the argument count separates the two. - Static methods that cannot run in the worker. One that reads a static property the Rust side owns, or that reaches a guarded class, forwards through __shirabeCallStatic instead of being materialized. That also fixes Filesystem::isLocalPath and getPlatformPath, whose materialized bodies called the guarded Composer\Util\Platform. The async surface stays an explicit error. executeAsync resolves its promise with a Symfony Process, whose proc_open() resource and pipes belong to whichever process called start(), so a Rust-side spawn has none to hand back; running the real start() in the worker needs a promise representation that crosses the boundary unresolved. The fixture project drives the whole synchronous surface from plugin code and compares the trace against upstream Composer byte for byte. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe-php-rpc/src')
-rw-r--r--crates/shirabe-php-rpc/src/lib.rs24
1 files changed, 20 insertions, 4 deletions
diff --git a/crates/shirabe-php-rpc/src/lib.rs b/crates/shirabe-php-rpc/src/lib.rs
index b2bc2c28..dfd8fa67 100644
--- a/crates/shirabe-php-rpc/src/lib.rs
+++ b/crates/shirabe-php-rpc/src/lib.rs
@@ -643,6 +643,12 @@ impl std::error::Error for PhpThrow {}
/// Handles `CallRustMethod` requests arriving while a Rust-initiated call is waiting for its
/// `Return` (the cooperative reentrancy loop). A handler may itself issue nested RPC calls.
+///
+/// `out_param_positions` names the by-ref parameters of the called method, as the calling stub
+/// declared them. A handler that assigns to one writes the resulting value into `out_params` under
+/// the same position, and the stub copies it back into the caller's variable; leaving a position
+/// out means the method never assigned to it, which is what PHP does with an untouched by-ref
+/// parameter.
pub trait RustMethodDispatcher {
fn dispatch(
&mut self,
@@ -650,6 +656,7 @@ pub trait RustMethodDispatcher {
method_name: &str,
args: Vec<PluginValue>,
out_param_positions: &[u32],
+ out_params: &mut IndexMap<u32, PluginValue>,
) -> Result<PluginValue, PhpThrow>;
}
@@ -812,10 +819,15 @@ fn rpc_call(
args,
out_param_positions,
} => {
+ let mut out_params = IndexMap::new();
let outcome = match dispatcher.as_deref_mut() {
- Some(dispatcher) => {
- dispatcher.dispatch(rhandle, &method_name, args, &out_param_positions)
- }
+ Some(dispatcher) => dispatcher.dispatch(
+ rhandle,
+ &method_name,
+ args,
+ &out_param_positions,
+ &mut out_params,
+ ),
// Never fall back to a silent null: an unroutable callback is reported as an
// explicit error on the PHP side.
None => Err(PhpThrow::runtime(format!(
@@ -827,7 +839,7 @@ fn rpc_call(
Ok(value) => Frame::Return {
corr_id,
value,
- out_params: IndexMap::new(),
+ out_params,
},
Err(throw) => Frame::Throw {
corr_id,
@@ -1011,6 +1023,10 @@ const STUB_FILES: &[(&str, &str)] = &[
"Composer/Util/Filesystem.php",
include_str!("../php/stubs/Composer/Util/Filesystem.php"),
),
+ (
+ "Composer/Util/ProcessExecutor.php",
+ include_str!("../php/stubs/Composer/Util/ProcessExecutor.php"),
+ ),
];
/// Hand-written worker-side classes (two-world implementations with behavior of their own, not