aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe-external-packages
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-08-09 11:14:42 +0900
committernsfisis <nsfisis@gmail.com>2026-08-09 11:14:42 +0900
commit446f719f6c34453f027d5ccdbf81b16e63f4c982 (patch)
tree9c0443adbb99da5891a81d0131fc414d9a04c8ef /crates/shirabe-external-packages
parent880ba0fd1d05faf98588cc326b3d9fbe625ebf2b (diff)
downloadphp-shirabe-446f719f6c34453f027d5ccdbf81b16e63f4c982.tar.gz
php-shirabe-446f719f6c34453f027d5ccdbf81b16e63f4c982.tar.zst
php-shirabe-446f719f6c34453f027d5ccdbf81b16e63f4c982.zip
refactor(symfony-process): extract symfony/process into the shirabe-symfony-process crate
Move `Symfony\Component\Process` out of shirabe-external-packages and into its own crate, so the path is `shirabe_symfony_process::Process` instead of `shirabe_external_packages::symfony::process::Process`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe-external-packages')
-rw-r--r--crates/shirabe-external-packages/Cargo.toml1
-rw-r--r--crates/shirabe-external-packages/src/symfony.rs1
-rw-r--r--crates/shirabe-external-packages/src/symfony/console/command/dump_completion_command.rs2
-rw-r--r--crates/shirabe-external-packages/src/symfony/console/helper/process_helper.rs4
-rw-r--r--crates/shirabe-external-packages/src/symfony/process.rs11
-rw-r--r--crates/shirabe-external-packages/src/symfony/process/exception.rs13
-rw-r--r--crates/shirabe-external-packages/src/symfony/process/exception/invalid_argument_exception.rs20
-rw-r--r--crates/shirabe-external-packages/src/symfony/process/exception/logic_exception.rs20
-rw-r--r--crates/shirabe-external-packages/src/symfony/process/exception/process_failed_exception.rs48
-rw-r--r--crates/shirabe-external-packages/src/symfony/process/exception/process_signaled_exception.rs34
-rw-r--r--crates/shirabe-external-packages/src/symfony/process/exception/process_timed_out_exception.rs31
-rw-r--r--crates/shirabe-external-packages/src/symfony/process/exception/runtime_exception.rs20
-rw-r--r--crates/shirabe-external-packages/src/symfony/process/executable_finder.rs116
-rw-r--r--crates/shirabe-external-packages/src/symfony/process/php_executable_finder.rs70
-rw-r--r--crates/shirabe-external-packages/src/symfony/process/pipes.rs4
-rw-r--r--crates/shirabe-external-packages/src/symfony/process/pipes/abstract_pipes.rs104
-rw-r--r--crates/shirabe-external-packages/src/symfony/process/pipes/pipes_interface.rs28
-rw-r--r--crates/shirabe-external-packages/src/symfony/process/pipes/unix_pipes.rs136
-rw-r--r--crates/shirabe-external-packages/src/symfony/process/pipes/windows_pipes.rs60
-rw-r--r--crates/shirabe-external-packages/src/symfony/process/process.rs1293
-rw-r--r--crates/shirabe-external-packages/src/symfony/process/process_utils.rs43
21 files changed, 4 insertions, 2055 deletions
diff --git a/crates/shirabe-external-packages/Cargo.toml b/crates/shirabe-external-packages/Cargo.toml
index c1e70046..c9897725 100644
--- a/crates/shirabe-external-packages/Cargo.toml
+++ b/crates/shirabe-external-packages/Cargo.toml
@@ -7,6 +7,7 @@ edition.workspace = true
shirabe-pcre.workspace = true
shirabe-php-shim.workspace = true
shirabe-semver.workspace = true
+shirabe-symfony-process.workspace = true
shirabe-symfony-string.workspace = true
anyhow.workspace = true
chrono.workspace = true
diff --git a/crates/shirabe-external-packages/src/symfony.rs b/crates/shirabe-external-packages/src/symfony.rs
index e0d9e4fe..23422146 100644
--- a/crates/shirabe-external-packages/src/symfony.rs
+++ b/crates/shirabe-external-packages/src/symfony.rs
@@ -1,4 +1,3 @@
pub mod console;
pub mod filesystem;
pub mod finder;
-pub mod process;
diff --git a/crates/shirabe-external-packages/src/symfony/console/command/dump_completion_command.rs b/crates/shirabe-external-packages/src/symfony/console/command/dump_completion_command.rs
index 31a94f0f..cc46366c 100644
--- a/crates/shirabe-external-packages/src/symfony/console/command/dump_completion_command.rs
+++ b/crates/shirabe-external-packages/src/symfony/console/command/dump_completion_command.rs
@@ -9,8 +9,8 @@ use crate::symfony::console::input::input_argument::InputArgument;
use crate::symfony::console::input::input_interface::InputInterface;
use crate::symfony::console::input::input_option::InputOption;
use crate::symfony::console::output::output_interface::{self, OutputInterface};
-use crate::symfony::process::process::Process;
use shirabe_php_shim::{PhpMixed, impl_php_class};
+use shirabe_symfony_process::process::Process;
use std::ops::{Deref, DerefMut};
/// __DIR__.'/../Resources/completion.bash', embedded at compile time (this port ships as a
diff --git a/crates/shirabe-external-packages/src/symfony/console/helper/process_helper.rs b/crates/shirabe-external-packages/src/symfony/console/helper/process_helper.rs
index 0f81348d..303a4569 100644
--- a/crates/shirabe-external-packages/src/symfony/console/helper/process_helper.rs
+++ b/crates/shirabe-external-packages/src/symfony/console/helper/process_helper.rs
@@ -6,8 +6,8 @@ use crate::symfony::console::helper::helper_interface::HelperInterface;
use crate::symfony::console::helper::helper_set::HelperSet;
use crate::symfony::console::output::ConsoleOutputInterface;
use crate::symfony::console::output::output_interface::{self, OutputInterface};
-use crate::symfony::process::exception::process_failed_exception::ProcessFailedException;
-use crate::symfony::process::process::Process;
+use shirabe_symfony_process::exception::process_failed_exception::ProcessFailedException;
+use shirabe_symfony_process::process::Process;
/// The ProcessHelper class provides helpers to run external processes.
///
diff --git a/crates/shirabe-external-packages/src/symfony/process.rs b/crates/shirabe-external-packages/src/symfony/process.rs
deleted file mode 100644
index 3a5656c6..00000000
--- a/crates/shirabe-external-packages/src/symfony/process.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-pub mod exception;
-pub mod executable_finder;
-pub mod php_executable_finder;
-pub(crate) mod pipes;
-pub mod process;
-pub(crate) mod process_utils;
-
-pub use exception::*;
-pub use executable_finder::*;
-pub use php_executable_finder::*;
-pub use process::*;
diff --git a/crates/shirabe-external-packages/src/symfony/process/exception.rs b/crates/shirabe-external-packages/src/symfony/process/exception.rs
deleted file mode 100644
index 689a64b6..00000000
--- a/crates/shirabe-external-packages/src/symfony/process/exception.rs
+++ /dev/null
@@ -1,13 +0,0 @@
-pub mod invalid_argument_exception;
-pub mod logic_exception;
-pub mod process_failed_exception;
-pub mod process_signaled_exception;
-pub mod process_timed_out_exception;
-pub mod runtime_exception;
-
-pub use invalid_argument_exception::*;
-pub use logic_exception::*;
-pub use process_failed_exception::*;
-pub use process_signaled_exception::*;
-pub use process_timed_out_exception::*;
-pub use runtime_exception::*;
diff --git a/crates/shirabe-external-packages/src/symfony/process/exception/invalid_argument_exception.rs b/crates/shirabe-external-packages/src/symfony/process/exception/invalid_argument_exception.rs
deleted file mode 100644
index c9a42653..00000000
--- a/crates/shirabe-external-packages/src/symfony/process/exception/invalid_argument_exception.rs
+++ /dev/null
@@ -1,20 +0,0 @@
-//! ref: composer/vendor/symfony/process/Exception/InvalidArgumentException.php
-
-#[derive(Debug)]
-pub struct InvalidArgumentException {
- inner: shirabe_php_shim::InvalidArgumentException,
-}
-
-impl InvalidArgumentException {
- pub fn new(message: String) -> Self {
- Self {
- inner: shirabe_php_shim::InvalidArgumentException::new(message),
- }
- }
-}
-
-shirabe_php_shim::impl_php_exception!(
- InvalidArgumentException,
- inner,
- r"Symfony\Component\Process\Exception\InvalidArgumentException"
-);
diff --git a/crates/shirabe-external-packages/src/symfony/process/exception/logic_exception.rs b/crates/shirabe-external-packages/src/symfony/process/exception/logic_exception.rs
deleted file mode 100644
index 36e9bcca..00000000
--- a/crates/shirabe-external-packages/src/symfony/process/exception/logic_exception.rs
+++ /dev/null
@@ -1,20 +0,0 @@
-//! ref: composer/vendor/symfony/process/Exception/LogicException.php
-
-#[derive(Debug)]
-pub struct LogicException {
- inner: shirabe_php_shim::LogicException,
-}
-
-impl LogicException {
- pub fn new(message: String) -> Self {
- Self {
- inner: shirabe_php_shim::LogicException::new(message),
- }
- }
-}
-
-shirabe_php_shim::impl_php_exception!(
- LogicException,
- inner,
- r"Symfony\Component\Process\Exception\LogicException"
-);
diff --git a/crates/shirabe-external-packages/src/symfony/process/exception/process_failed_exception.rs b/crates/shirabe-external-packages/src/symfony/process/exception/process_failed_exception.rs
deleted file mode 100644
index e5d6d7fc..00000000
--- a/crates/shirabe-external-packages/src/symfony/process/exception/process_failed_exception.rs
+++ /dev/null
@@ -1,48 +0,0 @@
-//! ref: composer/vendor/symfony/process/Exception/ProcessFailedException.php
-
-use crate::symfony::process::exception::invalid_argument_exception::InvalidArgumentException;
-use crate::symfony::process::exception::runtime_exception::RuntimeException;
-use crate::symfony::process::process::Process;
-
-#[derive(Debug)]
-pub struct ProcessFailedException {
- inner: RuntimeException,
-}
-
-impl ProcessFailedException {
- pub fn new(process: &mut Process) -> anyhow::Result<Self> {
- if process.is_successful() {
- return Err(InvalidArgumentException::new(
- "Expected a failed process, but the given process was successful.".to_string(),
- )
- .into());
- }
-
- let mut error = format!(
- "The command \"{}\" failed.\n\nExit Code: {}({})\n\nWorking directory: {}",
- process.get_command_line(),
- process
- .get_exit_code()
- .map(|c| c.to_string())
- .unwrap_or_default(),
- process.get_exit_code_text().unwrap_or_default(),
- process.get_working_directory().unwrap_or_default(),
- );
-
- error += &format!(
- "\n\nOutput:\n================\n{}\n\nError Output:\n================\n{}",
- process.get_output()?,
- process.get_error_output()?,
- );
-
- Ok(Self {
- inner: RuntimeException::new(error),
- })
- }
-}
-
-shirabe_php_shim::impl_php_exception!(
- ProcessFailedException,
- inner,
- r"Symfony\Component\Process\Exception\ProcessFailedException"
-);
diff --git a/crates/shirabe-external-packages/src/symfony/process/exception/process_signaled_exception.rs b/crates/shirabe-external-packages/src/symfony/process/exception/process_signaled_exception.rs
deleted file mode 100644
index 9f325750..00000000
--- a/crates/shirabe-external-packages/src/symfony/process/exception/process_signaled_exception.rs
+++ /dev/null
@@ -1,34 +0,0 @@
-//! ref: composer/vendor/symfony/process/Exception/ProcessSignaledException.php
-
-use crate::symfony::process::exception::runtime_exception::RuntimeException;
-use crate::symfony::process::process::Process;
-
-#[derive(Debug)]
-pub struct ProcessSignaledException {
- inner: RuntimeException,
- signal: i64,
-}
-
-impl ProcessSignaledException {
- pub fn new(process: &mut Process) -> anyhow::Result<Self> {
- let signal = process.get_term_signal()?;
-
- Ok(Self {
- inner: RuntimeException::new(format!(
- "The process has been signaled with signal \"{}\".",
- signal
- )),
- signal,
- })
- }
-
- pub fn get_signal(&self) -> i64 {
- self.signal
- }
-}
-
-shirabe_php_shim::impl_php_exception!(
- ProcessSignaledException,
- inner,
- r"Symfony\Component\Process\Exception\ProcessSignaledException"
-);
diff --git a/crates/shirabe-external-packages/src/symfony/process/exception/process_timed_out_exception.rs b/crates/shirabe-external-packages/src/symfony/process/exception/process_timed_out_exception.rs
deleted file mode 100644
index 9485d1c2..00000000
--- a/crates/shirabe-external-packages/src/symfony/process/exception/process_timed_out_exception.rs
+++ /dev/null
@@ -1,31 +0,0 @@
-//! ref: composer/vendor/symfony/process/Exception/ProcessTimedOutException.php
-
-use crate::symfony::process::exception::runtime_exception::RuntimeException;
-use crate::symfony::process::process::Process;
-
-#[derive(Debug)]
-pub struct ProcessTimedOutException {
- inner: RuntimeException,
-}
-
-impl ProcessTimedOutException {
- pub fn new(process: &Process) -> Self {
- let exceeded_timeout = process.get_timeout();
-
- let message = format!(
- "The process \"{}\" exceeded the timeout of {} seconds.",
- process.get_command_line(),
- exceeded_timeout.map(|t| t.to_string()).unwrap_or_default(),
- );
-
- Self {
- inner: RuntimeException::new(message),
- }
- }
-}
-
-shirabe_php_shim::impl_php_exception!(
- ProcessTimedOutException,
- inner,
- r"Symfony\Component\Process\Exception\ProcessTimedOutException"
-);
diff --git a/crates/shirabe-external-packages/src/symfony/process/exception/runtime_exception.rs b/crates/shirabe-external-packages/src/symfony/process/exception/runtime_exception.rs
deleted file mode 100644
index e9cc31e0..00000000
--- a/crates/shirabe-external-packages/src/symfony/process/exception/runtime_exception.rs
+++ /dev/null
@@ -1,20 +0,0 @@
-//! ref: composer/vendor/symfony/process/Exception/RuntimeException.php
-
-#[derive(Debug)]
-pub struct RuntimeException {
- inner: shirabe_php_shim::RuntimeException,
-}
-
-impl RuntimeException {
- pub fn new(message: String) -> Self {
- Self {
- inner: shirabe_php_shim::RuntimeException::new(message),
- }
- }
-}
-
-shirabe_php_shim::impl_php_exception!(
- RuntimeException,
- inner,
- r"Symfony\Component\Process\Exception\RuntimeException"
-);
diff --git a/crates/shirabe-external-packages/src/symfony/process/executable_finder.rs b/crates/shirabe-external-packages/src/symfony/process/executable_finder.rs
deleted file mode 100644
index 5b1c0994..00000000
--- a/crates/shirabe-external-packages/src/symfony/process/executable_finder.rs
+++ /dev/null
@@ -1,116 +0,0 @@
-//! ref: composer/vendor/symfony/process/ExecutableFinder.php
-
-const CMD_BUILTINS: &[&str] = &[
- "assoc", "break", "call", "cd", "chdir", "cls", "color", "copy", "date", "del", "dir", "echo",
- "endlocal", "erase", "exit", "for", "ftype", "goto", "help", "if", "label", "md", "mkdir",
- "mklink", "move", "path", "pause", "popd", "prompt", "pushd", "rd", "rem", "ren", "rename",
- "rmdir", "set", "setlocal", "shift", "start", "time", "title", "type", "ver", "vol",
-];
-
-#[derive(Debug)]
-pub struct ExecutableFinder {
- suffixes: Vec<String>,
-}
-
-impl Default for ExecutableFinder {
- fn default() -> Self {
- Self::new()
- }
-}
-
-impl ExecutableFinder {
- pub fn new() -> Self {
- Self { suffixes: vec![] }
- }
-
- pub fn find(&self, name: &str, default: Option<&str>, extra_dirs: &[String]) -> Option<String> {
- // windows built-in commands that are present in cmd.exe should not be resolved using PATH as they do not exist as exes
- if cfg!(windows) && CMD_BUILTINS.contains(&shirabe_php_shim::strtolower(name).as_str()) {
- return Some(name.to_string());
- }
-
- let path = shirabe_php_shim::getenv("PATH")
- .or_else(|| shirabe_php_shim::getenv("Path"))
- .map(|v| v.to_string_lossy().into_owned())
- .unwrap_or_default();
- let mut dirs: Vec<String> = std::env::split_paths(&path)
- .map(|dir| dir.into_os_string().into_string().unwrap())
- .collect();
- dirs.extend_from_slice(extra_dirs);
-
- let mut suffixes: Vec<String> = vec![];
- if cfg!(windows) {
- let path_ext =
- shirabe_php_shim::getenv("PATHEXT").map(|v| v.to_string_lossy().into_owned());
- suffixes = self.suffixes.clone();
- let exts = match path_ext {
- Some(ref ext) if !ext.is_empty() => std::env::split_paths(ext)
- .map(|e| e.into_os_string().into_string().unwrap())
- .collect(),
- _ => vec![
- ".exe".to_string(),
- ".bat".to_string(),
- ".cmd".to_string(),
- ".com".to_string(),
- ],
- };
- suffixes.extend(exts);
- }
- suffixes =
- if !shirabe_php_shim::pathinfo(name, shirabe_php_shim::PATHINFO_EXTENSION).is_empty() {
- let mut s = vec![String::new()];
- s.extend(suffixes);
- s
- } else {
- suffixes.push(String::new());
- suffixes
- };
- for suffix in &suffixes {
- for dir in &dirs {
- let dir = if dir.is_empty() { "." } else { dir.as_str() };
- let file = std::path::Path::new(dir)
- .join(format!("{name}{suffix}"))
- .into_os_string()
- .into_string()
- .unwrap();
- if shirabe_php_shim::is_file(&file)
- && (cfg!(windows) || shirabe_php_shim::is_executable(&file))
- {
- return Some(file);
- }
-
- if !shirabe_php_shim::is_dir(dir)
- && shirabe_php_shim::basename(dir) == format!("{name}{suffix}")
- && shirabe_php_shim::is_executable(dir)
- {
- return Some(dir.to_string());
- }
- }
- }
-
- if cfg!(windows)
- || name.len()
- != shirabe_php_shim::strcspn(name, &format!("/{}", std::path::MAIN_SEPARATOR))
- {
- return default.map(ToString::to_string);
- }
-
- let exec_result = shirabe_php_shim::exec(
- &format!("command -v -- {}", shirabe_php_shim::escapeshellarg(name)),
- None,
- None,
- )
- .unwrap_or_default();
-
- let executable_path = shirabe_php_shim::substr(
- &exec_result,
- 0,
- shirabe_php_shim::strpos(&exec_result, shirabe_php_shim::PHP_EOL).map(|i| i as i64),
- );
- if !executable_path.is_empty() && shirabe_php_shim::is_executable(&executable_path) {
- return Some(executable_path);
- }
-
- default.map(ToString::to_string)
- }
-}
diff --git a/crates/shirabe-external-packages/src/symfony/process/php_executable_finder.rs b/crates/shirabe-external-packages/src/symfony/process/php_executable_finder.rs
deleted file mode 100644
index e8661866..00000000
--- a/crates/shirabe-external-packages/src/symfony/process/php_executable_finder.rs
+++ /dev/null
@@ -1,70 +0,0 @@
-//! ref: composer/vendor/symfony/process/PhpExecutableFinder.php
-
-use super::executable_finder::ExecutableFinder;
-
-#[derive(Debug)]
-pub struct PhpExecutableFinder {
- executable_finder: ExecutableFinder,
-}
-
-impl Default for PhpExecutableFinder {
- fn default() -> Self {
- Self::new()
- }
-}
-
-impl PhpExecutableFinder {
- pub fn new() -> Self {
- Self {
- executable_finder: ExecutableFinder::new(),
- }
- }
-
- /// Finds The PHP executable.
- pub fn find(&self, _include_args: bool) -> Option<String> {
- if let Some(php) = shirabe_php_shim::getenv("PHP_BINARY").filter(|v| !v.is_empty()) {
- let mut php = php.to_string_lossy().into_owned();
- if !shirabe_php_shim::is_executable(&php) {
- match self.executable_finder.find(&php, None, &[]) {
- Some(found) => php = found,
- None => return None,
- }
- }
-
- if shirabe_php_shim::is_dir(&php) {
- return None;
- }
-
- return Some(php);
- }
-
- // The original `\PHP_BINARY && \PHP_SAPI` branch describes the running PHP interpreter.
- // These constants cannot be obtained in Rust, the branch is skipped here.
-
- if let Some(php) = shirabe_php_shim::getenv("PHP_PATH").filter(|v| !v.is_empty()) {
- let php = php.to_string_lossy().into_owned();
- if !shirabe_php_shim::is_executable(&php) || shirabe_php_shim::is_dir(&php) {
- return None;
- }
-
- return Some(php);
- }
-
- if let Some(php) = shirabe_php_shim::getenv("PHP_PEAR_PHP_BIN").filter(|v| !v.is_empty()) {
- let php = php.to_string_lossy().into_owned();
- if shirabe_php_shim::is_executable(&php) && !shirabe_php_shim::is_dir(&php) {
- return Some(php);
- }
- }
-
- // Even if `\PHP_BINDIR` is unavailable, searching `$PATH` should be performed.
- self.executable_finder.find("php", None, &[])
- }
-
- /// Finds the PHP executable arguments.
- pub fn find_arguments(&self) -> Vec<String> {
- // If PHP_SAPI is not "phpdbg", returns an empty array. In Rust, PHP_SAPI is always "cli",
- // so always returns an empty array.
- vec![]
- }
-}
diff --git a/crates/shirabe-external-packages/src/symfony/process/pipes.rs b/crates/shirabe-external-packages/src/symfony/process/pipes.rs
deleted file mode 100644
index 6c3ea7dd..00000000
--- a/crates/shirabe-external-packages/src/symfony/process/pipes.rs
+++ /dev/null
@@ -1,4 +0,0 @@
-pub mod abstract_pipes;
-pub mod pipes_interface;
-pub mod unix_pipes;
-pub mod windows_pipes;
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
deleted file mode 100644
index 8741926c..00000000
--- a/crates/shirabe-external-packages/src/symfony/process/pipes/abstract_pipes.rs
+++ /dev/null
@@ -1,104 +0,0 @@
-//! ref: composer/vendor/symfony/process/Pipes/AbstractPipes.php
-
-use indexmap::IndexMap;
-use shirabe_php_shim::{PhpMixed, PhpResource};
-
-#[derive(Debug)]
-pub struct AbstractPipes {
- pub pipes: IndexMap<i64, PhpResource>,
-
- input_buffer: String,
- input: PhpMixed,
- blocked: bool,
- last_error: Option<String>,
-}
-
-impl AbstractPipes {
- pub fn new(input: PhpMixed) -> Self {
- let input_buffer;
- let stored_input;
- // TODO(plugin): `$input instanceof \Iterator` is not modeled. The PHP `is_resource($input)`
- // branch never applies: a PhpMixed is never a resource, so input is never stored as-is here.
- if let PhpMixed::String(s) = &input {
- input_buffer = s.clone();
- stored_input = PhpMixed::Null;
- } else {
- input_buffer = input.as_string().map(|s| s.to_string()).unwrap_or_default();
- stored_input = PhpMixed::Null;
- }
-
- Self {
- pipes: IndexMap::new(),
- input_buffer,
- input: stored_input,
- blocked: true,
- last_error: None,
- }
- }
-
- pub fn close(&mut self) {
- for (_, pipe) in &self.pipes {
- shirabe_php_shim::fclose(pipe);
- }
- self.pipes = IndexMap::new();
- }
-
- /// Returns true if a system call has been interrupted.
- pub(crate) fn has_system_call_been_interrupted(&mut self) -> bool {
- let last_error = self.last_error.take();
-
- // stream_select returns false when the `select` system call is interrupted by an incoming signal
- last_error
- .map(|e| e.to_lowercase().contains("interrupted system call"))
- .unwrap_or(false)
- }
-
- /// Unblocks streams.
- pub(crate) fn unblock(&mut self) {
- if !self.blocked {
- return;
- }
-
- for (_, pipe) in &self.pipes {
- shirabe_php_shim::stream_set_blocking(pipe, false);
- }
- // The `is_resource($this->input)` branch does not apply: `input` is never a resource in this
- // port (is_resource on a PhpMixed is always false).
-
- self.blocked = false;
- }
-
- /// Writes input to stdin.
- pub(crate) fn write(&mut self) -> Option<Vec<PhpResource>> {
- let stdin = self.pipes.get(&0)?.clone();
-
- // TODO(plugin): the `$input instanceof \Iterator` branch is not modeled. `input` is never a
- // resource here, so the fread($input)/stream_set_blocking($input) paths do not apply and
- // only the input buffer is written to stdin.
-
- let mut r: Vec<PhpResource> = Vec::new();
- let mut e: Vec<PhpResource> = Vec::new();
- let mut w: Vec<PhpResource> = vec![stdin.clone()];
-
- // let's have a look if something changed in streams
- shirabe_php_shim::stream_select(&mut r, &mut w, &mut e, 0, Some(0))?;
-
- if !self.input_buffer.is_empty() {
- let written =
- shirabe_php_shim::fwrite(&stdin, &self.input_buffer, None).unwrap_or(0) as usize;
- self.input_buffer = self.input_buffer.get(written..).unwrap_or("").to_string();
- if !self.input_buffer.is_empty() {
- return Some(vec![stdin]);
- }
- }
-
- // no input to read on resource, buffer is empty
- if self.input_buffer.is_empty() && !shirabe_php_shim::php_truthy(&self.input) {
- self.input = PhpMixed::Null;
- shirabe_php_shim::fclose(&stdin);
- self.pipes.shift_remove(&0);
- }
-
- None
- }
-}
diff --git a/crates/shirabe-external-packages/src/symfony/process/pipes/pipes_interface.rs b/crates/shirabe-external-packages/src/symfony/process/pipes/pipes_interface.rs
deleted file mode 100644
index 46609bb8..00000000
--- a/crates/shirabe-external-packages/src/symfony/process/pipes/pipes_interface.rs
+++ /dev/null
@@ -1,28 +0,0 @@
-//! ref: composer/vendor/symfony/process/Pipes/PipesInterface.php
-
-use indexmap::IndexMap;
-use shirabe_php_shim::{Descriptor, PhpResource};
-
-pub const CHUNK_SIZE: i64 = 16384;
-
-/// PipesInterface manages descriptors and pipes for the use of proc_open.
-pub trait PipesInterface: std::fmt::Debug {
- /// Returns an array of descriptors for the use of proc_open.
- fn get_descriptors(&mut self) -> Vec<Descriptor>;
-
- /// Returns an array of filenames indexed by their related stream in case these pipes use temporary files.
- fn get_files(&self) -> IndexMap<i64, String>;
-
- /// Reads data in file handles and pipes.
- fn read_and_write(&mut self, blocking: bool, close: bool) -> IndexMap<i64, String>;
-
- /// Returns if the current state has open file handles or pipes.
- fn are_open(&self) -> bool;
-
- /// Closes file handles and pipes.
- fn close(&mut self);
-
- /// Accessor for the `pipes` property populated by proc_open, keyed by fd index.
- fn pipes(&self) -> &IndexMap<i64, PhpResource>;
- fn pipes_mut(&mut self) -> &mut IndexMap<i64, PhpResource>;
-}
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
deleted file mode 100644
index a7026318..00000000
--- a/crates/shirabe-external-packages/src/symfony/process/pipes/unix_pipes.rs
+++ /dev/null
@@ -1,136 +0,0 @@
-//! ref: composer/vendor/symfony/process/Pipes/UnixPipes.php
-
-use crate::symfony::process::pipes::abstract_pipes::AbstractPipes;
-use crate::symfony::process::pipes::pipes_interface::{CHUNK_SIZE, PipesInterface};
-use crate::symfony::process::process::Process;
-use indexmap::IndexMap;
-use shirabe_php_shim::{Descriptor, PhpMixed, PhpResource};
-
-/// UnixPipes implementation uses unix pipes as handles.
-#[derive(Debug)]
-pub struct UnixPipes {
- inner: AbstractPipes,
- tty_mode: Option<bool>,
-}
-
-impl UnixPipes {
- pub fn new(tty_mode: Option<bool>, input: PhpMixed) -> Self {
- Self {
- inner: AbstractPipes::new(input),
- tty_mode,
- }
- }
-}
-
-fn descriptor(items: &[&str]) -> Descriptor {
- match items {
- ["pipe", mode] => Descriptor::Pipe(mode.to_string()),
- ["file", path, mode] => Descriptor::File(path.to_string(), mode.to_string()),
- _ => panic!("unsupported descriptor spec: {:?}", items),
- }
-}
-
-impl PipesInterface for UnixPipes {
- fn get_descriptors(&mut self) -> Vec<Descriptor> {
- if self.tty_mode == Some(true) {
- return vec![
- descriptor(&["file", "/dev/tty", "r"]),
- descriptor(&["file", "/dev/tty", "w"]),
- descriptor(&["file", "/dev/tty", "w"]),
- ];
- }
-
- vec![
- descriptor(&["pipe", "r"]),
- descriptor(&["pipe", "w"]),
- descriptor(&["pipe", "w"]),
- ]
- }
-
- fn get_files(&self) -> IndexMap<i64, String> {
- IndexMap::new()
- }
-
- fn read_and_write(&mut self, blocking: bool, close: bool) -> IndexMap<i64, String> {
- self.inner.unblock();
- let w = self.inner.write();
-
- let mut read: IndexMap<i64, String> = IndexMap::new();
- // $r = $this->pipes; unset($r[0]);
- let r: Vec<(i64, PhpResource)> = self
- .inner
- .pipes
- .iter()
- .filter(|(fd, _)| **fd != 0)
- .map(|(fd, pipe)| (*fd, pipe.clone()))
- .collect();
-
- // TODO(plugin): set_error_handler/restore_error_handler around stream_select is not modeled.
- let mut r_sel: Vec<PhpResource> = r.iter().map(|(_, p)| p.clone()).collect();
- let mut w_sel: Vec<PhpResource> = w.clone().unwrap_or_default();
- let mut e_sel: Vec<PhpResource> = Vec::new();
-
- // let's have a look if something changed in streams
- if (!r_sel.is_empty() || w.is_some())
- && shirabe_php_shim::stream_select(
- &mut r_sel,
- &mut w_sel,
- &mut e_sel,
- 0,
- Some(if blocking {
- (Process::TIMEOUT_PRECISION * 1e6) as i64
- } else {
- 0
- }),
- )
- .is_none()
- {
- // if a system call has been interrupted, forget about it, let's try again
- // otherwise, an error occurred, let's reset pipes
- if !self.inner.has_system_call_been_interrupted() {
- self.inner.pipes = IndexMap::new();
- }
-
- return read;
- }
-
- for (fd, pipe) in &r {
- let mut data = String::new();
- loop {
- let chunk = shirabe_php_shim::fread(pipe, CHUNK_SIZE).unwrap_or_default();
- let len = chunk.len() as i64;
- data.push_str(&chunk);
- if !(len > 0 && (close || len >= CHUNK_SIZE)) {
- break;
- }
- }
-
- if !data.is_empty() {
- read.insert(*fd, data);
- }
-
- if close && shirabe_php_shim::feof(pipe) {
- shirabe_php_shim::fclose(pipe);
- self.inner.pipes.shift_remove(fd);
- }
- }
-
- read
- }
-
- fn are_open(&self) -> bool {
- !self.inner.pipes.is_empty()
- }
-
- fn close(&mut self) {
- self.inner.close();
- }
-
- fn pipes(&self) -> &IndexMap<i64, PhpResource> {
- &self.inner.pipes
- }
-
- fn pipes_mut(&mut self) -> &mut IndexMap<i64, PhpResource> {
- &mut self.inner.pipes
- }
-}
diff --git a/crates/shirabe-external-packages/src/symfony/process/pipes/windows_pipes.rs b/crates/shirabe-external-packages/src/symfony/process/pipes/windows_pipes.rs
deleted file mode 100644
index cf4f3414..00000000
--- a/crates/shirabe-external-packages/src/symfony/process/pipes/windows_pipes.rs
+++ /dev/null
@@ -1,60 +0,0 @@
-//! ref: composer/vendor/symfony/process/Pipes/WindowsPipes.php
-
-use crate::symfony::process::pipes::abstract_pipes::AbstractPipes;
-use crate::symfony::process::pipes::pipes_interface::PipesInterface;
-use indexmap::IndexMap;
-use shirabe_php_shim::{Descriptor, PhpMixed, PhpResource};
-
-/// WindowsPipes implementation uses temporary files as handles.
-#[derive(Debug)]
-pub struct WindowsPipes {
- inner: AbstractPipes,
- files: IndexMap<i64, String>,
- file_handles: IndexMap<i64, PhpResource>,
- lock_handles: IndexMap<i64, PhpResource>,
- read_bytes: IndexMap<i64, i64>,
-}
-
-impl WindowsPipes {
- pub fn new(_input: PhpMixed) -> Self {
- // Windows-only path: never constructed on non-Windows targets.
- todo!()
- }
-}
-
-impl PipesInterface for WindowsPipes {
- fn get_descriptors(&mut self) -> Vec<Descriptor> {
- let _ = (
- &self.files,
- &self.file_handles,
- &self.lock_handles,
- &self.read_bytes,
- );
- todo!()
- }
-
- fn get_files(&self) -> IndexMap<i64, String> {
- self.files.clone()
- }
-
- fn read_and_write(&mut self, _blocking: bool, _close: bool) -> IndexMap<i64, String> {
- todo!()
- }
-
- fn are_open(&self) -> bool {
- !self.inner.pipes.is_empty() && !self.file_handles.is_empty()
- }
-
- fn close(&mut self) {
- self.inner.close();
- todo!()
- }
-
- fn pipes(&self) -> &IndexMap<i64, PhpResource> {
- &self.inner.pipes
- }
-
- fn pipes_mut(&mut self) -> &mut IndexMap<i64, PhpResource> {
- &mut self.inner.pipes
- }
-}
diff --git a/crates/shirabe-external-packages/src/symfony/process/process.rs b/crates/shirabe-external-packages/src/symfony/process/process.rs
deleted file mode 100644
index 3fb3fab2..00000000
--- a/crates/shirabe-external-packages/src/symfony/process/process.rs
+++ /dev/null
@@ -1,1293 +0,0 @@
-//! ref: composer/vendor/symfony/process/Process.php
-
-use crate::symfony::process::exception::invalid_argument_exception::InvalidArgumentException;
-use crate::symfony::process::exception::logic_exception::LogicException;
-use crate::symfony::process::exception::process_signaled_exception::ProcessSignaledException;
-use crate::symfony::process::exception::process_timed_out_exception::ProcessTimedOutException;
-use crate::symfony::process::exception::runtime_exception::RuntimeException;
-use crate::symfony::process::executable_finder::ExecutableFinder;
-use crate::symfony::process::pipes::pipes_interface::PipesInterface;
-use crate::symfony::process::pipes::unix_pipes::UnixPipes;
-use crate::symfony::process::pipes::windows_pipes::WindowsPipes;
-use crate::symfony::process::process_utils::ProcessUtils;
-use indexmap::IndexMap;
-use shirabe_php_shim::{Descriptor, PhpMixed, PhpResource, php_regex};
-use std::sync::OnceLock;
-
-/// A user-supplied callback invoked with the output type ("out"/"err") and a chunk of output.
-pub type UserCallback = Box<dyn FnMut(&str, &str) -> bool>;
-
-/// The callback built by `build_callback`. It receives the owning Process so it can append output
-/// to the internal buffers, mirroring the `$this`-capturing closure produced in PHP.
-type ProcessCallback = Box<dyn FnMut(&mut Process, &str, &str) -> bool>;
-
-/// PHP `$this->commandline` is `array|string`.
-#[derive(Debug, Clone)]
-enum CommandLine {
- Array(Vec<String>),
- String(String),
-}
-
-/// Test-only behaviour for a Process fabricated via [`Process::__mock`]: `getOutput`/
-/// `getErrorOutput`/`getExitCode`/`isSuccessful` return these fixed values instead of reading a
-/// real subprocess. Mirrors PHPUnit's `getMockBuilder(Process::class)->disableOriginalConstructor()`
-/// mocks used by the Composer test suite (e.g. `ZipDownloaderTest`). Held in [`Process::mock`];
-/// always `None` in production.
-#[derive(Debug, Clone)]
-pub struct ProcessMock {
- pub exit_code: i64,
- pub stdout: String,
- pub stderr: String,
-}
-
-/// Process is a thin wrapper around proc_* functions to easily
-/// start independent PHP processes.
-pub struct Process {
- callback: Option<ProcessCallback>,
- commandline: CommandLine,
- cwd: Option<String>,
- env: IndexMap<String, PhpMixed>,
- input: PhpMixed,
- starttime: Option<f64>,
- timeout: Option<f64>,
- exitcode: Option<i64>,
- fallback_status: IndexMap<String, PhpMixed>,
- process_information: Option<IndexMap<String, PhpMixed>>,
- stdout: Option<PhpResource>,
- stderr: Option<PhpResource>,
- process: Option<PhpResource>,
- status: String,
- incremental_output_offset: i64,
- incremental_error_output_offset: i64,
- tty: bool,
- options: IndexMap<String, PhpMixed>,
- use_file_handles: bool,
- process_pipes: Option<Box<dyn PipesInterface>>,
- latest_signal: Option<i64>,
- cached_exit_code: Option<i64>,
- /// Test-only mock state. `None` in production; set via [`Process::__mock`] in tests.
- mock: Option<ProcessMock>,
-}
-
-impl std::fmt::Debug for Process {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- f.debug_struct("Process")
- .field("commandline", &self.commandline)
- .field("cwd", &self.cwd)
- .field("status", &self.status)
- .field("exitcode", &self.exitcode)
- .finish_non_exhaustive()
- }
-}
-
-fn descriptor(items: &[&str]) -> Descriptor {
- match items {
- ["pipe", mode] => Descriptor::Pipe(mode.to_string()),
- ["file", path, mode] => Descriptor::File(path.to_string(), mode.to_string()),
- _ => panic!("unsupported descriptor spec: {:?}", items),
- }
-}
-
-/// PHP `(string)` cast for an environment value or stream payload.
-fn to_php_string(value: &PhpMixed) -> String {
- match value {
- PhpMixed::String(s) => s.clone(),
- PhpMixed::Int(i) => i.to_string(),
- PhpMixed::Float(f) => f.to_string(),
- PhpMixed::Bool(b) => {
- if *b {
- "1".to_string()
- } else {
- String::new()
- }
- }
- _ => String::new(),
- }
-}
-
-impl Process {
- pub const ERR: &'static str = "err";
- pub const OUT: &'static str = "out";
-
- pub const STATUS_READY: &'static str = "ready";
- pub const STATUS_STARTED: &'static str = "started";
- pub const STATUS_TERMINATED: &'static str = "terminated";
-
- pub const STDOUT: i64 = 1;
- pub const STDERR: i64 = 2;
-
- // Timeout Precision in seconds.
- pub const TIMEOUT_PRECISION: f64 = 0.2;
-
- /// Exit codes translation table.
- fn exit_code_text(code: i64) -> Option<&'static str> {
- Some(match code {
- 0 => "OK",
- 1 => "General error",
- 2 => "Misuse of shell builtins",
-
- 126 => "Invoked command cannot execute",
- 127 => "Command not found",
- 128 => "Invalid exit argument",
-
- // signals
- 129 => "Hangup",
- 130 => "Interrupt",
- 131 => "Quit and dump core",
- 132 => "Illegal instruction",
- 133 => "Trace/breakpoint trap",
- 134 => "Process aborted",
- 135 => "Bus error: \"access to undefined portion of memory object\"",
- 136 => "Floating point exception: \"erroneous arithmetic operation\"",
- 137 => "Kill (terminate immediately)",
- 138 => "User-defined 1",
- 139 => "Segmentation violation",
- 140 => "User-defined 2",
- 141 => "Write to pipe with no one reading",
- 142 => "Signal raised by alarm",
- 143 => "Termination (request to terminate)",
- // 144 - not defined
- 145 => "Child process terminated, stopped (or continued*)",
- 146 => "Continue if stopped",
- 147 => "Stop executing temporarily",
- 148 => "Terminal stop signal",
- 149 => "Background process attempting to read from tty (\"in\")",
- 150 => "Background process attempting to write to tty (\"out\")",
- 151 => "Urgent data available on socket",
- 152 => "CPU time limit exceeded",
- 153 => "File size limit exceeded",
- 154 => "Signal raised by timer counting virtual time: \"virtual timer expired\"",
- 155 => "Profiling timer expired",
- // 156 - not defined
- 157 => "Pollable event",
- // 158 - not defined
- 159 => "Bad syscall",
- _ => return None,
- })
- }
-
- fn empty() -> Self {
- let mut options = IndexMap::new();
- options.insert("suppress_errors".to_string(), PhpMixed::Bool(true));
- options.insert("bypass_shell".to_string(), PhpMixed::Bool(true));
-
- Self {
- callback: None,
- commandline: CommandLine::Array(Vec::new()),
- cwd: None,
- env: IndexMap::new(),
- input: PhpMixed::Null,
- starttime: None,
- timeout: None,
- exitcode: None,
- fallback_status: IndexMap::new(),
- process_information: None,
- stdout: None,
- stderr: None,
- process: None,
- status: Self::STATUS_READY.to_string(),
- incremental_output_offset: 0,
- incremental_error_output_offset: 0,
- tty: false,
- options,
- use_file_handles: false,
- process_pipes: None,
- latest_signal: None,
- cached_exit_code: None,
- mock: None,
- }
- }
-
- /// For testing only. Builds an already-terminated mock Process whose getOutput/
- /// getErrorOutput/getExitCode/isSuccessful return the configured values, without spawning a
- /// real subprocess.
- pub fn __mock(mock: ProcessMock) -> Self {
- let mut this = Self::empty();
- this.status = Self::STATUS_TERMINATED.to_string();
- this.exitcode = Some(mock.exit_code);
- this.mock = Some(mock);
- this
- }
-
- pub fn new(
- command: Vec<String>,
- cwd: Option<String>,
- env: Option<IndexMap<String, String>>,
- input: PhpMixed,
- timeout: Option<f64>,
- ) -> anyhow::Result<Self> {
- if !shirabe_php_shim::function_exists("proc_open") {
- return Err(LogicException::new(
- "The Process class relies on proc_open, which is not available on your PHP installation.".to_string(),
- )
- .into());
- }
-
- let mut this = Self::empty();
- this.commandline = CommandLine::Array(command);
- this.cwd = cwd;
-
- // on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started
- // PHP: null === $this->cwd && (\defined('ZEND_THREAD_SAFE') || '\\' === \DIRECTORY_SEPARATOR)
- // `\defined('ZEND_THREAD_SAFE')` is unconditionally true in modern PHP (the constant always
- // exists; its value merely reflects the NTS/ZTS build), so the disjunction is always true and
- // the cwd is defaulted to getcwd() whenever it was null.
- if this.cwd.is_none() {
- this.cwd = shirabe_php_shim::getcwd();
- }
- if let Some(env) = env {
- this.set_env(
- env.into_iter()
- .map(|(k, v)| (k, PhpMixed::String(v)))
- .collect(),
- );
- }
-
- this.set_input(input)?;
- this.set_timeout(timeout)?;
- this.use_file_handles = cfg!(windows);
-
- Ok(this)
- }
-
- /// Creates a Process instance as a command-line to be run in a shell wrapper.
- pub fn from_shell_commandline(
- command: &str,
- cwd: Option<&str>,
- env: Option<IndexMap<String, String>>,
- input: PhpMixed,
- timeout: Option<f64>,
- ) -> anyhow::Result<Self> {
- let mut process = Self::new(Vec::new(), cwd.map(String::from), env, input, timeout)?;
- process.commandline = CommandLine::String(command.to_string());
-
- Ok(process)
- }
-
- /// Runs the process.
- pub fn run(
- &mut self,
- callback: Option<UserCallback>,
- env: IndexMap<String, PhpMixed>,
- ) -> anyhow::Result<i64> {
- self.start(callback, env)?;
-
- self.wait(None)
- }
-
- /// Starts the process and returns after writing the input to STDIN.
- pub fn start(
- &mut self,
- callback: Option<UserCallback>,
- mut env: IndexMap<String, PhpMixed>,
- ) -> anyhow::Result<()> {
- if self.is_running() {
- return Err(RuntimeException::new("Process is already running.".to_string()).into());
- }
-
- self.reset_process_data();
- self.starttime = Some(shirabe_php_shim::microtime());
- self.callback = Some(self.build_callback(callback));
- let mut descriptors = self.get_descriptors();
-
- if !self.env.is_empty() {
- // non-Windows: $env += $this->env;
- for (k, v) in &self.env {
- env.entry(k.clone()).or_insert_with(|| v.clone());
- }
- }
-
- for (k, v) in self.get_default_env() {
- env.entry(k).or_insert(v);
- }
-
- let mut commandline = match &self.commandline {
- CommandLine::Array(args) => {
- let mut cmd = args
- .iter()
- .map(|a| self.escape_argument(Some(a)))
- .collect::<Vec<_>>()
- .join(" ");
-
- if !cfg!(windows) {
- // exec is mandatory to deal with sending a signal to the process
- cmd = format!("exec {}", cmd);
- }
- cmd
- }
- CommandLine::String(s) => self.replace_placeholders(s, &env)?,
- };
-
- if cfg!(windows) {
- commandline = self.prepare_windows_command_line(&commandline, &mut env)?;
- } else if !self.use_file_handles && self.is_sigchild_enabled() {
- // last exit code is output on the fourth pipe and caught to work around --enable-sigchild
- descriptors.push(descriptor(&["pipe", "w"]));
-
- commandline = format!("{{ ({}) <&3 3<&- 3>/dev/null & }} 3<&0;", commandline);
- commandline.push_str(
- "pid=$!; echo $pid >&3; wait $pid 2>/dev/null; code=$?; echo $code >&3; exit $code",
- );
-
- // Workaround for the bug, when PTS functionality is enabled.
- let _pts_workaround = shirabe_php_shim::fopen("Process.php", "r");
- }
-
- let mut env_pairs: Vec<String> = Vec::new();
- for (k, v) in &env {
- let is_false = matches!(v, PhpMixed::Bool(false));
- if !is_false && !["argc", "argv", "ARGC", "ARGV"].contains(&k.as_str()) {
- env_pairs.push(format!("{}={}", k, to_php_string(v)));
- }
- }
-
- if !self
- .cwd
- .as_deref()
- .map(shirabe_php_shim::is_dir)
- .unwrap_or(false)
- {
- return Err(RuntimeException::new(format!(
- "The provided cwd \"{}\" does not exist.",
- self.cwd.as_deref().unwrap_or("")
- ))
- .into());
- }
-
- let cwd = self.cwd.clone();
- let options = self.options.clone();
- let process = {
- let pipes = self.process_pipes.as_mut().unwrap().pipes_mut();
- shirabe_php_shim::proc_open(
- &commandline,
- &descriptors,
- pipes,
- cwd.as_deref().map(std::path::Path::new),
- Some(&env_pairs),
- Some(&options),
- )
- };
- self.process = process.ok();
-
- if self.process.is_none() {
- return Err(
- RuntimeException::new("Unable to launch a new process.".to_string()).into(),
- );
- }
- self.status = Self::STATUS_STARTED.to_string();
-
- if descriptors.len() > 3 {
- let pipe3 = self
- .process_pipes
- .as_ref()
- .unwrap()
- .pipes()
- .get(&3)
- .cloned();
- let pid = pipe3
- .and_then(|p| shirabe_php_shim::fgets(&p, None))
- .map(|s| s.trim().parse::<i64>().unwrap_or(0))
- .unwrap_or(0);
- self.fallback_status
- .insert("pid".to_string(), PhpMixed::Int(pid));
- }
-
- if self.tty {
- return Ok(());
- }
-
- self.update_status(false);
- self.check_timeout()?;
- Ok(())
- }
-
- /// Waits for the process to terminate.
- pub fn wait(&mut self, callback: Option<UserCallback>) -> anyhow::Result<i64> {
- self.require_process_is_started("wait")?;
-
- self.update_status(false);
-
- if let Some(callback) = callback {
- self.callback = Some(self.build_callback(Some(callback)));
- }
-
- loop {
- self.check_timeout()?;
- let running = self.is_running()
- && (cfg!(windows) || self.process_pipes.as_ref().unwrap().are_open());
- self.read_pipes(running, !cfg!(windows) || !running);
- if !running {
- break;
- }
- }
-
- while self.is_running() {
- self.check_timeout()?;
- shirabe_php_shim::usleep(1000);
- }
-
- let signaled = self
- .process_information
- .as_ref()
- .and_then(|i| i.get("signaled"))
- .map(shirabe_php_shim::php_truthy)
- .unwrap_or(false);
- let termsig = self
- .process_information
- .as_ref()
- .and_then(|i| i.get("termsig"))
- .and_then(|v| v.as_int());
- if signaled && termsig != self.latest_signal {
- return Err(ProcessSignaledException::new(self)?.into());
- }
-
- Ok(self.exitcode.unwrap_or(0))
- }
-
- /// Returns the Pid (process identifier), if applicable.
- pub fn get_pid(&mut self) -> Option<i64> {
- if self.is_running() {
- self.process_information
- .as_ref()
- .and_then(|i| i.get("pid"))
- .and_then(|v| v.as_int())
- } else {
- None
- }
- }
-
- /// Returns the current output of the process (STDOUT).
- pub fn get_output(&mut self) -> anyhow::Result<String> {
- if let Some(mock) = &self.mock {
- return Ok(mock.stdout.clone());
- }
-
- self.read_pipes_for_output("getOutput", false)?;
-
- Ok(
- shirabe_php_shim::stream_get_contents3(self.stdout.as_ref().unwrap(), -1, 0)
- .unwrap_or_default(),
- )
- }
-
- /// Returns the current error output of the process (STDERR).
- pub fn get_error_output(&mut self) -> anyhow::Result<String> {
- if let Some(mock) = &self.mock {
- return Ok(mock.stderr.clone());
- }
-
- self.read_pipes_for_output("getErrorOutput", false)?;
-
- Ok(
- shirabe_php_shim::stream_get_contents3(self.stderr.as_ref().unwrap(), -1, 0)
- .unwrap_or_default(),
- )
- }
-
- /// Returns the exit code returned by the process.
- pub fn get_exit_code(&mut self) -> Option<i64> {
- if self.mock.is_some() {
- return self.exitcode;
- }
-
- self.update_status(false);
-
- self.exitcode
- }
-
- /// Returns a string representation for the exit code returned by the process.
- pub fn get_exit_code_text(&mut self) -> Option<String> {
- let exitcode = self.get_exit_code()?;
-
- Some(
- Self::exit_code_text(exitcode)
- .unwrap_or("Unknown error")
- .to_string(),
- )
- }
-
- /// Checks if the process ended successfully.
- pub fn is_successful(&mut self) -> bool {
- self.get_exit_code() == Some(0)
- }
-
- /// Returns the number of the signal that caused the child process to terminate.
- pub fn get_term_signal(&mut self) -> anyhow::Result<i64> {
- self.require_process_is_terminated("getTermSignal")?;
-
- let termsig = self
- .process_information
- .as_ref()
- .and_then(|i| i.get("termsig"))
- .and_then(|v| v.as_int());
- if self.is_sigchild_enabled() && termsig == Some(-1) {
- return Err(RuntimeException::new(
- "This PHP has been compiled with --enable-sigchild. Term signal cannot be retrieved.".to_string(),
- )
- .into());
- }
-
- Ok(termsig.unwrap_or(0))
- }
-
- /// Checks if the process is currently running.
- pub fn is_running(&mut self) -> bool {
- if Self::STATUS_STARTED != self.status {
- return false;
- }
-
- self.update_status(false);
-
- self.process_information
- .as_ref()
- .and_then(|i| i.get("running"))
- .map(shirabe_php_shim::php_truthy)
- .unwrap_or(false)
- }
-
- /// Checks if the process has been started with no regard to the current state.
- pub fn is_started(&self) -> bool {
- Self::STATUS_READY != self.status
- }
-
- /// Checks if the process is terminated.
- pub fn is_terminated(&mut self) -> bool {
- self.update_status(false);
-
- Self::STATUS_TERMINATED == self.status
- }
-
- /// Stops the process.
- pub fn stop(&mut self, timeout: f64, signal: Option<i64>) -> Option<i64> {
- let timeout_micro = shirabe_php_shim::microtime() + timeout;
- if self.is_running() {
- // given SIGTERM may not be defined and that "proc_terminate" uses the constant value
- // and not the constant itself, we use the same here
- let _ = self.do_signal(15, false);
- loop {
- shirabe_php_shim::usleep(1000);
- if !(self.is_running() && shirabe_php_shim::microtime() < timeout_micro) {
- break;
- }
- }
-
- if self.is_running() {
- // Avoid exception here: process is supposed to be running, but it might have
- // stopped just after this line. Silently discard the error.
- let _ = self.do_signal(signal.filter(|&s| s != 0).unwrap_or(9), false);
- }
- }
-
- if self.is_running() {
- if self.fallback_status.contains_key("pid") {
- self.fallback_status.shift_remove("pid");
-
- return self.stop(0.0, signal);
- }
- self.close();
- }
-
- self.exitcode
- }
-
- /// Adds a line to the STDOUT stream.
- pub fn add_output(&mut self, line: &str) {
- let stdout = self.stdout.as_ref().unwrap();
- shirabe_php_shim::fseek(stdout, 0, shirabe_php_shim::SEEK_END);
- shirabe_php_shim::fwrite(stdout, line, Some(line.len() as i64));
- shirabe_php_shim::fseek(
- stdout,
- self.incremental_output_offset,
- shirabe_php_shim::SEEK_SET,
- );
- }
-
- /// Adds a line to the STDERR stream.
- pub fn add_error_output(&mut self, line: &str) {
- let stderr = self.stderr.as_ref().unwrap();
- shirabe_php_shim::fseek(stderr, 0, shirabe_php_shim::SEEK_END);
- shirabe_php_shim::fwrite(stderr, line, Some(line.len() as i64));
- shirabe_php_shim::fseek(
- stderr,
- self.incremental_error_output_offset,
- shirabe_php_shim::SEEK_SET,
- );
- }
-
- /// Gets the command line to be executed.
- pub fn get_command_line(&self) -> String {
- match &self.commandline {
- CommandLine::Array(args) => args
- .iter()
- .map(|a| self.escape_argument(Some(a)))
- .collect::<Vec<_>>()
- .join(" "),
- CommandLine::String(s) => s.clone(),
- }
- }
-
- /// Gets the process timeout in seconds (max. runtime).
- pub fn get_timeout(&self) -> Option<f64> {
- self.timeout
- }
-
- /// Sets the process timeout (max. runtime) in seconds.
- pub fn set_timeout(&mut self, timeout: Option<f64>) -> anyhow::Result<&mut Self> {
- self.timeout = self.validate_timeout(timeout)?;
-
- Ok(self)
- }
-
- /// Enables or disables the TTY mode.
- pub fn set_tty(&mut self, tty: bool) -> anyhow::Result<&mut Self> {
- if cfg!(windows) && tty {
- return Err(RuntimeException::new(
- "TTY mode is not supported on Windows platform.".to_string(),
- )
- .into());
- }
-
- if tty && !Self::is_tty_supported() {
- return Err(RuntimeException::new(
- "TTY mode requires /dev/tty to be read/writable.".to_string(),
- )
- .into());
- }
-
- self.tty = tty;
-
- Ok(self)
- }
-
- /// Checks if the TTY mode is enabled.
- pub fn is_tty(&self) -> bool {
- self.tty
- }
-
- /// Gets the working directory.
- pub fn get_working_directory(&self) -> Option<String> {
- if self.cwd.is_none() {
- // getcwd() will return false if any one of the parent directories does not have
- // the readable or search mode set, even if the current directory does
- return shirabe_php_shim::getcwd().filter(|s| !s.is_empty());
- }
-
- self.cwd.clone()
- }
-
- /// Sets the environment variables.
- pub fn set_env(&mut self, env: IndexMap<String, PhpMixed>) -> &mut Self {
- self.env = env;
-
- self
- }
-
- /// Sets the input.
- pub fn set_input(&mut self, input: PhpMixed) -> anyhow::Result<&mut Self> {
- if self.is_running() {
- return Err(LogicException::new(
- "Input cannot be set while the process is running.".to_string(),
- )
- .into());
- }
-
- self.input =
- ProcessUtils::validate_input("Symfony\\Component\\Process\\Process::setInput", input)?;
-
- Ok(self)
- }
-
- /// Performs a check between the timeout definition and the time the process started.
- pub fn check_timeout(&mut self) -> anyhow::Result<()> {
- if Self::STATUS_STARTED != self.status {
- return Ok(());
- }
-
- if let Some(timeout) = self.timeout
- && timeout < shirabe_php_shim::microtime() - self.starttime.unwrap_or(0.0)
- {
- self.stop(0.0, None);
-
- return Err(ProcessTimedOutException::new(self).into());
- }
-
- Ok(())
- }
-
- /// Returns whether TTY is supported on the current operating system.
- pub fn is_tty_supported() -> bool {
- static IS_TTY_SUPPORTED: OnceLock<bool> = OnceLock::new();
-
- *IS_TTY_SUPPORTED.get_or_init(|| {
- let mut pipes = IndexMap::new();
- shirabe_php_shim::proc_open(
- "echo 1 >/dev/null",
- &[
- descriptor(&["file", "/dev/tty", "r"]),
- descriptor(&["file", "/dev/tty", "w"]),
- descriptor(&["file", "/dev/tty", "w"]),
- ],
- &mut pipes,
- None,
- None,
- None,
- )
- .is_ok()
- })
- }
-
- /// Creates the descriptors needed by the proc_open.
- fn get_descriptors(&mut self) -> Vec<Descriptor> {
- // TODO(plugin): $this->input instanceof \Iterator -> rewind() is not modeled.
- if cfg!(windows) {
- self.process_pipes = Some(Box::new(WindowsPipes::new(self.input.clone())));
- } else {
- self.process_pipes = Some(Box::new(UnixPipes::new(
- Some(self.is_tty()),
- self.input.clone(),
- )));
- }
-
- self.process_pipes.as_mut().unwrap().get_descriptors()
- }
-
- /// Builds up the callback used by wait().
- fn build_callback(&self, callback: Option<UserCallback>) -> ProcessCallback {
- let mut callback = callback;
- let out = Self::OUT;
-
- Box::new(
- move |this: &mut Process, r#type: &str, data: &str| -> bool {
- if out == r#type {
- this.add_output(data);
- } else {
- this.add_error_output(data);
- }
-
- match callback.as_mut() {
- Some(cb) => cb(r#type, data),
- None => false,
- }
- },
- )
- }
-
- /// Updates the status of the process, reads pipes.
- fn update_status(&mut self, blocking: bool) {
- if Self::STATUS_STARTED != self.status {
- return;
- }
-
- self.process_information = Some(shirabe_php_shim::proc_get_status(
- self.process.as_ref().unwrap(),
- ));
- let running = self
- .process_information
- .as_ref()
- .unwrap()
- .get("running")
- .map(shirabe_php_shim::php_truthy)
- .unwrap_or(false);
-
- // In PHP < 8.3, "proc_get_status" only returns the correct exit status on the first call.
- if shirabe_php_shim::PHP_VERSION_ID < 80300 {
- let exitcode = self
- .process_information
- .as_ref()
- .unwrap()
- .get("exitcode")
- .and_then(|v| v.as_int());
- if self.cached_exit_code.is_none() && !running && exitcode != Some(-1) {
- self.cached_exit_code = exitcode;
- }
-
- if let Some(cached) = self.cached_exit_code
- && !running
- && exitcode == Some(-1)
- {
- self.process_information
- .as_mut()
- .unwrap()
- .insert("exitcode".to_string(), PhpMixed::Int(cached));
- }
- }
-
- self.read_pipes(running && blocking, !cfg!(windows) || !running);
-
- if !self.fallback_status.is_empty() && self.is_sigchild_enabled() {
- // processInformation = fallbackStatus + processInformation (fallback keys win)
- let mut merged = self.fallback_status.clone();
- for (k, v) in self.process_information.take().unwrap() {
- merged.entry(k).or_insert(v);
- }
- self.process_information = Some(merged);
- }
-
- if !running {
- self.close();
- }
- }
-
- /// Returns whether PHP has been compiled with the '--enable-sigchild' option or not.
- fn is_sigchild_enabled(&self) -> bool {
- static SIGCHILD: OnceLock<bool> = OnceLock::new();
-
- if let Some(v) = SIGCHILD.get() {
- return *v;
- }
-
- if !shirabe_php_shim::function_exists("phpinfo") {
- return *SIGCHILD.get_or_init(|| false);
- }
-
- shirabe_php_shim::ob_start();
- shirabe_php_shim::phpinfo(shirabe_php_shim::INFO_GENERAL);
-
- *SIGCHILD.get_or_init(|| {
- shirabe_php_shim::str_contains(
- &shirabe_php_shim::ob_get_clean().unwrap_or_default(),
- "--enable-sigchild",
- )
- })
- }
-
- /// Reads pipes for the freshest output.
- fn read_pipes_for_output(&mut self, caller: &str, blocking: bool) -> anyhow::Result<()> {
- self.require_process_is_started(caller)?;
-
- self.update_status(blocking);
- Ok(())
- }
-
- /// Validates and returns the filtered timeout.
- fn validate_timeout(&self, timeout: Option<f64>) -> anyhow::Result<Option<f64>> {
- let timeout = timeout.unwrap_or(0.0);
-
- if timeout == 0.0 {
- Ok(None)
- } else if timeout < 0.0 {
- Err(InvalidArgumentException::new(
- "The timeout value must be a valid positive integer or float number.".to_string(),
- )
- .into())
- } else {
- Ok(Some(timeout))
- }
- }
-
- /// Reads pipes, executes callback.
- fn read_pipes(&mut self, blocking: bool, close: bool) {
- let result = self
- .process_pipes
- .as_mut()
- .unwrap()
- .read_and_write(blocking, close);
-
- let mut callback = self.callback.take();
- for (r#type, data) in result {
- if r#type != 3 {
- if let Some(cb) = callback.as_mut() {
- cb(
- self,
- if Self::STDOUT == r#type {
- Self::OUT
- } else {
- Self::ERR
- },
- &data,
- );
- }
- } else if !self.fallback_status.contains_key("signaled") {
- self.fallback_status.insert(
- "exitcode".to_string(),
- PhpMixed::Int(data.trim().parse().unwrap_or(0)),
- );
- }
- }
- self.callback = callback;
- }
-
- /// Closes process resource, closes file handles, sets the exitcode.
- fn close(&mut self) -> i64 {
- if let Some(p) = self.process_pipes.as_mut() {
- p.close();
- }
- if self.process.is_some() {
- shirabe_php_shim::proc_close(self.process.as_ref().unwrap());
- self.process = None;
- }
- self.exitcode = self
- .process_information
- .as_ref()
- .and_then(|i| i.get("exitcode"))
- .and_then(|v| v.as_int());
- self.status = Self::STATUS_TERMINATED.to_string();
-
- if self.exitcode == Some(-1) {
- let signaled = self
- .process_information
- .as_ref()
- .and_then(|i| i.get("signaled"))
- .map(shirabe_php_shim::php_truthy)
- .unwrap_or(false);
- let termsig = self
- .process_information
- .as_ref()
- .and_then(|i| i.get("termsig"))
- .and_then(|v| v.as_int())
- .unwrap_or(0);
- if signaled && termsig > 0 {
- // if process has been signaled, no exitcode but a valid termsig, apply Unix convention
- self.exitcode = Some(128 + termsig);
- } else if self.is_sigchild_enabled()
- && let Some(i) = self.process_information.as_mut()
- {
- i.insert("signaled".to_string(), PhpMixed::Bool(true));
- i.insert("termsig".to_string(), PhpMixed::Int(-1));
- }
- }
-
- // Free memory from self-reference callback created by buildCallback
- self.callback = None;
-
- self.exitcode.unwrap_or(-1)
- }
-
- /// Resets data related to the latest run of the process.
- fn reset_process_data(&mut self) {
- self.starttime = None;
- self.callback = None;
- self.exitcode = None;
- self.fallback_status = IndexMap::new();
- self.process_information = None;
- // php://temp is an in-memory stream; fopen never fails for it.
- self.stdout = Some(
- shirabe_php_shim::fopen(&format!("php://temp/maxmemory:{}", 1024 * 1024), "w+")
- .unwrap(),
- );
- self.stderr = Some(
- shirabe_php_shim::fopen(&format!("php://temp/maxmemory:{}", 1024 * 1024), "w+")
- .unwrap(),
- );
- self.process = None;
- self.latest_signal = None;
- self.status = Self::STATUS_READY.to_string();
- self.incremental_output_offset = 0;
- self.incremental_error_output_offset = 0;
- }
-
- /// Sends a POSIX signal to the process.
- fn do_signal(&mut self, signal: i64, throw_exception: bool) -> anyhow::Result<bool> {
- let pid = match self.get_pid() {
- None => {
- if throw_exception {
- return Err(LogicException::new(
- "Cannot send signal on a non running process.".to_string(),
- )
- .into());
- }
-
- return Ok(false);
- }
- Some(pid) => pid,
- };
-
- if cfg!(windows) {
- let mut output: Vec<String> = Vec::new();
- let mut exit_code: i64 = 0;
- shirabe_php_shim::exec(
- &format!("taskkill /F /T /PID {} 2>&1", pid),
- Some(&mut output),
- Some(&mut exit_code),
- );
- if exit_code != 0 && self.is_running() {
- if throw_exception {
- return Err(RuntimeException::new(format!(
- "Unable to kill the process ({}).",
- output.join(" ")
- ))
- .into());
- }
-
- return Ok(false);
- }
- } else {
- let ok;
- if !self.is_sigchild_enabled() {
- ok = shirabe_php_shim::proc_terminate(self.process.as_ref().unwrap(), signal);
- } else if shirabe_php_shim::function_exists("posix_kill") {
- ok = shirabe_php_shim::posix_kill(pid, signal);
- } else {
- let mut pipes = IndexMap::new();
- let opened = shirabe_php_shim::proc_open(
- &format!("kill -{} {}", signal, pid),
- &[
- Descriptor::Inherit,
- Descriptor::Inherit,
- descriptor(&["pipe", "w"]),
- ],
- &mut pipes,
- None,
- None,
- None,
- );
- ok = match opened {
- Ok(_) => pipes
- .get(&2)
- .and_then(|p| shirabe_php_shim::fgets(p, None))
- .is_none(),
- Err(_) => false,
- };
- }
- if !ok {
- if throw_exception {
- return Err(RuntimeException::new(format!(
- "Error while sending signal \"{}\".",
- signal
- ))
- .into());
- }
-
- return Ok(false);
- }
- }
-
- self.latest_signal = Some(signal);
- self.fallback_status
- .insert("signaled".to_string(), PhpMixed::Bool(true));
- self.fallback_status
- .insert("exitcode".to_string(), PhpMixed::Int(-1));
- self.fallback_status.insert(
- "termsig".to_string(),
- PhpMixed::Int(self.latest_signal.unwrap()),
- );
-
- Ok(true)
- }
-
- fn prepare_windows_command_line(
- &mut self,
- cmd: &str,
- env: &mut IndexMap<String, PhpMixed>,
- ) -> anyhow::Result<String> {
- let uid = shirabe_php_shim::uniqid("", true);
- let mut var_count = 0;
- let mut var_cache: IndexMap<String, String> = IndexMap::new();
- let cmd = shirabe_php_shim::preg_replace_callback(
- php_regex!(
- r#"/"(?:(
- [^"%!^]*+
- (?:
- (?: !LF! | "(?:\^[%!^])?+" )
- [^"%!^]*+
- )++
- ) | [^"]*+ )"/x"#
- ),
- |m: &[Option<String>]| -> anyhow::Result<String> {
- let m0 = m.first().cloned().flatten().unwrap_or_default();
- let m1 = m.get(1).cloned().flatten();
- if m1.is_none() {
- return Ok(m0);
- }
- if let Some(cached) = var_cache.get(&m0) {
- return Ok(cached.clone());
- }
- let mut value = m1.unwrap();
- if value.contains('\0') {
- value = value.replace('\0', "?");
- }
- if shirabe_php_shim::strpbrk(&value, "\"%!\n").is_none() {
- return Ok(format!("\"{}\"", value));
- }
-
- for (from, to) in [
- ("!LF!", "\n"),
- ("\"^!\"", "!"),
- ("\"^%\"", "%"),
- ("\"^^\"", "^"),
- ("\"\"", "\""),
- ] {
- value = value.replace(from, to);
- }
- value = format!(
- "\"{}\"",
- shirabe_php_shim::preg_replace(php_regex!(r#"/(\\*)"/"#), "$1$1\\\"", &value)
- );
- var_count += 1;
- let var = format!("{}{}", uid, var_count);
-
- env.insert(var.clone(), PhpMixed::String(value));
-
- let replacement = format!("!{}!", var);
- var_cache.insert(m0, replacement.clone());
- Ok(replacement)
- },
- cmd,
- )?;
-
- static COM_SPEC: OnceLock<Option<String>> = OnceLock::new();
- let com_spec = COM_SPEC
- .get_or_init(|| {
- ExecutableFinder::new()
- .find("cmd.exe", None, &[])
- .map(|spec| {
- format!(
- "\"{}\"",
- shirabe_php_shim::preg_replace(
- php_regex!(r#"{(\\*+)"}"#),
- "$1$1\\\"",
- &spec,
- )
- )
- })
- })
- .clone();
-
- let mut cmd = format!(
- "{} /V:ON /E:ON /D /C ({})",
- com_spec.unwrap_or_else(|| "cmd".to_string()),
- cmd.replace('\n', " ")
- );
- for (offset, filename) in self.process_pipes.as_ref().unwrap().get_files() {
- cmd.push_str(&format!(" {}>\"{}\"", offset, filename));
- }
-
- Ok(cmd)
- }
-
- /// Ensures the process is running or terminated.
- fn require_process_is_started(&self, function_name: &str) -> anyhow::Result<()> {
- if !self.is_started() {
- return Err(LogicException::new(format!(
- "Process must be started before calling \"{}()\".",
- function_name
- ))
- .into());
- }
- Ok(())
- }
-
- /// Ensures the process is terminated.
- fn require_process_is_terminated(&mut self, function_name: &str) -> anyhow::Result<()> {
- if !self.is_terminated() {
- return Err(LogicException::new(format!(
- "Process must be terminated before calling \"{}()\".",
- function_name
- ))
- .into());
- }
- Ok(())
- }
-
- /// Escapes a string to be used as a shell argument.
- fn escape_argument(&self, argument: Option<&str>) -> String {
- let argument = match argument {
- None | Some("") => return "\"\"".to_string(),
- Some(a) => a,
- };
- if !cfg!(windows) {
- return format!("'{}'", argument.replace('\'', "'\\''"));
- }
- let mut argument = argument.to_string();
- if argument.contains('\0') {
- argument = argument.replace('\0', "?");
- }
- if !shirabe_php_shim::preg_match(
- php_regex!(r#"/[()%!^"<>&|\s\[\]=;*?'$]/"#),
- &argument,
- &mut Vec::new(),
- ) {
- return argument;
- }
- argument = shirabe_php_shim::preg_replace(php_regex!(r"/(\\+)$/"), "$1$1", &argument);
-
- let mut result = argument;
- for (from, to) in [
- ("\"", "\"\""),
- ("^", "\"^^\""),
- ("%", "\"^%\""),
- ("!", "\"^!\""),
- ("\n", "!LF!"),
- ] {
- result = result.replace(from, to);
- }
- format!("\"{}\"", result)
- }
-
- fn replace_placeholders(
- &self,
- commandline: &str,
- env: &IndexMap<String, PhpMixed>,
- ) -> anyhow::Result<String> {
- shirabe_php_shim::preg_replace_callback(
- php_regex!(r#"/"\$\{:([_a-zA-Z]+[_a-zA-Z0-9]*)\}"/"#),
- |matches: &[Option<String>]| -> anyhow::Result<String> {
- let key = matches.get(1).cloned().flatten().unwrap_or_default();
- match env.get(&key) {
- None => Err(InvalidArgumentException::new(format!(
- "Command line is missing a value for parameter \"{}\": {}",
- key, commandline
- ))
- .into()),
- Some(PhpMixed::Bool(false)) => Err(InvalidArgumentException::new(format!(
- "Command line is missing a value for parameter \"{}\": {}",
- key, commandline
- ))
- .into()),
- Some(v) => Ok(self.escape_argument(Some(&to_php_string(v)))),
- }
- },
- commandline,
- )
- }
-
- fn get_default_env(&self) -> IndexMap<String, PhpMixed> {
- let env: IndexMap<String, String> = shirabe_php_shim::getenv_all()
- .map(|(k, v)| {
- (
- k.to_string_lossy().into_owned(),
- v.to_string_lossy().into_owned(),
- )
- })
- .collect();
- let server = shirabe_php_shim::PHP_SERVER.lock().unwrap();
-
- // non-Windows: array_intersect_key($env, $_SERVER) ?: $env
- let mut intersect: IndexMap<String, PhpMixed> = IndexMap::new();
- for (k, v) in &env {
- if server.get(k).is_some() {
- intersect.insert(k.clone(), PhpMixed::String(v.clone()));
- }
- }
- let env_map: IndexMap<String, PhpMixed> = if intersect.is_empty() {
- env.into_iter()
- .map(|(k, v)| (k, PhpMixed::String(v)))
- .collect()
- } else {
- intersect
- };
-
- // $_ENV + env_map
- let mut result: IndexMap<String, PhpMixed> = shirabe_php_shim::PHP_ENV
- .lock()
- .unwrap()
- .get_all()
- .map(|(k, v)| {
- (
- k.to_string_lossy().into_owned(),
- PhpMixed::String(v.to_string_lossy().into_owned()),
- )
- })
- .collect();
- for (k, v) in env_map {
- result.entry(k).or_insert(v);
- }
- result
- }
-}
-
-impl Drop for Process {
- fn drop(&mut self) {
- self.stop(0.0, None);
- }
-}
diff --git a/crates/shirabe-external-packages/src/symfony/process/process_utils.rs b/crates/shirabe-external-packages/src/symfony/process/process_utils.rs
deleted file mode 100644
index 71943b80..00000000
--- a/crates/shirabe-external-packages/src/symfony/process/process_utils.rs
+++ /dev/null
@@ -1,43 +0,0 @@
-//! ref: composer/vendor/symfony/process/ProcessUtils.php
-
-use crate::symfony::process::exception::invalid_argument_exception::InvalidArgumentException;
-use shirabe_php_shim::PhpMixed;
-
-/// ProcessUtils is a bunch of utility methods.
-#[derive(Debug)]
-pub struct ProcessUtils;
-
-impl ProcessUtils {
- /// Validates and normalizes a Process input.
- pub fn validate_input(caller: &str, input: PhpMixed) -> anyhow::Result<PhpMixed> {
- if !input.is_null() {
- if shirabe_php_shim::is_string(&input) {
- return Ok(input);
- }
- if shirabe_php_shim::is_scalar(&input) {
- let s = match &input {
- PhpMixed::Bool(b) => {
- if *b {
- "1".to_string()
- } else {
- String::new()
- }
- }
- PhpMixed::Int(i) => i.to_string(),
- PhpMixed::Float(f) => f.to_string(),
- other => other.as_string().map(|s| s.to_string()).unwrap_or_default(),
- };
- return Ok(PhpMixed::String(s));
- }
- // TODO(plugin): Process / \Iterator / \Traversable inputs are not modeled by PhpMixed.
-
- return Err(InvalidArgumentException::new(format!(
- "\"{}\" only accepts strings, Traversable objects or stream resources.",
- caller
- ))
- .into());
- }
-
- Ok(input)
- }
-}