Skip to content

Instantly share code, notes, and snippets.

@masakielastic
Last active February 26, 2026 21:41
Show Gist options
  • Select an option

  • Save masakielastic/2117f2f41ffe699cf3b99a114d7c62e4 to your computer and use it in GitHub Desktop.

Select an option

Save masakielastic/2117f2f41ffe699cf3b99a114d7c62e4 to your computer and use it in GitHub Desktop.
ext-php-rs と embed PHP で実行したスクリプトの結果を取得する

ext-php-rs と embed PHP で実行したスクリプトの結果を取得する

src/main.rs

use ext_php_rs::builders::SapiBuilder;
use ext_php_rs::embed::{ext_php_rs_sapi_shutdown, ext_php_rs_sapi_startup, Embed};
use ext_php_rs::ffi::{
    php_module_shutdown, php_module_startup, php_request_shutdown, php_request_startup, sapi_shutdown,
    sapi_startup, ZEND_RESULT_CODE_SUCCESS,
};
use ext_php_rs::zend::try_catch_first;

use std::ffi::c_char;
use std::sync::Mutex;

// PHP は複数SAPI同時起動が苦手なので直列化(tests/sapi.rs でも同様):contentReference[oaicite:3]{index=3}
static SAPI_MUTEX: Mutex<()> = Mutex::new(());
static OUTPUT: Mutex<String> = Mutex::new(String::new());

// ub_write: PHP の unbuffered output をここで受け取る:contentReference[oaicite:4]{index=4}
extern "C" fn ub_write_capture(buf: *const c_char, len: usize) -> usize {
    if buf.is_null() || len == 0 {
        return 0;
    }
    let bytes = unsafe { std::slice::from_raw_parts(buf as *const u8, len) };
    let s = String::from_utf8_lossy(bytes);

    // 追記して回収
    let mut out = OUTPUT.lock().unwrap();
    out.push_str(&s);
    len
}

fn main() {
    let _guard = SAPI_MUTEX.lock().unwrap();

    // 1) SAPI Module を構築(ub_write を差し替え)
    let sapi = SapiBuilder::new("capture", "Capture SAPI")
        .ub_write_function(ub_write_capture)
        .build()
        .expect("build sapi")
        .into_raw();

    // 2) ライフサイクル開始(C shim + sapi_startup + module_startup)
    unsafe { ext_php_rs_sapi_startup() };
    unsafe { sapi_startup(sapi) };
    unsafe { php_module_startup(sapi, std::ptr::null_mut()) };

    // 3) “1リクエスト” を開始(これをやらないと多くのAPIが動きません)
    let rc = unsafe { php_request_startup() };
    assert_eq!(rc, ZEND_RESULT_CODE_SUCCESS);

    // 4) PHP を実行(例:ファイル実行)
    let _ = try_catch_first(|| {
        // 出力を回収したいので run_script を使う(echo が ub_write に流れる)
        Embed::run_script("script.php").expect("run_script failed");

        // 返り値が欲しいなら include を eval で:
        // let z = Embed::eval("include 'script.php';").unwrap();
        // eprintln!("return zval = {:?}", z);
    });

    // 5) リクエスト終了 → module/sapi 終了
    unsafe { php_request_shutdown(std::ptr::null_mut()) };
    unsafe { php_module_shutdown() };
    unsafe { sapi_shutdown() };
    unsafe { ext_php_rs_sapi_shutdown() };

    // 6) 回収結果を表示
    let out = OUTPUT.lock().unwrap().clone();
    println!("=== captured output ===\n{out}");
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment