diff --git a/Kint.class.php b/Kint.class.php new file mode 100644 index 0000000..61b3443 --- /dev/null +++ b/Kint.class.php @@ -0,0 +1,772 @@ + 0 ) { + self::$enabled = $value; + return; + } + + // ...and a getter + return self::$enabled; + } + + public static function _init() + { + // init settings + if ( isset( $GLOBALS['_kint_settings'] ) ) { + foreach ( $GLOBALS['_kint_settings'] as $key => $val ) { + self::$$key = $val; + } + } + self::$_richDecorator = new kintRichDecorator; + self::$_plainDecorator = new kintPlainDecorator; + + self::$pathDisplayCallback or self::$pathDisplayCallback = "kint::_debugPath"; + } + + /** + * Prints a debug backtrace + * + * @param array $trace [OPTIONAL] you can pass your own trace, otherwise, `debug_backtrace` will be called + * + * @return void + */ + public static function trace( $trace = NULL ) + { + if ( !Kint::enabled() ) return; + + echo self::$_richDecorator->_css(); + + isset( $trace ) or $trace = debug_backtrace( true ); + + $output = array(); + foreach ( $trace as $step ) { + self::$traceCleanupCallback and $step = call_user_func( self::$traceCleanupCallback, $step ); + + // if the user defined trace cleanup function returns null, skip this line + if ( $step === null ) { + continue; + } + + if ( !isset( $step['function'] ) ) { + // Invalid trace step + continue; + } + + if ( isset( $step['file'] ) AND isset( $step['line'] ) ) { + // Include the source of this step + $source = self::_debugSource( $step['file'], $step['line'] ); + } + + if ( isset( $step['file'] ) ) { + $file = $step['file']; + + if ( isset( $step['line'] ) ) { + $line = $step['line']; + } + } + + + $function = $step['function']; + + if ( in_array( $step['function'], self::$_statements ) ) { + if ( empty( $step['args'] ) ) { + // No arguments + $args = array(); + } else { + // Sanitize the file path + $args = array( self::_debugPath( $step['args'][0] ) ); + } + } elseif ( isset( $step['args'] ) ) { + if ( empty( $step['class'] ) && !function_exists( $step['function'] ) ) { + // Introspection on closures or language constructs in a stack trace is impossible before PHP 5.3 + $params = NULL; + } else { + if ( isset( $step['class'] ) ) { + if ( method_exists( $step['class'], $step['function'] ) ) { + $reflection = new ReflectionMethod( $step['class'], $step['function'] ); + } else { + $reflection = new ReflectionMethod( $step['class'], '__call' ); + } + } else { + $reflection = new ReflectionFunction( $step['function'] ); + } + + // Get the function parameters + $params = $reflection->getParameters(); + } + + $args = array(); + foreach ( $step['args'] as $i => $arg ) { + if ( isset( $params[$i] ) ) { + // Assign the argument by the parameter name + $args[$params[$i]->name] = $arg; + } else { + // Assign the argument by number + $args[$i] = $arg; + } + } + } + + if ( isset( $step['class'] ) ) { + // Class->method() or Class::method() + $function = $step['class'] . $step['type'] . $step['function']; + } + + if ( isset( $step['object'] ) ) { + $function = $step['class'] . $step['type'] . $step['function']; + } + + $output[] = array( + 'function' => $function, + 'args' => isset( $args ) ? $args : null, + 'file' => isset( $file ) ? $file : null, + 'line' => isset( $line ) ? $line : null, + 'source' => isset( $source ) ? $source : null, + 'object' => isset( $step['object'] ) ? $step['object'] : null, + ); + + unset( $function, $args, $file, $line, $source ); + } + + require KINT_DIR . 'view/trace.phtml'; + } + + /** + * dump information about variables + * + * @param mixed $data + * + * @return void|string + */ + public static function dump( $data = null ) + { + if ( !Kint::enabled() ) return; + + + $data = func_num_args() === 0 + ? array( "[[no arguments passed]]" ) // todo if no arguments were provided, dump the whole environment + : func_get_args(); + + + // find caller information + $trace = debug_backtrace(); + list( $names, $modifier, $callee, $previousCaller ) = self::_getPassedNames( $trace ); + + // process modifiers: @, + and - + switch ( $modifier ) { + case '-': + self::$_firstRun = TRUE; + ob_clean(); + break; + case '+': + $maxLevelsOldValue = self::$maxLevels; + self::$maxLevels = 0; + break; + } + + $output = self::$_richDecorator->_css() + . self::$_richDecorator->_wrapStart( $callee ); + + foreach ( $data as $k => $argument ) { + + // sometimes it's beneficial to display each key of passed array as separate variable, e.g. function + // parameters. Catch these cases and display appropriately. Note this applies when there is only one + // array passed + $displayAsVarList = false; + if ( sizeof( $data ) === 1 ) foreach ( self::$_displayAsVariableList as $pattern ) { + if ( is_array( $argument ) && !empty( $argument ) && substr( $names[$k], 0, strlen( $pattern ) ) === $pattern ) { + $displayAsVarList = true; + break; + } + } + + if ( $displayAsVarList ) { + foreach ( $argument as $name => $value ) { + $name = is_numeric( $name ) ? '' : '$' . $name; + $output .= self::_dump( $value, $name ); + } + } else { + $output .= self::_dump( $argument, $names[$k] ); + } + + } + $output .= self::$_richDecorator->_wrapEnd( $callee, $previousCaller ); + + self::$_firstRun = false; + + if ( $modifier === '+' ) { + self::$maxLevels = $maxLevelsOldValue; + } + + if ( $modifier === '@' ) { + self::$_firstRun = true; + return $output; + } else { + echo $output; + return ''; + } + } + + protected static function _dump( $var, $name = '' ) + { + return self::$_richDecorator->decorate( + kintParser::factory( $var, $name ) + ); + } + + + /** + * generic path display callback, can be changed in the settings + * + * @param string $file + * @param int $line [OPTIONAL] + * + * @return string + */ + private static function _debugPath( $file, $line = NULL ) + { + if ( !$line ) { // called from resource dump + return $file; + } + + $path = str_replace( '/', '\\', $file ); + $root = str_replace( '/', '\\', $_SERVER['DOCUMENT_ROOT'] ); + + if ( strpos( $path, $root ) === 0 ) { + $path = 'ROOT' . substr( $path, strlen( $root ) ); + } + + return "" . $path . " line {$line}"; + } + + + /** + * trace helper, shows the place in code inline + * + * @param string $file full path to file + * @param int $lineNumber the line to display + * @param int $padding surrounding lines to show besides the main one + * + * @return bool|string + */ + private static function _debugSource( $file, $lineNumber, $padding = 7 ) + { + if ( !$file OR !is_readable( $file ) ) { + // Continuing will cause errors + return false; + } + + // Open the file and set the line position + $file = fopen( $file, 'r' ); + $line = 0; + + // Set the reading range + $range = array( + 'start' => $lineNumber - $padding, + 'end' => $lineNumber + $padding + ); + + // Set the zero-padding amount for line numbers + $format = '% ' . strlen( $range['end'] ) . 'd'; + + $source = ''; + while ( ( $row = fgets( $file ) ) !== false ) { + // Increment the line number + if ( ++$line > $range['end'] ) { + break; + } + + if ( $line >= $range['start'] ) { + // Make the row safe for output + $row = htmlspecialchars( $row, ENT_NOQUOTES ); + + // Trim whitespace and sanitize the row + $row = '' . sprintf( $format, $line ) . ' ' . $row; + + if ( $line === $lineNumber ) { + // Apply highlighting to this row + $row = '
'; + foreach ( $argv as $k => $v ) { + $k && print( "\n\n" ); + echo kintLite( $v ); + } + echo ''; + } + + /** + * Alias of kintLite() + * [!!!] IMPORTANT: execution will halt after call to this function + * + * @return string + */ + function sd() + { + if ( !Kint::enabled() ) return; + + echo '
'; + foreach ( func_get_args() as $k => $v ) { + $k && print( "\n\n" ); + echo kintLite( $v ); + } + echo ''; + die; + + } + +} + + +/** + * lightweight version of Kint::dump(). Uses whitespace for formatting instead of html + * sadly not DRY yet + * + * @param $var + * @param int $level + * + * @return string + */ +function kintLite( &$var, $level = 0 ) +{ + + // initialize function names into variables for prettier string output (html and implode are also DRY) + $html = "htmlspecialchars"; + $implode = "implode"; + $strlen = "strlen"; + $count = "count"; + $getClass = "get_class"; + + + if ( $var === NULL ) { + return 'NULL'; + } + elseif ( is_bool( $var ) ) { + return 'bool ' . ( $var ? 'TRUE' : 'FALSE' ); + } + elseif ( is_bool( $var ) ) { + return 'bool ' . ( $var ? 'TRUE' : 'FALSE' ); + } + elseif ( is_float( $var ) ) { + return 'float ' . $var; + } + elseif ( is_int( $var ) ) { + return 'integer ' . $var; + } + elseif ( is_resource( $var ) ) { + if ( ( $type = get_resource_type( $var ) ) === 'stream' AND $meta = stream_get_meta_data( $var ) ) { + + if ( isset( $meta['uri'] ) ) { + $file = $meta['uri']; + + return "resource ({$type}) {$html($file,0)}"; + } + else { + return "resource ({$type})"; + } + } + else { + return "resource ({$type})"; + } + } + elseif ( is_string( $var ) ) { + return "string ({$strlen($var)}) \"{$html($var)}\""; + } + elseif ( is_array( $var ) ) { + $output = array(); + $space = str_repeat( $s = ' ', $level ); + + static $marker; + + if ( $marker === NULL ) { + // Make a unique marker + $marker = uniqid( "\x00" ); + } + + if ( empty( $var ) ) { + return "array()"; + } + elseif ( isset( $var[$marker] ) ) { + $output[] = "[\n$space$s*RECURSION*\n$space]"; + } + elseif ( $level < 7 ) { + $isSeq = array_keys( $var ) === range( 0, count( $var ) - 1 ); + + $output[] = "["; + + $var[$marker] = TRUE; + + + foreach ( $var as $key => &$val ) { + if ( $key === $marker ) continue; + + $key = $space . $s . ( $isSeq ? "" : "'{$html($key,0)}' => " ); + + $dump = kintLite( $val, $level + 1 ); + $output[] = "{$key}{$dump}"; + } + + unset( $var[$marker] ); + $output[] = "$space]"; + + } + else { + $output[] = "[\n$space$s*depth too great*\n$space]"; + } + return "array({$count($var)}) {$implode("\n", $output)}"; + } + elseif ( is_object( $var ) ) { + if ( $var instanceof SplFileInfo ) { + return "object SplFileInfo " . $var->getRealPath(); + } + + // Copy the object as an array + $array = (array)$var; + + $output = array(); + $space = str_repeat( $s = ' ', $level ); + + $hash = spl_object_hash( $var ); + + // Objects that are being dumped + static $objects = array(); + + if ( empty( $array ) ) { + return "object {$getClass($var)} {}"; + } + elseif ( isset( $objects[$hash] ) ) { + $output[] = "{\n$space$s*RECURSION*\n$space}"; + } + elseif ( $level < 7 ) { + $output[] = "{"; + $objects[$hash] = TRUE; + + $reflection = new ReflectionClass( $var ); + foreach ( $reflection->getProperties( ReflectionProperty::IS_STATIC ) as $property ) { + if ( $property->isPrivate() ) { + $property->setAccessible( true ); + $access = "private"; + } elseif ( $property->isProtected() ) { + $property->setAccessible( true ); + $access = "protected"; + } else { + $access = 'public'; + } + $access = $access . " static"; + $key = $property->getName(); + + $value = $property->getValue(); + $output[] = "$space$s{$access} {$key} :: " . kintLite( $value, $level + 1 ); + } + + foreach ( $array as $key => & $val ) { + if ( $key[0] === "\x00" ) { + + $access = $key[1] === "*" ? "protected" : "private"; + + // Remove the access level from the variable name + $key = substr( $key, strrpos( $key, "\x00" ) + 1 ); + } + else { + $access = "public"; + } + + $output[] = "$space$s$access $key -> " . kintLite( $val, $level + 1 ); + } + unset( $objects[$hash] ); + $output[] = "$space}"; + + } + else { + $output[] = "{\n$space$s*depth too great*\n$space}"; + } + + return "object {$getClass($var)} ({$count($array)}) {$implode("\n", $output)}"; + } + else { + return gettype( $var ) . htmlspecialchars( var_export( $var, true ), ENT_NOQUOTES ); + } +} + +Kint::_init(); \ No newline at end of file