diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-06-22 23:41:12 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-06-22 23:41:12 +0900 |
| commit | b291e714bc739262140323e08fe2fb9e91e00ee7 (patch) | |
| tree | c95f742064b9000e72b902f88e8905b4017d5dbc /crates/shirabe-external-packages | |
| parent | 99ef82a9807578c1e5749156a027949efaba75c4 (diff) | |
| download | php-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')
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 { |
