3v4l.org

run code in 300+ PHP versions simultaneously
<?php class ErrorHandler { /*** The core: how to redact only sensitive parts *** ****************************************************/ // Redacting values happens either per key in an array element or name in an object property... public static $sMatchIndex= '#pass|time#i'; // ...or per function name. Both are PCRE patterns to match just any text. public static $sMatchFunction= '#connect$|login#i'; // Should every STRING data type be hidden? This is set anew per iterated stack trace function. private static $bRedactAllStrings= FALSE; // Handle a variable as per its data type, so an array or an object is recursively checked against // STRINGs, too. Side effect: make STRINGs look like literals. This method should be used on: // - every array element value, // - every object property value, // - optionally on every array element key (to later distinguish numeric indexes from textual). public static function as_per_type ( $vValue // Variable of any data type. , &$sType // Recognized data type; is needed later, too. , $vKey= '' // Potential array index to test for password-like name. ) { $sType= gettype( $vValue ); switch( $sType ) { case 'array': // Each key and value can have different data types. return self:: recurse_array( $vValue ); case 'object': // Each property can have different data types. return self:: recurse_object( $vValue ); case 'string': // Either all STRING values should be redacted, or the key has a name hinting for a password. if( self:: $bRedactAllStrings ) { return '**REDACTED_PER_FUNCTION**'; } else if( $vKey&& preg_match( self:: $sMatchIndex, $vKey ) ) { return '**REDACTED_PER_INDEX**'; } else return "'$vValue'"; // Original text, but as literal. default: // BOOLEAN, INTEGER, DOUBLE, RESOURCE, NULL and others: won't have passwords. return $vValue; } } // Handle a class instance's properties as per their data types, which can be arrays or objects again. public static function recurse_object ( $oInput // Object with any properties. ) { $aObj= get_object_vars( $oInput ); // Get all property names as array. $aOutput= array(); foreach( $aObj as $iObj=> $vObj ) { $vValue= self:: as_per_type( $oInput-> $iObj, $sType, $iObj ); $aOutput["$iObj ($sType)"]= $vValue; // Array key hints at value data type. } return $aOutput; } // Handle all array elements as per their data types, which can be objects or arrays again. public static function recurse_array ( $aInput // Array with any elements. ) { $aOutput= array(); foreach( $aInput as $iKey=> $vValue ) { $sKey= self:: as_per_type( $iKey, $sTypeKey ); // Element keys need no redaction... $sValue= self:: as_per_type( $vValue, $sTypeValue, $iKey ); // ...but values do. // Objects are converted to arrays by us, loosing the information of which class they were. // So we append the class name to the type hint in the array element key. if( $sTypeValue== 'object' ) $sTypeValue.= ' '. get_class( $vValue ); $aOutput["$sKey ($sTypeValue)"]= $sValue; // Array key hints at value data type. } return $aOutput; } // Parse the stack trace to redact potentially sensitive texts. public static function redact_backtrace ( $aTrace // Stack trace array to be parsed. ) { foreach( $aTrace as $iFunc=> $aFunc ) { self:: $bRedactAllStrings= FALSE; // Yet this is no sensitive function being called. // If this is a class instance we only need to redact by property name. if( isset( $aFunc['object'] ) ) { $aTrace[$iFunc]['object']= self:: recurse_object( $aTrace[$iFunc]['object'] ); } // Should the function's name match we'll recursively redact ANY string. if( isset( $aFunc['function'] ) ) { self:: $bRedactAllStrings= preg_match( self:: $sMatchFunction, $aFunc['function'] ); } // Now parse all parameters to potentially redact chosen ones. if( isset( $aFunc['args'] ) ) { $aTrace[$iFunc]['args']= self:: recurse_array( $aTrace[$iFunc]['args'] ); } } return $aTrace; } /*** Functional example: seeing the redacted data *** ****************************************************/ // Write log messages to wherever we want to. private static $bHeadersSent= FALSE; public static function err_log ( $aLog // Array to be saved. ) { if( !self:: $bHeadersSent ) { header( 'content-type: text/plain' ); // Don't let browser interpret output as HTML, preserve spaces. self:: $bHeadersSent= TRUE; // Only send header once. } print_r( $aLog ); // Imagine this being our log file. } /*** Demo: actually handling errors to get stack traces *** **********************************************************/ // Handler for uncaught errors. public static function err_handler ( $iError // See https://www.php.net/manual/en/errorfunc.constants.php , $sText // Error message. , $sFile // PHP file which was parsed. , $iLine // Line of error in PHP file. ) { // First one is this function, and we won't need this ever $aTrace= debug_backtrace(); unset( $aTrace[0] ); self:: err_log ( array ( 'where' => 'Error handler' , 'file' => $sFile , 'line' => $iLine , 'code' => $iError , 'msg' => $sText , 'trace' => self:: redact_backtrace( $aTrace ) ) ); } // Handler for uncaught exceptions. public static function exc_handler ( $e // Exception ) { self:: err_log ( array ( 'where' => 'Exception handler' , 'file' => $e-> getFile() , 'line' => $e-> getLine() , 'code' => $e-> getCode() , 'msg' => $e-> getMessage() , 'trace' => self:: redact_backtrace( $e-> getTrace() ) , 'class' => get_class( $e ) ) ); } } // For register_shutdown_function() a stack trace is not available. set_error_handler ( array( 'ErrorHandler', 'err_handler' ), E_ALL ); set_exception_handler ( array( 'ErrorHandler', 'exc_handler' ) ); /*** Demo: creating errors *** *****************************/ class Example { public $iNumber = 12345; // Integers won't be redacted. public $sPassword = 'secret'; // The property name should hint at a password. public $sInfo = 'a password?'; // No chance to identify this as password. public function login( $sUser, $sPass ) { echo( array() ); // Notice: Array to string conversion. } public function test( $a, $b ) { $this-> login( 'username', 'password' ); // Deeper nesting, recognition by function name. unset( $a['obj'] ); // Seeing the object once is enough for demonstration purposes. 1/ 0; // Error: Division by zero. 1+ $x; // Error: Undefined variable. throw new Exception( 'TestException' ); // Unhandled exception. } } // Building a rather complex parameter, using as many data types as possible. $aFirst= array ( 'string' => 'Text' , 'int' => 42 , 'float' => 3.1415 , 'bool' => TRUE , 'array' => array ( 'key' => 'value' , 'db_password' => 'adminadmin' // Array in array: should be redacted as per key text. ) , 'obj' => new DateTime // So we get an actual class instance. , 'pass' => '12345' // Should be redacted as per key text. , 110 => 'ordinal index' ); // Simple parameter: array with ordinal indexes only. $aSecond= array ( 'no index identifying a password' , 'username' ); // Outcome: // WHERE | REDACTION // --------------------------------|---------- // Example-> login() | // - $oTest-> sPassword | Index // - $sUser (1st function param) | Function // - $sPass (2nd function param) | Function // Example-> test() | // - $oTest-> sPassword | Index // - $a['array']['db_password'] | Index // - $a['obj']-> timezone | Index // - $a['pass'] | Index $oTest= new Example; $oTest-> test( $aFirst, $aSecond );
Output for 8.1.23 - 8.1.33, 8.2.10 - 8.2.29, 8.3.0 - 8.3.26, 8.4.1 - 8.4.13
Array ( [where] => Error handler [file] => /in/fKQAc [line] => 187 [code] => 2 [msg] => Array to string conversion [trace] => Array ( [1] => Array ( [file] => /in/fKQAc [line] => 191 [function] => login [class] => Example [object] => Array ( [iNumber (integer)] => 12345 [sPassword (string)] => **REDACTED_PER_INDEX** [sInfo (string)] => 'a password?' ) [type] => -> [args] => Array ( [0 (string)] => **REDACTED_PER_FUNCTION** [1 (string)] => **REDACTED_PER_FUNCTION** ) ) [2] => Array ( [file] => /in/fKQAc [line] => 237 [function] => test [class] => Example [object] => Array ( [iNumber (integer)] => 12345 [sPassword (string)] => **REDACTED_PER_INDEX** [sInfo (string)] => 'a password?' ) [type] => -> [args] => Array ( [0 (array)] => Array ( ['string' (string)] => 'Text' ['int' (integer)] => 42 ['float' (double)] => 3.1415 ['bool' (boolean)] => 1 ['array' (array)] => Array ( ['key' (string)] => 'value' ['db_password' (string)] => **REDACTED_PER_INDEX** ) ['obj' (object DateTime)] => Array ( ) ['pass' (string)] => **REDACTED_PER_INDEX** [110 (string)] => 'ordinal index' ) [1 (array)] => Array ( [0 (string)] => 'no index identifying a password' [1 (string)] => 'username' ) ) ) ) ) ArrayArray ( [where] => Exception handler [file] => /in/fKQAc [line] => 195 [code] => 0 [msg] => Division by zero [trace] => Array ( [0] => Array ( [file] => /in/fKQAc [line] => 237 [function] => test [class] => Example [type] => -> [args] => Array ( [0 (array)] => Array ( ['string' (string)] => 'Text' ['int' (integer)] => 42 ['float' (double)] => 3.1415 ['bool' (boolean)] => 1 ['array' (array)] => Array ( ['key' (string)] => 'value' ['db_password' (string)] => **REDACTED_PER_INDEX** ) ['pass' (string)] => **REDACTED_PER_INDEX** [110 (string)] => 'ordinal index' ) [1 (array)] => Array ( [0 (string)] => 'no index identifying a password' [1 (string)] => 'username' ) ) ) ) [class] => DivisionByZeroError )
Output for 7.0.33
Array ( [where] => Error handler [file] => /in/fKQAc [line] => 187 [code] => 8 [msg] => Array to string conversion [trace] => Array ( [1] => Array ( [file] => /in/fKQAc [line] => 191 [function] => login [class] => Example [object] => Array ( [iNumber (integer)] => 12345 [sPassword (string)] => **REDACTED_PER_INDEX** [sInfo (string)] => 'a password?' ) [type] => -> [args] => Array ( [0 (string)] => **REDACTED_PER_FUNCTION** [1 (string)] => **REDACTED_PER_FUNCTION** ) ) [2] => Array ( [file] => /in/fKQAc [line] => 237 [function] => test [class] => Example [object] => Array ( [iNumber (integer)] => 12345 [sPassword (string)] => **REDACTED_PER_INDEX** [sInfo (string)] => 'a password?' ) [type] => -> [args] => Array ( [0 (array)] => Array ( ['string' (string)] => 'Text' ['int' (integer)] => 42 ['float' (double)] => 3.1415 ['bool' (boolean)] => 1 ['array' (array)] => Array ( ['key' (string)] => 'value' ['db_password' (string)] => **REDACTED_PER_INDEX** ) ['obj' (object DateTime)] => Array ( [date (string)] => '2023-12-21 11:39:59.000000' [timezone_type (integer)] => 3 [timezone (string)] => **REDACTED_PER_INDEX** ) ['pass' (string)] => **REDACTED_PER_INDEX** [110 (string)] => 'ordinal index' ) [1 (array)] => Array ( [0 (string)] => 'no index identifying a password' [1 (string)] => 'username' ) ) ) ) ) ArrayArray ( [where] => Error handler [file] => /in/fKQAc [line] => 195 [code] => 2 [msg] => Division by zero [trace] => Array ( [1] => Array ( [file] => /in/fKQAc [line] => 237 [function] => test [class] => Example [object] => Array ( [iNumber (integer)] => 12345 [sPassword (string)] => **REDACTED_PER_INDEX** [sInfo (string)] => 'a password?' ) [type] => -> [args] => Array ( [0 (array)] => Array ( ['string' (string)] => 'Text' ['int' (integer)] => 42 ['float' (double)] => 3.1415 ['bool' (boolean)] => 1 ['array' (array)] => Array ( ['key' (string)] => 'value' ['db_password' (string)] => **REDACTED_PER_INDEX** ) ['pass' (string)] => **REDACTED_PER_INDEX** [110 (string)] => 'ordinal index' ) [1 (array)] => Array ( [0 (string)] => 'no index identifying a password' [1 (string)] => 'username' ) ) ) ) ) Array ( [where] => Error handler [file] => /in/fKQAc [line] => 196 [code] => 8 [msg] => Undefined variable: x [trace] => Array ( [1] => Array ( [file] => /in/fKQAc [line] => 237 [function] => test [class] => Example [object] => Array ( [iNumber (integer)] => 12345 [sPassword (string)] => **REDACTED_PER_INDEX** [sInfo (string)] => 'a password?' ) [type] => -> [args] => Array ( [0 (array)] => Array ( ['string' (string)] => 'Text' ['int' (integer)] => 42 ['float' (double)] => 3.1415 ['bool' (boolean)] => 1 ['array' (array)] => Array ( ['key' (string)] => 'value' ['db_password' (string)] => **REDACTED_PER_INDEX** ) ['pass' (string)] => **REDACTED_PER_INDEX** [110 (string)] => 'ordinal index' ) [1 (array)] => Array ( [0 (string)] => 'no index identifying a password' [1 (string)] => 'username' ) ) ) ) ) Array ( [where] => Exception handler [file] => /in/fKQAc [line] => 197 [code] => 0 [msg] => TestException [trace] => Array ( [0] => Array ( [file] => /in/fKQAc [line] => 237 [function] => test [class] => Example [type] => -> [args] => Array ( [0 (array)] => Array ( ['string' (string)] => 'Text' ['int' (integer)] => 42 ['float' (double)] => 3.1415 ['bool' (boolean)] => 1 ['array' (array)] => Array ( ['key' (string)] => 'value' ['db_password' (string)] => **REDACTED_PER_INDEX** ) ['pass' (string)] => **REDACTED_PER_INDEX** [110 (string)] => 'ordinal index' ) [1 (array)] => Array ( [0 (string)] => 'no index identifying a password' [1 (string)] => 'username' ) ) ) ) [class] => Exception )
Output for 5.6.40
Array ( [where] => Error handler [file] => /in/fKQAc [line] => 187 [code] => 8 [msg] => Array to string conversion [trace] => Array ( [1] => Array ( [file] => /in/fKQAc [line] => 191 [function] => login [class] => Example [object] => Array ( [iNumber (integer)] => 12345 [sPassword (string)] => **REDACTED_PER_INDEX** [sInfo (string)] => 'a password?' ) [type] => -> [args] => Array ( [0 (string)] => **REDACTED_PER_FUNCTION** [1 (string)] => **REDACTED_PER_FUNCTION** ) ) [2] => Array ( [file] => /in/fKQAc [line] => 237 [function] => test [class] => Example [object] => Array ( [iNumber (integer)] => 12345 [sPassword (string)] => **REDACTED_PER_INDEX** [sInfo (string)] => 'a password?' ) [type] => -> [args] => Array ( [0 (array)] => Array ( ['string' (string)] => 'Text' ['int' (integer)] => 42 ['float' (double)] => 3.1415 ['bool' (boolean)] => 1 ['array' (array)] => Array ( ['key' (string)] => 'value' ['db_password' (string)] => **REDACTED_PER_INDEX** ) ['obj' (object DateTime)] => Array ( [date (string)] => '2023-07-31 19:47:39.000000' [timezone_type (integer)] => 3 [timezone (string)] => **REDACTED_PER_INDEX** ) ['pass' (string)] => **REDACTED_PER_INDEX** [110 (string)] => 'ordinal index' ) [1 (array)] => Array ( [0 (string)] => 'no index identifying a password' [1 (string)] => 'username' ) ) ) ) ) ArrayArray ( [where] => Error handler [file] => /in/fKQAc [line] => 195 [code] => 2 [msg] => Division by zero [trace] => Array ( [1] => Array ( [file] => /in/fKQAc [line] => 237 [function] => test [class] => Example [object] => Array ( [iNumber (integer)] => 12345 [sPassword (string)] => **REDACTED_PER_INDEX** [sInfo (string)] => 'a password?' ) [type] => -> [args] => Array ( [0 (array)] => Array ( ['string' (string)] => 'Text' ['int' (integer)] => 42 ['float' (double)] => 3.1415 ['bool' (boolean)] => 1 ['array' (array)] => Array ( ['key' (string)] => 'value' ['db_password' (string)] => **REDACTED_PER_INDEX** ) ['obj' (object DateTime)] => Array ( [date (string)] => '2023-07-31 19:47:39.000000' [timezone_type (integer)] => 3 [timezone (string)] => **REDACTED_PER_INDEX** ) ['pass' (string)] => **REDACTED_PER_INDEX** [110 (string)] => 'ordinal index' ) [1 (array)] => Array ( [0 (string)] => 'no index identifying a password' [1 (string)] => 'username' ) ) ) ) ) Array ( [where] => Error handler [file] => /in/fKQAc [line] => 196 [code] => 8 [msg] => Undefined variable: x [trace] => Array ( [1] => Array ( [file] => /in/fKQAc [line] => 237 [function] => test [class] => Example [object] => Array ( [iNumber (integer)] => 12345 [sPassword (string)] => **REDACTED_PER_INDEX** [sInfo (string)] => 'a password?' ) [type] => -> [args] => Array ( [0 (array)] => Array ( ['string' (string)] => 'Text' ['int' (integer)] => 42 ['float' (double)] => 3.1415 ['bool' (boolean)] => 1 ['array' (array)] => Array ( ['key' (string)] => 'value' ['db_password' (string)] => **REDACTED_PER_INDEX** ) ['obj' (object DateTime)] => Array ( [date (string)] => '2023-07-31 19:47:39.000000' [timezone_type (integer)] => 3 [timezone (string)] => **REDACTED_PER_INDEX** ) ['pass' (string)] => **REDACTED_PER_INDEX** [110 (string)] => 'ordinal index' ) [1 (array)] => Array ( [0 (string)] => 'no index identifying a password' [1 (string)] => 'username' ) ) ) ) ) Array ( [where] => Exception handler [file] => /in/fKQAc [line] => 197 [code] => 0 [msg] => TestException [trace] => Array ( [0] => Array ( [file] => /in/fKQAc [line] => 237 [function] => test [class] => Example [type] => -> [args] => Array ( [0 (array)] => Array ( ['string' (string)] => 'Text' ['int' (integer)] => 42 ['float' (double)] => 3.1415 ['bool' (boolean)] => 1 ['array' (array)] => Array ( ['key' (string)] => 'value' ['db_password' (string)] => **REDACTED_PER_INDEX** ) ['obj' (object DateTime)] => Array ( [date (string)] => '2023-07-31 19:47:39.000000' [timezone_type (integer)] => 3 [timezone (string)] => **REDACTED_PER_INDEX** ) ['pass' (string)] => **REDACTED_PER_INDEX** [110 (string)] => 'ordinal index' ) [1 (array)] => Array ( [0 (string)] => 'no index identifying a password' [1 (string)] => 'username' ) ) ) ) [class] => Exception )

preferences:
88.35 ms | 459 KiB | 5 Q