aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-05-15 01:19:56 +0900
committernsfisis <nsfisis@gmail.com>2026-05-15 19:51:17 +0900
commite521b075f6a7dacc1b90c0105df05c689aa793a4 (patch)
tree5d3b8a931bc1e0f8991f457e602b09b5aa39d9a1 /crates/shirabe/src
parent196bf8fa15e03130c07131ed198309ae0328a1dc (diff)
downloadphp-shirabe-e521b075f6a7dacc1b90c0105df05c689aa793a4.tar.gz
php-shirabe-e521b075f6a7dacc1b90c0105df05c689aa793a4.tar.zst
php-shirabe-e521b075f6a7dacc1b90c0105df05c689aa793a4.zip
feat(port): port ErrorHandler.php
Diffstat (limited to 'crates/shirabe/src')
-rw-r--r--crates/shirabe/src/util/error_handler.rs118
1 files changed, 118 insertions, 0 deletions
diff --git a/crates/shirabe/src/util/error_handler.rs b/crates/shirabe/src/util/error_handler.rs
index d853270..2f8c699 100644
--- a/crates/shirabe/src/util/error_handler.rs
+++ b/crates/shirabe/src/util/error_handler.rs
@@ -1 +1,119 @@
//! ref: composer/src/Composer/Util/ErrorHandler.php
+
+use std::sync::{Mutex, OnceLock};
+use shirabe_php_shim::{
+ debug_backtrace, error_reporting, filter_var, ini_get, is_resource,
+ set_error_handler, E_ALL, E_DEPRECATED, E_USER_DEPRECATED, E_WARNING, E_USER_WARNING,
+ FILTER_VALIDATE_BOOLEAN, PHP_EOL, STDERR, PhpMixed, ErrorException,
+};
+use crate::io::io_interface::IOInterface;
+
+static IO: OnceLock<Mutex<Option<Box<dyn IOInterface + Send>>>> = OnceLock::new();
+static HAS_SHOWN_DEPRECATION_NOTICE: Mutex<i64> = Mutex::new(0);
+
+fn io() -> &'static Mutex<Option<Box<dyn IOInterface + Send>>> {
+ IO.get_or_init(|| Mutex::new(None))
+}
+
+pub struct ErrorHandler;
+
+impl ErrorHandler {
+ pub fn handle(level: i64, message: String, file: String, line: i64) -> Result<bool, ErrorException> {
+ let is_deprecation_notice = level == E_DEPRECATED || level == E_USER_DEPRECATED;
+
+ // error code is not included in error_reporting
+ if !is_deprecation_notice && 0 == (error_reporting(None) & level) {
+ return Ok(true);
+ }
+
+ let mut message = message;
+
+ let xdebug_scream = ini_get("xdebug.scream").unwrap_or_default();
+ if filter_var(&xdebug_scream, FILTER_VALIDATE_BOOLEAN) {
+ message += "\n\nWarning: You have xdebug.scream enabled, the warning above may be\na legitimately suppressed error that you were not supposed to see.";
+ }
+
+ if !is_deprecation_notice {
+ // ignore some newly introduced warnings in new php versions until dependencies
+ // can be fixed as we do not want to abort execution for those
+ if (level == E_WARNING || level == E_USER_WARNING)
+ && message.contains("should either be used or intentionally ignored by casting it as (void)")
+ {
+ Self::output_warning(
+ &format!("Ignored new PHP warning but it should be reported and fixed: {} in {}:{}", message, file, line),
+ true,
+ );
+ return Ok(true);
+ }
+
+ return Err(ErrorException {
+ message,
+ code: 0,
+ severity: level,
+ filename: file,
+ lineno: line,
+ });
+ }
+
+ let io_guard = io().lock().unwrap();
+ if io_guard.is_some() {
+ let has_shown = *HAS_SHOWN_DEPRECATION_NOTICE.lock().unwrap();
+ if has_shown > 0 && !io_guard.as_ref().unwrap().is_verbose() {
+ if has_shown == 1 {
+ io_guard.as_ref().unwrap().write_error("<warning>More deprecation notices were hidden, run again with `-v` to show them.</warning>");
+ *HAS_SHOWN_DEPRECATION_NOTICE.lock().unwrap() = 2;
+ }
+ return Ok(true);
+ }
+ *HAS_SHOWN_DEPRECATION_NOTICE.lock().unwrap() = 1;
+ drop(io_guard);
+ Self::output_warning(&format!("Deprecation Notice: {} in {}:{}", message, file, line), false);
+ }
+
+ Ok(true)
+ }
+
+ pub fn register(io: Option<Box<dyn IOInterface + Send>>) {
+ set_error_handler(|level, message, file, line| {
+ Self::handle(level, message.to_string(), file.to_string(), line).unwrap_or(true)
+ });
+ error_reporting(Some(E_ALL));
+ *self::io().lock().unwrap() = io;
+ }
+
+ fn output_warning(message: &str, output_even_without_io: bool) {
+ let io_guard = io().lock().unwrap();
+ if let Some(ref io) = *io_guard {
+ io.write_error(&format!("<warning>{}</warning>", message));
+ if io.is_verbose() {
+ io.write_error("<warning>Stack trace:</warning>");
+ let frames: Vec<String> = debug_backtrace()
+ .into_iter()
+ .skip(2)
+ .filter_map(|frame| {
+ let line = frame.get("line").and_then(|v| v.as_int());
+ let file = frame.get("file").and_then(|v| v.as_string()).map(|s| s.to_string());
+ if let (Some(line), Some(file)) = (line, file) {
+ Some(format!("<warning> {}:{}</warning>", file, line))
+ } else {
+ None
+ }
+ })
+ .collect();
+ for frame_str in frames {
+ io.write_error(&frame_str);
+ }
+ }
+ return;
+ }
+ drop(io_guard);
+
+ if output_even_without_io {
+ if is_resource(&PhpMixed::Int(STDERR)) {
+ shirabe_php_shim::fwrite(PhpMixed::Int(STDERR), &format!("Warning: {}{}", message, PHP_EOL), -1);
+ } else {
+ print!("Warning: {}{}", message, PHP_EOL);
+ }
+ }
+ }
+}