aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-06-14 00:15:52 +0900
committernsfisis <nsfisis@gmail.com>2026-06-14 00:15:52 +0900
commitfe15d4dba4530f574856a6c443a3f665aa5abe7a (patch)
treed7dc0433e40a2c38c50daf399ec8a3de5c2ac307 /crates
parente165b2ffa32e21c50787ae7227dc5333131d1414 (diff)
downloadphp-shirabe-fe15d4dba4530f574856a6c443a3f665aa5abe7a.tar.gz
php-shirabe-fe15d4dba4530f574856a6c443a3f665aa5abe7a.tar.zst
php-shirabe-fe15d4dba4530f574856a6c443a3f665aa5abe7a.zip
feat(console): implement get_command_name_before_binding via input clone
Replace the todo!() stub with the real Symfony logic: clone the input, bind it against the application definition (ignoring binding errors), and read the first argument so the command name is detected even when global options precede it. Add a dup() method to InputInterface to model PHP's clone, derive Clone on the input types, and treat InvalidArgument/InvalidOption/MissingInput as ignorable ExceptionInterface errors during the pre-binding probe.
Diffstat (limited to 'crates')
-rw-r--r--crates/shirabe-external-packages/src/symfony/console/input/argv_input.rs6
-rw-r--r--crates/shirabe-external-packages/src/symfony/console/input/array_input.rs6
-rw-r--r--crates/shirabe-external-packages/src/symfony/console/input/input.rs2
-rw-r--r--crates/shirabe-external-packages/src/symfony/console/input/input_interface.rs3
-rw-r--r--crates/shirabe-external-packages/src/symfony/console/input/string_input.rs6
-rw-r--r--crates/shirabe/src/console/application.rs42
6 files changed, 46 insertions, 19 deletions
diff --git a/crates/shirabe-external-packages/src/symfony/console/input/argv_input.rs b/crates/shirabe-external-packages/src/symfony/console/input/argv_input.rs
index 765f601..4772cdb 100644
--- a/crates/shirabe-external-packages/src/symfony/console/input/argv_input.rs
+++ b/crates/shirabe-external-packages/src/symfony/console/input/argv_input.rs
@@ -24,7 +24,7 @@ use shirabe_php_shim::PhpMixed;
/// When passing an argument to the constructor, be sure that it respects
/// the same rules as the argv one. It's almost always better to use the
/// `StringInput` when you want to provide your own input.
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub struct ArgvInput {
pub(crate) inner: Input,
tokens: Vec<String>,
@@ -543,6 +543,10 @@ impl ArgvInput {
}
impl InputInterface for ArgvInput {
+ fn dup(&self) -> std::rc::Rc<std::cell::RefCell<dyn InputInterface>> {
+ std::rc::Rc::new(std::cell::RefCell::new(self.clone()))
+ }
+
fn get_first_argument(&self) -> Option<String> {
ArgvInput::get_first_argument(self)
}
diff --git a/crates/shirabe-external-packages/src/symfony/console/input/array_input.rs b/crates/shirabe-external-packages/src/symfony/console/input/array_input.rs
index ccc1916..705c997 100644
--- a/crates/shirabe-external-packages/src/symfony/console/input/array_input.rs
+++ b/crates/shirabe-external-packages/src/symfony/console/input/array_input.rs
@@ -14,7 +14,7 @@ use shirabe_php_shim::PhpMixed;
///
/// PHP arrays can mix integer and string keys; `parameters` preserves both the
/// key type (`PhpMixed::Int` / `PhpMixed::String`) and the insertion order.
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub struct ArrayInput {
pub(crate) inner: Input,
parameters: Vec<(PhpMixed, PhpMixed)>,
@@ -298,6 +298,10 @@ impl ArrayInput {
}
impl InputInterface for ArrayInput {
+ fn dup(&self) -> std::rc::Rc<std::cell::RefCell<dyn InputInterface>> {
+ std::rc::Rc::new(std::cell::RefCell::new(self.clone()))
+ }
+
fn get_first_argument(&self) -> Option<String> {
ArrayInput::get_first_argument(self).map(|v| shirabe_php_shim::php_to_string(&v))
}
diff --git a/crates/shirabe-external-packages/src/symfony/console/input/input.rs b/crates/shirabe-external-packages/src/symfony/console/input/input.rs
index 2c9a3a6..2881ccb 100644
--- a/crates/shirabe-external-packages/src/symfony/console/input/input.rs
+++ b/crates/shirabe-external-packages/src/symfony/console/input/input.rs
@@ -11,7 +11,7 @@ use shirabe_php_shim::PhpMixed;
/// * `ArgvInput`: The input comes from the CLI arguments (argv)
/// * `StringInput`: The input is provided as a string
/// * `ArrayInput`: The input is provided as an array
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub struct Input {
pub(crate) definition: InputDefinition,
pub(crate) stream: PhpMixed,
diff --git a/crates/shirabe-external-packages/src/symfony/console/input/input_interface.rs b/crates/shirabe-external-packages/src/symfony/console/input/input_interface.rs
index d271845..09ffd26 100644
--- a/crates/shirabe-external-packages/src/symfony/console/input/input_interface.rs
+++ b/crates/shirabe-external-packages/src/symfony/console/input/input_interface.rs
@@ -2,6 +2,9 @@ use crate::symfony::console::input::input_definition::InputDefinition;
use shirabe_php_shim::PhpMixed;
pub trait InputInterface: std::fmt::Debug + shirabe_php_shim::AsAny {
+ /// Models PHP's `clone` operatior.
+ fn dup(&self) -> std::rc::Rc<std::cell::RefCell<dyn InputInterface>>;
+
fn get_first_argument(&self) -> Option<String>;
fn has_parameter_option(&self, values: PhpMixed, only_params: bool) -> bool;
diff --git a/crates/shirabe-external-packages/src/symfony/console/input/string_input.rs b/crates/shirabe-external-packages/src/symfony/console/input/string_input.rs
index bbc9e86..4f2a045 100644
--- a/crates/shirabe-external-packages/src/symfony/console/input/string_input.rs
+++ b/crates/shirabe-external-packages/src/symfony/console/input/string_input.rs
@@ -10,7 +10,7 @@ use shirabe_php_shim::PhpMixed;
/// Usage:
///
/// $input = new StringInput('foo --bar="foobar"');
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub struct StringInput {
pub(crate) inner: ArgvInput,
}
@@ -122,6 +122,10 @@ impl StringInput {
}
impl InputInterface for StringInput {
+ fn dup(&self) -> std::rc::Rc<std::cell::RefCell<dyn InputInterface>> {
+ std::rc::Rc::new(std::cell::RefCell::new(self.clone()))
+ }
+
fn get_first_argument(&self) -> Option<String> {
self.inner.get_first_argument()
}
diff --git a/crates/shirabe/src/console/application.rs b/crates/shirabe/src/console/application.rs
index b97a5d5..bc58297 100644
--- a/crates/shirabe/src/console/application.rs
+++ b/crates/shirabe/src/console/application.rs
@@ -1,4 +1,5 @@
//! ref: composer/src/Composer/Console/Application.php
+//! ref: composer/vendor/symfony/console/Application.php
use crate::io::io_interface;
use indexmap::IndexMap;
@@ -19,7 +20,10 @@ use shirabe_external_packages::symfony::console::event::console_signal_event::Co
use shirabe_external_packages::symfony::console::event::console_terminate_event::ConsoleTerminateEvent;
use shirabe_external_packages::symfony::console::exception::CommandNotFoundException;
use shirabe_external_packages::symfony::console::exception::ExceptionInterface;
+use shirabe_external_packages::symfony::console::exception::invalid_argument_exception::InvalidArgumentException as ConsoleInvalidArgumentException;
+use shirabe_external_packages::symfony::console::exception::invalid_option_exception::InvalidOptionException;
use shirabe_external_packages::symfony::console::exception::logic_exception::LogicException as ConsoleLogicException;
+use shirabe_external_packages::symfony::console::exception::missing_input_exception::MissingInputException;
use shirabe_external_packages::symfony::console::exception::namespace_not_found_exception::NamespaceNotFoundException;
use shirabe_external_packages::symfony::console::exception::runtime_exception::RuntimeException as ConsoleRuntimeException;
use shirabe_external_packages::symfony::console::formatter::output_formatter::OutputFormatter;
@@ -53,7 +57,7 @@ use shirabe_external_packages::symfony::contracts::event_dispatcher::event_dispa
use shirabe_external_packages::symfony::process::exception::ProcessTimedOutException;
use shirabe_php_shim::{
LogicException as ShimLogicException, PHP_BINARY, PHP_VERSION, PHP_VERSION_ID, PhpMixed,
- RuntimeException, UnexpectedValueException, array_merge, bin2hex, chdir, clone, count,
+ RuntimeException, UnexpectedValueException, array_merge, bin2hex, chdir, count,
date_default_timezone_get, date_default_timezone_set, defined, dirname, disk_free_space,
error_get_last, extension_loaded, file_exists, file_get_contents, file_put_contents,
function_exists, get_class, getcwd, getmypid, glob, in_array, ini_set, is_array, is_dir,
@@ -324,7 +328,7 @@ impl Application {
// determine command name to be executed without including plugin commands
let mut command_name: Option<String> = Some(String::new());
- let raw_command_name = self.get_command_name_before_binding(input.clone());
+ let raw_command_name = self.get_command_name_before_binding(input.clone())?;
if let Some(ref raw) = raw_command_name {
match self.find(raw) {
Ok(cmd) => {
@@ -567,7 +571,7 @@ impl Application {
// determine command name to be executed incl plugin commands, and check if it's a proxy command
let is_proxy_command = false;
- if let Some(ref name) = self.get_command_name_before_binding(input.clone()) {
+ if let Some(ref name) = self.get_command_name_before_binding(input.clone())? {
if let Ok(command) = self.find(name) {
// TODO(phase-c): same blocker as the earlier find() call — the Symfony command
// registry is a PhpMixed/todo!() stub, so the resolved command's name and its
@@ -1161,21 +1165,25 @@ impl Application {
}
/// This ensures we can find the correct command name even if a global input option is present before it
+ ///
+ /// e.g. "composer -d foo bar" should detect bar as the command name, and not foo
fn get_command_name_before_binding(
- &self,
+ &mut self,
input: std::rc::Rc<std::cell::RefCell<dyn InputInterface>>,
- ) -> Option<String> {
- let mut input = clone(&input);
+ ) -> anyhow::Result<Option<String>> {
+ let input = input.borrow().dup();
// Makes ArgvInput::getFirstArgument() able to distinguish an option from an argument.
- // PHP: $input = clone $input; try { $input->bind($this->getDefinition()); } catch (...) {}
- // return $input->getFirstArgument();
- // TODO(phase-c): two blockers. (1) PHP clones the input so binding does not mutate the
- // caller's instance; the `clone` php-shim must stay todo!() and dyn InputInterface has no
- // deep-clone, so we cannot produce an independent copy to bind. (2) $this->getDefinition()
- // returns the application's typed InputDefinition, but the Symfony Application stub returns
- // PhpMixed. Until both land, getFirstArgument cannot be computed and we return None.
- let _ = input;
- None
+ match input.borrow_mut().bind(&self.get_definition().borrow()) {
+ Ok(()) => {}
+ Err(e) => {
+ // Errors must be ignored, full binding/validation happens later when the command is known.
+ if !is_exception_interface(&e) {
+ return Err(e);
+ }
+ }
+ }
+
+ Ok(input.borrow().get_first_argument())
}
pub fn get_long_version(&self) -> String {
@@ -2925,6 +2933,10 @@ fn is_exception_interface(e: &anyhow::Error) -> bool {
|| e.downcast_ref::<NamespaceNotFoundException>().is_some()
|| e.downcast_ref::<ConsoleLogicException>().is_some()
|| e.downcast_ref::<ConsoleRuntimeException>().is_some()
+ || e.downcast_ref::<ConsoleInvalidArgumentException>()
+ .is_some()
+ || e.downcast_ref::<InvalidOptionException>().is_some()
+ || e.downcast_ref::<MissingInputException>().is_some()
}
/// Helper mirroring PHP's `$e instanceof CommandNotFoundException`.