aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe-external-packages
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-06-22 23:41:12 +0900
committernsfisis <nsfisis@gmail.com>2026-06-22 23:41:12 +0900
commitb291e714bc739262140323e08fe2fb9e91e00ee7 (patch)
treec95f742064b9000e72b902f88e8905b4017d5dbc /crates/shirabe-external-packages
parent99ef82a9807578c1e5749156a027949efaba75c4 (diff)
downloadphp-shirabe-b291e714bc739262140323e08fe2fb9e91e00ee7.tar.gz
php-shirabe-b291e714bc739262140323e08fe2fb9e91e00ee7.tar.zst
php-shirabe-b291e714bc739262140323e08fe2fb9e91e00ee7.zip
feat(php-shim): implement fopen-family stream API on PhpResource
Redesign PhpResource into a real stream handle (File/Memory backing with tracked position, eof, closed state) and unify the whole fopen family (fopen/fwrite/fread/fgets/fgetc/feof/fclose/ftell/fseek/rewind/fstat/ ftruncate/fflush and stream_get_contents/stream_copy_to_stream) on &PhpResource, replacing the split PhpMixed/PhpResource APIs and their todo!() stubs. fopen now returns Result; read functions stay String for now (TODO(phase-e) to move to byte strings). Propagate the signatures through callers: Process stdout/stderr, Cursor input, curl header/body handles (extracted into typed maps keyed by job id), Filesystem copy/safe_copy/files_are_equal, BufferIO, error_handler, platform, perforce, zip. The proc_open pipe paths cannot carry a PhpResource in a PhpMixed list, so they are left as todo!() with notes.
Diffstat (limited to 'crates/shirabe-external-packages')
-rw-r--r--crates/shirabe-external-packages/src/symfony/console/cursor.rs20
-rw-r--r--crates/shirabe-external-packages/src/symfony/console/helper/question_helper.rs56
-rw-r--r--crates/shirabe-external-packages/src/symfony/console/terminal.rs19
-rw-r--r--crates/shirabe-external-packages/src/symfony/process/pipes/abstract_pipes.rs9
-rw-r--r--crates/shirabe-external-packages/src/symfony/process/pipes/unix_pipes.rs9
-rw-r--r--crates/shirabe-external-packages/src/symfony/process/process.rs89
6 files changed, 85 insertions, 117 deletions
diff --git a/crates/shirabe-external-packages/src/symfony/console/cursor.rs b/crates/shirabe-external-packages/src/symfony/console/cursor.rs
index 42efff7..811553f 100644
--- a/crates/shirabe-external-packages/src/symfony/console/cursor.rs
+++ b/crates/shirabe-external-packages/src/symfony/console/cursor.rs
@@ -8,25 +8,15 @@ use std::rc::Rc;
#[derive(Debug)]
pub struct Cursor {
output: Rc<RefCell<dyn OutputInterface>>,
- input: shirabe_php_shim::PhpMixed,
+ input: shirabe_php_shim::PhpResource,
}
impl Cursor {
pub fn new(
output: Rc<RefCell<dyn OutputInterface>>,
- input: Option<shirabe_php_shim::PhpMixed>,
+ input: Option<shirabe_php_shim::PhpResource>,
) -> Self {
- let input = input.unwrap_or_else(|| {
- if shirabe_php_shim::defined("STDIN") {
- // PHP uses the `STDIN` resource constant here, but the shim models it as
- // `PhpResource` while this field is `PhpMixed` (which has no resource
- // variant). Fall back to opening `php://input` so the value stays a
- // PhpMixed stream handle.
- shirabe_php_shim::fopen("php://input", "r+")
- } else {
- shirabe_php_shim::fopen("php://input", "r+")
- }
- });
+ let input = input.unwrap_or(shirabe_php_shim::STDIN);
Self { output, input }
}
@@ -228,10 +218,10 @@ impl Cursor {
let stty_mode = shirabe_php_shim::shell_exec("stty -g");
shirabe_php_shim::shell_exec("stty -icanon -echo");
- shirabe_php_shim::fwrite(self.input.clone(), "\x1b[6n", 0);
+ shirabe_php_shim::fwrite(&self.input, "\x1b[6n", None);
let code = shirabe_php_shim::trim(
- shirabe_php_shim::fread(self.input.clone(), 1024)
+ shirabe_php_shim::fread(&self.input, 1024)
.as_deref()
.unwrap_or(""),
None,
diff --git a/crates/shirabe-external-packages/src/symfony/console/helper/question_helper.rs b/crates/shirabe-external-packages/src/symfony/console/helper/question_helper.rs
index c36f77a..db9c094 100644
--- a/crates/shirabe-external-packages/src/symfony/console/helper/question_helper.rs
+++ b/crates/shirabe-external-packages/src/symfony/console/helper/question_helper.rs
@@ -383,7 +383,7 @@ impl QuestionHelper {
);
// Read a keypress
- while !feof_resource(input_stream) {
+ while !shirabe_php_shim::feof(input_stream) {
while is_stdin
&& 0 == shirabe_php_shim::stream_select(
&mut r,
@@ -396,7 +396,7 @@ impl QuestionHelper {
// Give signal handlers a chance to run
r = vec![input_stream.clone()];
}
- let mut c = fread_resource(input_stream, 1);
+ let mut c = shirabe_php_shim::fread(input_stream, 1);
// as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false.
if c.is_none()
@@ -430,7 +430,7 @@ impl QuestionHelper {
ret = QuestionHelper::substr(Some(&ret), 0, Some(i));
} else if c.as_deref() == Some("\u{1b}") {
// Did we read an escape sequence?
- let escape = fread_resource(input_stream, 2).unwrap_or_default();
+ let escape = shirabe_php_shim::fread(input_stream, 2).unwrap_or_default();
let cc = format!("{}{}", c.clone().unwrap_or_default(), escape);
c = Some(cc.clone());
@@ -510,7 +510,7 @@ impl QuestionHelper {
"\u{f0}" => 3,
_ => 0,
};
- let extra = fread_resource(input_stream, len).unwrap_or_default();
+ let extra = shirabe_php_shim::fread(input_stream, len).unwrap_or_default();
c = Some(format!("{}{}", cur, extra));
}
@@ -649,7 +649,7 @@ impl QuestionHelper {
})));
}
- let value = fgets_resource(input_stream, Some(4096));
+ let value = shirabe_php_shim::fgets(input_stream, Some(4096));
if STTY.load(std::sync::atomic::Ordering::SeqCst) && Terminal::has_stty_available() {
shirabe_php_shim::shell_exec(&format!("stty {}", stty_mode));
@@ -764,7 +764,7 @@ impl QuestionHelper {
) -> PhpMixed {
if !question.is_multiline() {
let cp = self.set_io_codepage();
- let ret = fgets_resource(input_stream, Some(4096));
+ let ret = shirabe_php_shim::fgets(input_stream, Some(4096));
return self.reset_io_codepage(
cp,
@@ -856,16 +856,16 @@ impl QuestionHelper {
let uri = uri?;
- let clone_stream = shirabe_php_shim::php_fopen_resource(&uri, &mode);
+ let clone_stream = shirabe_php_shim::fopen(&uri, &mode).ok()?;
// For seekable and writable streams, add all the same data to the
// cloned stream and then seek to the same offset.
if matches!(seekable, PhpMixed::Bool(true)) && !["r", "rb", "rt"].contains(&mode.as_str()) {
- let offset = shirabe_php_shim::ftell(input_stream);
- rewind_resource(input_stream);
- stream_copy_to_stream_resource(input_stream, &clone_stream);
- fseek_resource(input_stream, offset);
- fseek_resource(&clone_stream, offset);
+ let offset = shirabe_php_shim::ftell(input_stream).unwrap_or(0);
+ shirabe_php_shim::rewind(input_stream);
+ shirabe_php_shim::stream_copy_to_stream(input_stream, &clone_stream);
+ shirabe_php_shim::fseek(input_stream, offset, shirabe_php_shim::SEEK_SET);
+ shirabe_php_shim::fseek(&clone_stream, offset, shirabe_php_shim::SEEK_SET);
}
Some(clone_stream)
@@ -882,44 +882,12 @@ fn question_as_choice_question(question: &Question) -> Option<&ChoiceQuestion> {
question.as_any().downcast_ref::<ChoiceQuestion>()
}
-// PHP operates on raw stream resources, but the shim models `feof`/`fread`/
-// `fgets`/`fseek`/`rewind`/`stream_copy_to_stream` over `PhpMixed`, which has no
-// resource variant. These thin resource-typed wrappers stand in until the shim
-// grows `*_resource` counterparts (see report). Each defers to the real
-// implementation once it exists.
// PHP `__FILE__` magic constant. The shim's `file()` is PHP's file() function,
// not the magic constant, and there is no `__FILE__` shim yet (see report).
fn magic_file() -> String {
todo!("magic_file: shim needs a __FILE__ magic-constant equivalent")
}
-fn feof_resource(_stream: &shirabe_php_shim::PhpResource) -> bool {
- todo!("feof_resource: shim needs a PhpResource-based feof")
-}
-
-fn fread_resource(_stream: &shirabe_php_shim::PhpResource, _length: i64) -> Option<String> {
- todo!("fread_resource: shim needs a PhpResource-based fread")
-}
-
-fn fgets_resource(_stream: &shirabe_php_shim::PhpResource, _length: Option<i64>) -> Option<String> {
- todo!("fgets_resource: shim needs a PhpResource-based fgets")
-}
-
-fn fseek_resource(_stream: &shirabe_php_shim::PhpResource, _offset: i64) -> i64 {
- todo!("fseek_resource: shim needs a PhpResource-based fseek")
-}
-
-fn rewind_resource(_stream: &shirabe_php_shim::PhpResource) -> bool {
- todo!("rewind_resource: shim needs a PhpResource-based rewind")
-}
-
-fn stream_copy_to_stream_resource(
- _source: &shirabe_php_shim::PhpResource,
- _dest: &shirabe_php_shim::PhpResource,
-) -> Option<i64> {
- todo!("stream_copy_to_stream_resource: shim needs a PhpResource-based stream_copy_to_stream")
-}
-
impl HelperInterface for QuestionHelper {
fn set_helper_set(&mut self, helper_set: Option<Rc<RefCell<HelperSet>>>) {
self.inner.set_helper_set(helper_set);
diff --git a/crates/shirabe-external-packages/src/symfony/console/terminal.rs b/crates/shirabe-external-packages/src/symfony/console/terminal.rs
index e0de3f2..ac4009e 100644
--- a/crates/shirabe-external-packages/src/symfony/console/terminal.rs
+++ b/crates/shirabe-external-packages/src/symfony/console/terminal.rs
@@ -219,7 +219,7 @@ impl Terminal {
]),
];
- let cp = if shirabe_php_shim::function_exists("sapi_windows_cp_set") {
+ let _cp = if shirabe_php_shim::function_exists("sapi_windows_cp_set") {
shirabe_php_shim::sapi_windows_cp_get(None)
} else {
0
@@ -233,16 +233,13 @@ impl Terminal {
}
// $pipes[1] and $pipes[2] from proc_open's output array.
- let info = shirabe_php_shim::stream_get_contents(php_pipe(&pipes, 1));
- shirabe_php_shim::fclose(php_pipe(&pipes, 1));
- shirabe_php_shim::fclose(php_pipe(&pipes, 2));
- shirabe_php_shim::proc_close(process);
-
- if cp != 0 {
- shirabe_php_shim::sapi_windows_cp_set(cp);
- }
-
- info
+ let _pipe1 = php_pipe(&pipes, 1);
+ let _pipe2 = php_pipe(&pipes, 2);
+ // TODO(phase-d): the pipe contents are read via stream_get_contents() and the pipes are
+ // fclose()d, but they are PHP stream resources the PhpMixed pipe list cannot hold.
+ todo!(
+ "Terminal: reading proc_open pipes needs PhpResource handles the PhpMixed pipe list cannot carry"
+ );
}
}
diff --git a/crates/shirabe-external-packages/src/symfony/process/pipes/abstract_pipes.rs b/crates/shirabe-external-packages/src/symfony/process/pipes/abstract_pipes.rs
index 47db6b9..d8ced43 100644
--- a/crates/shirabe-external-packages/src/symfony/process/pipes/abstract_pipes.rs
+++ b/crates/shirabe-external-packages/src/symfony/process/pipes/abstract_pipes.rs
@@ -37,13 +37,8 @@ impl AbstractPipes {
}
pub fn close(&mut self) {
- if let PhpMixed::List(pipes) = &self.pipes {
- for pipe in pipes {
- if php::is_resource(pipe) {
- php::fclose(pipe.clone());
- }
- }
- }
+ // TODO(phase-d): each pipe is a PHP stream resource that should be fclose()d, but the pipe
+ // list is a PhpMixed that cannot hold a PhpResource; the handles are dropped instead.
self.pipes = PhpMixed::List(Vec::new());
}
diff --git a/crates/shirabe-external-packages/src/symfony/process/pipes/unix_pipes.rs b/crates/shirabe-external-packages/src/symfony/process/pipes/unix_pipes.rs
index 387bcdf..983ed07 100644
--- a/crates/shirabe-external-packages/src/symfony/process/pipes/unix_pipes.rs
+++ b/crates/shirabe-external-packages/src/symfony/process/pipes/unix_pipes.rs
@@ -44,9 +44,12 @@ fn descriptor(items: &[&str]) -> PhpMixed {
impl PipesInterface for UnixPipes {
fn get_descriptors(&mut self) -> Vec<PhpMixed> {
if !self.have_read_support {
- let nullstream = shirabe_php_shim::fopen("/dev/null", "c");
-
- return vec![descriptor(&["pipe", "r"]), nullstream.clone(), nullstream];
+ // TODO(phase-d): /dev/null is opened as a stream resource and placed directly into the
+ // proc_open descriptor spec, but the descriptor list is a Vec<PhpMixed> that cannot
+ // carry a PhpResource.
+ todo!(
+ "UnixPipes::get_descriptors: the /dev/null resource cannot be represented in a PhpMixed descriptor list"
+ );
}
if self.tty_mode == Some(true) {
diff --git a/crates/shirabe-external-packages/src/symfony/process/process.rs b/crates/shirabe-external-packages/src/symfony/process/process.rs
index fe7b06c..d0d104e 100644
--- a/crates/shirabe-external-packages/src/symfony/process/process.rs
+++ b/crates/shirabe-external-packages/src/symfony/process/process.rs
@@ -3,7 +3,7 @@
use std::sync::OnceLock;
use indexmap::IndexMap;
-use shirabe_php_shim::{self as php, PhpMixed};
+use shirabe_php_shim::{self as php, PhpMixed, PhpResource};
use crate::symfony::process::exception::invalid_argument_exception::InvalidArgumentException;
use crate::symfony::process::exception::logic_exception::LogicException;
@@ -48,8 +48,8 @@ pub struct Process {
fallback_status: IndexMap<String, PhpMixed>,
process_information: Option<IndexMap<String, PhpMixed>>,
output_disabled: bool,
- stdout: PhpMixed,
- stderr: PhpMixed,
+ stdout: Option<PhpResource>,
+ stderr: Option<PhpResource>,
process: PhpMixed,
status: String,
incremental_output_offset: i64,
@@ -199,8 +199,8 @@ impl Process {
fallback_status: IndexMap::new(),
process_information: None,
output_disabled: false,
- stdout: PhpMixed::Null,
- stderr: PhpMixed::Null,
+ stdout: None,
+ stderr: None,
process: PhpMixed::Null,
status: Self::STATUS_READY.to_string(),
incremental_output_offset: 0,
@@ -407,12 +407,12 @@ impl Process {
self.status = Self::STATUS_STARTED.to_string();
if descriptors.len() > 3 {
- let pipe3 = php_pipe(self.process_pipes.as_ref().unwrap().pipes(), 3);
- let pid = php::fgets(pipe3)
- .and_then(|s| s.trim().parse::<i64>().ok())
- .unwrap_or(0);
- self.fallback_status
- .insert("pid".to_string(), PhpMixed::Int(pid));
+ let _pipe3 = php_pipe(self.process_pipes.as_ref().unwrap().pipes(), 3);
+ // TODO(phase-d): the pid is read with fgets() from pipe 3, a PHP stream resource. The
+ // pipe list is a PhpMixed and cannot hold a PhpResource, so this cannot be expressed.
+ todo!(
+ "Process::start: reading the pid from pipe 3 needs a PhpResource the PhpMixed pipe list cannot carry"
+ );
}
if self.tty {
@@ -610,16 +610,19 @@ impl Process {
pub fn get_output(&mut self) -> anyhow::Result<String> {
self.read_pipes_for_output("getOutput", false)?;
- Ok(php::stream_get_contents3(self.stdout.clone(), -1, 0).unwrap_or_default())
+ Ok(php::stream_get_contents3(self.stdout.as_ref().unwrap(), -1, 0).unwrap_or_default())
}
/// Returns the output incrementally.
pub fn get_incremental_output(&mut self) -> anyhow::Result<String> {
self.read_pipes_for_output("getIncrementalOutput", false)?;
- let latest =
- php::stream_get_contents3(self.stdout.clone(), -1, self.incremental_output_offset);
- self.incremental_output_offset = php::ftell_stream(&self.stdout);
+ let latest = php::stream_get_contents3(
+ self.stdout.as_ref().unwrap(),
+ -1,
+ self.incremental_output_offset,
+ );
+ self.incremental_output_offset = php::ftell(self.stdout.as_ref().unwrap()).unwrap_or(0);
Ok(latest.unwrap_or_default())
}
@@ -637,15 +640,15 @@ impl Process {
let mut yields = Vec::new();
while self.callback.is_some()
- || (yield_out && !php::feof(self.stdout.clone()))
- || (yield_err && !php::feof(self.stderr.clone()))
+ || (yield_out && !php::feof(self.stdout.as_ref().unwrap()))
+ || (yield_err && !php::feof(self.stderr.as_ref().unwrap()))
{
let mut got_out = false;
let mut got_err = false;
if yield_out {
let out = php::stream_get_contents3(
- self.stdout.clone(),
+ self.stdout.as_ref().unwrap(),
-1,
self.incremental_output_offset,
)
@@ -656,7 +659,8 @@ impl Process {
if clear_output {
self.clear_output();
} else {
- self.incremental_output_offset = php::ftell_stream(&self.stdout);
+ self.incremental_output_offset =
+ php::ftell(self.stdout.as_ref().unwrap()).unwrap_or(0);
}
yields.push((Self::OUT.to_string(), out));
@@ -665,7 +669,7 @@ impl Process {
if yield_err {
let err = php::stream_get_contents3(
- self.stderr.clone(),
+ self.stderr.as_ref().unwrap(),
-1,
self.incremental_error_output_offset,
)
@@ -676,7 +680,8 @@ impl Process {
if clear_output {
self.clear_error_output();
} else {
- self.incremental_error_output_offset = php::ftell_stream(&self.stderr);
+ self.incremental_error_output_offset =
+ php::ftell(self.stderr.as_ref().unwrap()).unwrap_or(0);
}
yields.push((Self::ERR.to_string(), err));
@@ -696,8 +701,8 @@ impl Process {
/// Clears the process output.
pub fn clear_output(&mut self) -> &mut Self {
- php::ftruncate(&self.stdout, 0);
- php::fseek(self.stdout.clone(), 0);
+ php::ftruncate(self.stdout.as_ref().unwrap(), 0);
+ php::fseek(self.stdout.as_ref().unwrap(), 0, php::SEEK_SET);
self.incremental_output_offset = 0;
self
@@ -707,7 +712,7 @@ impl Process {
pub fn get_error_output(&mut self) -> anyhow::Result<String> {
self.read_pipes_for_output("getErrorOutput", false)?;
- Ok(php::stream_get_contents3(self.stderr.clone(), -1, 0).unwrap_or_default())
+ Ok(php::stream_get_contents3(self.stderr.as_ref().unwrap(), -1, 0).unwrap_or_default())
}
/// Returns the errorOutput incrementally.
@@ -715,19 +720,20 @@ impl Process {
self.read_pipes_for_output("getIncrementalErrorOutput", false)?;
let latest = php::stream_get_contents3(
- self.stderr.clone(),
+ self.stderr.as_ref().unwrap(),
-1,
self.incremental_error_output_offset,
);
- self.incremental_error_output_offset = php::ftell_stream(&self.stderr);
+ self.incremental_error_output_offset =
+ php::ftell(self.stderr.as_ref().unwrap()).unwrap_or(0);
Ok(latest.unwrap_or_default())
}
/// Clears the process error output.
pub fn clear_error_output(&mut self) -> &mut Self {
- php::ftruncate(&self.stderr, 0);
- php::fseek(self.stderr.clone(), 0);
+ php::ftruncate(self.stderr.as_ref().unwrap(), 0);
+ php::fseek(self.stderr.as_ref().unwrap(), 0, php::SEEK_SET);
self.incremental_error_output_offset = 0;
self
@@ -882,18 +888,20 @@ impl Process {
pub fn add_output(&mut self, line: &str) {
self.last_output_time = Some(php::microtime());
- php::fseek3(self.stdout.clone(), 0, php::SEEK_END);
- php::fwrite(self.stdout.clone(), line, line.len() as i64);
- php::fseek(self.stdout.clone(), self.incremental_output_offset);
+ let stdout = self.stdout.as_ref().unwrap();
+ php::fseek(stdout, 0, php::SEEK_END);
+ php::fwrite(stdout, line, Some(line.len() as i64));
+ php::fseek(stdout, self.incremental_output_offset, php::SEEK_SET);
}
/// Adds a line to the STDERR stream.
pub fn add_error_output(&mut self, line: &str) {
self.last_output_time = Some(php::microtime());
- php::fseek3(self.stderr.clone(), 0, php::SEEK_END);
- php::fwrite(self.stderr.clone(), line, line.len() as i64);
- php::fseek(self.stderr.clone(), self.incremental_error_output_offset);
+ let stderr = self.stderr.as_ref().unwrap();
+ php::fseek(stderr, 0, php::SEEK_END);
+ php::fwrite(stderr, line, Some(line.len() as i64));
+ php::fseek(stderr, self.incremental_error_output_offset, php::SEEK_SET);
}
/// Gets the last output time in seconds.
@@ -1398,8 +1406,11 @@ impl Process {
self.exitcode = None;
self.fallback_status = IndexMap::new();
self.process_information = None;
- self.stdout = php::fopen(&format!("php://temp/maxmemory:{}", 1024 * 1024), "w+");
- self.stderr = php::fopen(&format!("php://temp/maxmemory:{}", 1024 * 1024), "w+");
+ // php://temp is an in-memory stream; fopen never fails for it.
+ self.stdout =
+ Some(php::fopen(&format!("php://temp/maxmemory:{}", 1024 * 1024), "w+").unwrap());
+ self.stderr =
+ Some(php::fopen(&format!("php://temp/maxmemory:{}", 1024 * 1024), "w+").unwrap());
self.process = PhpMixed::Null;
self.latest_signal = None;
self.status = Self::STATUS_READY.to_string();
@@ -1458,7 +1469,11 @@ impl Process {
None,
None,
);
- ok = php::php_truthy(&opened) && php::fgets(php_pipe(&pipes, 2)).is_none();
+ let _pipe2 = php_pipe(&pipes, 2);
+ // TODO(phase-d): the original also requires `fgets($pipes[2]) === false`, reading
+ // from a PHP stream resource the PhpMixed pipe list cannot hold; that read is
+ // dropped here.
+ ok = php::php_truthy(&opened);
}
if !ok {
if throw_exception {