self::$_level, 'objects' => self::$_objects, ); self::$_level++; $varData = new kintVariableData; $varData->name = $name; # first parse the variable based on its type $varType = gettype( $variable ); $varType === 'unknown type' and $varType = 'unknown'; # PHP 5.4 inconsistency $methodName = '_parse_' . $varType; # objects can be presented in a different way altogether, INSTEAD, not ALONGSIDE the generic parser if ( $varType === 'object' ) { foreach ( self::$_objectParsers as $parserClass ) { $className = 'Kint_Objects_' . $parserClass; /** @var $object KintObject */ $object = new $className; if ( ( $alternativeTabs = $object->parse( $variable ) ) !== false ) { self::$_skipAlternatives = true; $alternativeDisplay = new kintVariableData; $alternativeDisplay->type = $object->name; $alternativeDisplay->value = $object->value; $alternativeDisplay->name = $name; foreach ( $alternativeTabs as $name => $values ) { $alternative = kintParser::factory( $values ); $alternative->type = $name; if ( Kint::enabled() === Kint::MODE_RICH ) { empty( $alternative->value ) and $alternative->value = $alternative->extendedValue; $alternativeDisplay->_alternatives[] = $alternative; } else { $alternativeDisplay->extendedValue[] = $alternative; } } self::$_skipAlternatives = false; self::$_level = $revert['level']; self::$_objects = $revert['objects']; return $alternativeDisplay; } } } # base type parser returning false means "stop processing further": e.g. recursion if ( self::$methodName( $variable, $varData ) === false ) { self::$_level--; return $varData; } if ( Kint::enabled() === Kint::MODE_RICH && !self::$_skipAlternatives ) { # if an alternative returns something that can be represented in an alternative way, don't :) self::$_skipAlternatives = true; # now check whether the variable can be represented in a different way foreach ( self::$_customDataTypes as $parserClass ) { $className = 'Kint_Parsers_' . $parserClass; /** @var $parser kintParser */ $parser = new $className; $parser->name = $name; # the parser may overwrite the name value, so set it first if ( $parser->_parse( $variable ) !== false ) { $varData->_alternatives[] = $parser; } } # if alternatives exist, push extendedValue to their front and display it as one of alternatives if ( !empty( $varData->_alternatives ) && isset( $varData->extendedValue ) ) { $_ = new kintVariableData; $_->value = $varData->extendedValue; $_->type = 'contents'; $_->size = null; array_unshift( $varData->_alternatives, $_ ); $varData->extendedValue = null; } self::$_skipAlternatives = false; } self::$_level = $revert['level']; self::$_objects = $revert['objects']; if ( strlen( $varData->name ) > 80 ) { $varData->name = self::_substr( $varData->name, 0, 37 ) . '...' . self::_substr( $varData->name, -38, null ); } return $varData; } private static function _checkDepth() { return Kint::$maxLevels != 0 && self::$_level >= Kint::$maxLevels; } private static function _isArrayTabular( array $variable ) { if ( Kint::enabled() !== Kint::MODE_RICH ) return false; $arrayKeys = array(); $keys = null; $closeEnough = false; foreach ( $variable as $row ) { if ( !is_array( $row ) || empty( $row ) ) return false; foreach ( $row as $col ) { if ( !empty( $col ) && !is_scalar( $col ) ) return false; // todo add tabular "tolerance" } if ( isset( $keys ) && !$closeEnough ) { # let's just see if the first two rows have same keys, that's faster and has the # positive side effect of easily spotting missing keys in later rows if ( $keys !== array_keys( $row ) ) return false; $closeEnough = true; } else { $keys = array_keys( $row ); } $arrayKeys = array_unique( array_merge( $arrayKeys, $keys ) ); } return $arrayKeys; } private static function _decorateCell( kintVariableData $kintVar ) { if ( $kintVar->extendedValue !== null || !empty( $kintVar->_alternatives ) ) { return '' . Kint_Decorators_Rich::decorate( $kintVar ) . ''; } $output = 'value !== null ) { $output .= ' title="' . $kintVar->type; if ( $kintVar->size !== null ) { $output .= " (" . $kintVar->size . ")"; } $output .= '">' . $kintVar->value; } else { $output .= '>'; if ( $kintVar->type !== 'NULL' ) { $output .= '' . $kintVar->type; if ( $kintVar->size !== null ) { $output .= "(" . $kintVar->size . ")"; } $output .= ''; } else { $output .= 'NULL'; } } return $output . ''; } public static function escape( $value, $encoding = null ) { if ( empty( $value ) ) return $value; if ( Kint::enabled() === Kint::MODE_CLI ) { $value = str_replace( "\x1b", "\\x1b", $value ); } if ( Kint::enabled() === Kint::MODE_CLI || Kint::enabled() === Kint::MODE_WHITESPACE ) return $value; $encoding or $encoding = self::_detectEncoding( $value ); $value = htmlspecialchars( $value, ENT_NOQUOTES, $encoding === 'ASCII' ? 'UTF-8' : $encoding ); if ( $encoding === 'UTF-8' ) { // todo we could make the symbols hover-title show the code for the invisible symbol # when possible force invisible characters to have some sort of display (experimental) $value = preg_replace( '/[\x00-\x08\x0B\x0C\x0E-\x1F\x80-\x9F]/u', '?', $value ); } # this call converts all non-ASCII characters into html chars of format if ( function_exists( 'mb_encode_numericentity' ) ) { $value = mb_encode_numericentity( $value, array( 0x80, 0xffff, 0, 0xffff, ), $encoding ); } return $value; } private static $_dealingWithGlobals = false; private static function _parse_array( &$variable, kintVariableData $variableData ) { isset( self::$_marker ) or self::$_marker = "\x00" . uniqid(); # naturally, $GLOBALS variable is an intertwined recursion nightmare, use black magic $globalsDetector = false; if ( array_key_exists( 'GLOBALS', $variable ) && is_array( $variable['GLOBALS'] ) ) { $globalsDetector = "\x01" . uniqid(); $variable['GLOBALS'][ $globalsDetector ] = true; if ( isset( $variable[ $globalsDetector ] ) ) { unset( $variable[ $globalsDetector ] ); self::$_dealingWithGlobals = true; } else { unset( $variable['GLOBALS'][ $globalsDetector ] ); $globalsDetector = false; } } $variableData->type = 'array'; $variableData->size = count( $variable ); if ( $variableData->size === 0 ) { return; } if ( isset( $variable[ self::$_marker ] ) ) { # recursion; todo mayhaps show from where if ( self::$_dealingWithGlobals ) { $variableData->value = '*RECURSION*'; } else { unset( $variable[ self::$_marker ] ); $variableData->value = self::$_marker; } return false; } if ( self::_checkDepth() ) { $variableData->extendedValue = "*DEPTH TOO GREAT*"; return false; } $isSequential = self::_isSequential( $variable ); if ( $variableData->size > 1 && ( $arrayKeys = self::_isArrayTabular( $variable ) ) !== false ) { $variable[ self::$_marker ] = true; # this must be AFTER _isArrayTabular $firstRow = true; $extendedValue = ''; foreach ( $variable as $rowIndex => & $row ) { # display strings in their full length self::$_placeFullStringInValue = true; if ( $rowIndex === self::$_marker ) continue; if ( isset( $row[ self::$_marker ] ) ) { $variableData->value = "*RECURSION*"; return false; } $extendedValue .= ''; if ( $isSequential ) { $output = ''; } else { $output = self::_decorateCell( kintParser::factory( $rowIndex ) ); } if ( $firstRow ) { $extendedValue .= ''; } # we iterate the known full set of keys from all rows in case some appeared at later rows, # as we only check the first two to assume foreach ( $arrayKeys as $key ) { if ( $firstRow ) { $extendedValue .= ''; } if ( !array_key_exists( $key, $row ) ) { $output .= ''; continue; } $var = kintParser::factory( $row[ $key ] ); if ( $var->value === self::$_marker ) { $variableData->value = '*RECURSION*'; return false; } elseif ( $var->value === '*RECURSION*' ) { $output .= ''; } else { $output .= self::_decorateCell( $var ); } unset( $var ); } if ( $firstRow ) { $extendedValue .= ''; $firstRow = false; } $extendedValue .= $output . ''; } self::$_placeFullStringInValue = false; $variableData->extendedValue = $extendedValue . '
' . '#' . ( $rowIndex + 1 ) . ' ' . self::escape( $key ) . '*RECURSION*
'; } else { $variable[ self::$_marker ] = true; $extendedValue = array(); foreach ( $variable as $key => & $val ) { if ( $key === self::$_marker ) continue; $output = kintParser::factory( $val ); if ( $output->value === self::$_marker ) { $variableData->value = "*RECURSION*"; // recursion occurred on a higher level, thus $this is recursion return false; } if ( !$isSequential ) { $output->operator = '=>'; } $output->name = $isSequential ? null : "'" . $key . "'"; $extendedValue[] = $output; } $variableData->extendedValue = $extendedValue; } if ( $globalsDetector ) { self::$_dealingWithGlobals = false; } unset( $variable[ self::$_marker ] ); } private static function _parse_object( &$variable, kintVariableData $variableData ) { if ( function_exists( 'spl_object_hash' ) ) { $hash = spl_object_hash( $variable ); } else { ob_start(); var_dump( $variable ); preg_match( '[#(\d+)]', ob_get_clean(), $match ); $hash = $match[1]; } $castedArray = (array) $variable; $variableData->type = get_class( $variable ); $variableData->size = count( $castedArray ); if ( isset( self::$_objects[ $hash ] ) ) { $variableData->value = '*RECURSION*'; return false; } if ( self::_checkDepth() ) { $variableData->extendedValue = "*DEPTH TOO GREAT*"; return false; } # ArrayObject (and maybe ArrayIterator, did not try yet) unsurprisingly consist of mainly dark magic. # What bothers me most, var_dump sees no problem with it, and ArrayObject also uses a custom, # undocumented serialize function, so you can see the properties in internal functions, but # can never iterate some of them if the flags are not STD_PROP_LIST. Fun stuff. if ( $variableData->type === 'ArrayObject' || is_subclass_of( $variable, 'ArrayObject' ) ) { $arrayObjectFlags = $variable->getFlags(); $variable->setFlags( ArrayObject::STD_PROP_LIST ); } self::$_objects[ $hash ] = true; // todo store reflectorObject here for alternatives cache $reflector = new ReflectionObject( $variable ); # add link to definition of userland objects if ( Kint::enabled() === Kint::MODE_RICH && Kint::$fileLinkFormat && $reflector->isUserDefined() ) { $url = Kint::getIdeLink( $reflector->getFileName(), $reflector->getStartLine() ); $class = ( strpos( $url, 'http://' ) === 0 ) ? 'class="kint-ide-link" ' : ''; $variableData->type = "{$variableData->type}"; } $variableData->size = 0; $extendedValue = array(); $encountered = array(); # copy the object as an array as it provides more info than Reflection (depends) foreach ( $castedArray as $key => $value ) { /* casting object to array: * integer properties are inaccessible; * private variables have the class name prepended to the variable name; * protected variables have a '*' prepended to the variable name. * These prepended values have null bytes on either side. * http://www.php.net/manual/en/language.types.array.php#language.types.array.casting */ 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"; } $encountered[ $key ] = true; $output = kintParser::factory( $value, self::escape( $key ) ); $output->access = $access; $output->operator = '->'; $extendedValue[] = $output; $variableData->size++; } foreach ( $reflector->getProperties() as $property ) { $name = $property->name; if ( $property->isStatic() || isset( $encountered[ $name ] ) ) continue; if ( $property->isProtected() ) { $property->setAccessible( true ); $access = "protected"; } elseif ( $property->isPrivate() ) { $property->setAccessible( true ); $access = "private"; } else { $access = "public"; } $value = $property->getValue( $variable ); $output = kintParser::factory( $value, self::escape( $name ) ); $output->access = $access; $output->operator = '->'; $extendedValue[] = $output; $variableData->size++; } if ( isset( $arrayObjectFlags ) ) { $variable->setFlags( $arrayObjectFlags ); } if ( $variableData->size ) { $variableData->extendedValue = $extendedValue; } } private static function _parse_boolean( &$variable, kintVariableData $variableData ) { $variableData->type = 'bool'; $variableData->value = $variable ? 'TRUE' : 'FALSE'; } private static function _parse_double( &$variable, kintVariableData $variableData ) { $variableData->type = 'float'; $variableData->value = $variable; } private static function _parse_integer( &$variable, kintVariableData $variableData ) { $variableData->type = 'integer'; $variableData->value = $variable; } private static function _parse_null( &$variable, kintVariableData $variableData ) { $variableData->type = 'NULL'; } private static function _parse_resource( &$variable, kintVariableData $variableData ) { $resourceType = get_resource_type( $variable ); $variableData->type = "resource ({$resourceType})"; if ( $resourceType === 'stream' && $meta = stream_get_meta_data( $variable ) ) { if ( isset( $meta['uri'] ) ) { $file = $meta['uri']; if ( function_exists( 'stream_is_local' ) ) { // Only exists on PHP >= 5.2.4 if ( stream_is_local( $file ) ) { $file = Kint::shortenPath( $file ); } } $variableData->value = $file; } } } private static function _parse_string( &$variable, kintVariableData $variableData ) { $variableData->type = 'string'; $encoding = self::_detectEncoding( $variable ); if ( $encoding !== 'ASCII' ) { $variableData->type .= ' ' . $encoding; } $variableData->size = self::_strlen( $variable, $encoding ); if ( Kint::enabled() !== Kint::MODE_RICH ) { $variableData->value = '"' . self::escape( $variable, $encoding ) . '"'; return; } if ( !self::$_placeFullStringInValue ) { $strippedString = preg_replace( '[\s+]', ' ', $variable ); if ( Kint::$maxStrLength && $variableData->size > Kint::$maxStrLength ) { // encode and truncate $variableData->value = '"' . self::escape( self::_substr( $strippedString, 0, Kint::$maxStrLength, $encoding ), $encoding ) . '…"'; $variableData->extendedValue = self::escape( $variable, $encoding ); return; } elseif ( $variable !== $strippedString ) { // omit no data from display $variableData->value = '"' . self::escape( $variable, $encoding ) . '"'; $variableData->extendedValue = self::escape( $variable, $encoding ); return; } } $variableData->value = '"' . self::escape( $variable, $encoding ) . '"'; } private static function _parse_unknown( &$variable, kintVariableData $variableData ) { $type = gettype( $variable ); $variableData->type = "UNKNOWN" . ( !empty( $type ) ? " ({$type})" : '' ); $variableData->value = var_export( $variable, true ); } }