123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608 |
- <?php
- abstract class kintParser extends kintVariableData
- {
- private static $_level = 0;
- private static $_customDataTypes;
- private static $_objectParsers;
- private static $_objects;
- private static $_marker;
- private static $_skipAlternatives = false;
- private static $_placeFullStringInValue = false;
- private static function _init()
- {
- $fh = opendir( KINT_DIR . 'parsers/custom/' );
- while ( $fileName = readdir( $fh ) ) {
- if ( substr( $fileName, -4 ) !== '.php' ) continue;
- require KINT_DIR . 'parsers/custom/' . $fileName;
- self::$_customDataTypes[] = substr( $fileName, 0, -4 );
- }
- $fh = opendir( KINT_DIR . 'parsers/objects/' );
- while ( $fileName = readdir( $fh ) ) {
- if ( substr( $fileName, -4 ) !== '.php' ) continue;
- require KINT_DIR . 'parsers/objects/' . $fileName;
- self::$_objectParsers[] = substr( $fileName, 0, -4 );
- }
- }
- public static function reset()
- {
- self::$_level = 0;
- self::$_objects = self::$_marker = null;
- }
- /**
- * main and usually single method a custom parser must implement
- *
- * @param mixed $variable
- *
- * @return mixed [!!!] false is returned if the variable is not of current type
- */
- abstract protected function _parse( & $variable );
- /**
- * the only public entry point to return a parsed representation of a variable
- *
- * @static
- *
- * @param $variable
- * @param null $name
- *
- * @throws Exception
- * @return \kintParser
- */
- public final static function factory( & $variable, $name = null )
- {
- isset( self::$_customDataTypes ) or self::_init();
- # save internal data to revert after dumping to properly handle recursions etc
- $revert = array(
- 'level' => 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 '<td>' . Kint_Decorators_Rich::decorate( $kintVar ) . '</td>';
- }
- $output = '<td';
- if ( $kintVar->value !== null ) {
- $output .= ' title="' . $kintVar->type;
- if ( $kintVar->size !== null ) {
- $output .= " (" . $kintVar->size . ")";
- }
- $output .= '">' . $kintVar->value;
- } else {
- $output .= '>';
- if ( $kintVar->type !== 'NULL' ) {
- $output .= '<u>' . $kintVar->type;
- if ( $kintVar->size !== null ) {
- $output .= "(" . $kintVar->size . ")";
- }
- $output .= '</u>';
- } else {
- $output .= '<u>NULL</u>';
- }
- }
- return $output . '</td>';
- }
- 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 = '<table class="kint-report"><thead>';
- 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 .= '<tr>';
- if ( $isSequential ) {
- $output = '<td>' . '#' . ( $rowIndex + 1 ) . '</td>';
- } else {
- $output = self::_decorateCell( kintParser::factory( $rowIndex ) );
- }
- if ( $firstRow ) {
- $extendedValue .= '<th> </th>';
- }
- # 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 .= '<th>' . self::escape( $key ) . '</th>';
- }
- if ( !array_key_exists( $key, $row ) ) {
- $output .= '<td class="kint-empty"></td>';
- continue;
- }
- $var = kintParser::factory( $row[ $key ] );
- if ( $var->value === self::$_marker ) {
- $variableData->value = '*RECURSION*';
- return false;
- } elseif ( $var->value === '*RECURSION*' ) {
- $output .= '<td class="kint-empty"><u>*RECURSION*</u></td>';
- } else {
- $output .= self::_decorateCell( $var );
- }
- unset( $var );
- }
- if ( $firstRow ) {
- $extendedValue .= '</tr></thead><tr>';
- $firstRow = false;
- }
- $extendedValue .= $output . '</tr>';
- }
- self::$_placeFullStringInValue = false;
- $variableData->extendedValue = $extendedValue . '</table>';
- } 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 = "<a {$class}href=\"{$url}\">{$variableData->type}</a>";
- }
- $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 );
- }
- }
|