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 );

Here you find the average performance (time & memory) of each version. A grayed out version indicates it didn't complete successfully (based on exit-code).

VersionSystem time (s)User time (s)Memory (MiB)
8.4.130.0130.00918.31
8.4.120.0090.00920.81
8.4.110.0090.00819.08
8.4.100.0110.00918.16
8.4.90.0110.01020.52
8.4.80.0090.01118.94
8.4.70.0060.00920.74
8.4.60.0150.00520.44
8.4.50.0070.01018.73
8.4.40.0170.00318.29
8.4.30.0130.00622.40
8.4.20.0100.01022.43
8.4.10.0050.00523.90
8.3.260.0100.01016.96
8.3.250.0090.00818.94
8.3.240.0110.00917.21
8.3.230.0110.01017.14
8.3.220.0150.00419.08
8.3.210.0080.00716.94
8.3.200.0060.00417.15
8.3.190.0140.00517.49
8.3.180.0100.01019.00
8.3.170.0090.00920.66
8.3.160.0100.01017.25
8.3.150.0090.00017.44
8.3.140.0100.01019.34
8.3.130.0060.00318.71
8.3.120.0090.00020.68
8.3.110.0040.00717.26
8.3.100.0040.00417.40
8.3.90.0000.00826.77
8.3.80.0030.00618.80
8.3.70.0000.01517.13
8.3.60.0090.00618.58
8.3.50.0070.01517.24
8.3.40.0040.01121.95
8.3.30.0090.00620.29
8.3.20.0040.00424.18
8.3.10.0000.00824.66
8.3.00.0050.00226.16
8.2.290.0150.00720.40
8.2.280.0120.00918.68
8.2.270.0090.00917.45
8.2.260.0090.00917.10
8.2.250.0080.00817.20
8.2.240.0040.00418.87
8.2.230.0040.00420.94
8.2.220.0060.00324.06
8.2.210.0060.01326.77
8.2.200.0060.00317.25
8.2.190.0170.01018.55
8.2.180.0100.01016.98
8.2.170.0030.01619.46
8.2.160.0070.01022.96
8.2.150.0080.00025.66
8.2.140.0000.00924.66
8.2.130.0050.00326.16
8.2.120.0000.00721.25
8.2.110.0070.00320.64
8.2.100.0030.00620.41
8.1.330.0090.01216.98
8.1.320.0050.00616.78
8.1.310.0070.01116.88
8.1.300.0030.00618.07
8.1.290.0040.00430.84
8.1.280.0160.00625.92
8.1.270.0000.00924.66
8.1.260.0070.00326.35
8.1.250.0080.00028.09
8.1.240.0040.00719.12
8.1.230.0090.00018.79
7.0.330.0150.00618.29
5.6.400.0080.00479.33

preferences:
28.42 ms | 403 KiB | 5 Q