Home > Software design >  How to emulate/capture unusual PHP operands from Kint
How to emulate/capture unusual PHP operands from Kint

Time:11-05

Background (Intro)
Kint is a PHP debugging tool that works as a more powerful replacement to PHP's var_dump(), print_r(), and debug_backtrace(). One unusual -- for PHP at least -- feature of Kint is the ability to use real-time modifiers in the form of operands. Here is what the manual states about that feature:

There are a couple of real-time modifiers you can use:

  • ~d($var) this call will output in plain text format.
  • d($var) will disregard depth level limits and output everything. Careful, this can hang your browser on large objects!
  • !d($var) will expand the output automatically.
  • -d($var) will attempt to ob_clean the previous output and flush after printing.
  • You can combine modifiers too: ~ d($var)

There is an older exiting SO question that is similar to this question if you need more information.


Questions

  1. How does Kint add these operands without triggering a PHP error?
  2. How can I emulate/capture any calls that use these operands?

Without loading Kint if you attempt to use these operands or create your own functions to capture Kint operand calls you get Fatal error: Uncaught Error: Unsupported operand types.

Important: I am using the kint.phar file and I'm not using composer or any kind of CLI usage.



My Use Case (Please don't get distracted from the Questions)
I'm adding this information for those that are curious and to further clarify my questions. I sincerely want to learn and understand HOW they are doing this and would appreciate answers to that end. This question is not about defending / critiquing / disagreeing with my use case:

For security and optimization I am creating a fake (empty) Kint class that loads when my site is in production mode. This ensures that any Kint calls left in the code on accident do not trigger fatal errors, can never print anything out, and uses less resources compared to loading the real Kint class.

I know you can disable Kint with Kint::$enabled_mode = false; but lets not focus on that. Here is the code I use to fake the Kint class. All that is missing is capturing calls that use these non-standard operands:

/**
 * Fake class.
 */
class Kint {

    const STATIC_BLACKHOLE = '';

    public static $enabled_mode = false;

    public function blackhole( $a ) {
        return;
    }

    public function __call( $m, $a ) {
        return call_user_func_array( array( $this, $this->blackhole ), $a );
    }

    public static function __callStatic( $m, $a ) {
        return self::STATIC_BLACKHOLE;
    }

}
$kint = new Kint();

// Alias of Kint::dump().

/**
 * Fake function to catch d().
 *
 * @return void
 */
function d() {
    return;
}

// Kint::dump basic mode.

/**
 * Fake function to catch s().
 *
 * @return void
 */
function s() {
    return;
}

\define( 'KINT_DIR', '/classes/Kint' );
\define( 'KINT_WIN', DIRECTORY_SEPARATOR !== '/' );
\define( 'KINT_PHP70', ( \version_compare( PHP_VERSION, '7.0' ) >= 0 ) );
\define( 'KINT_PHP71', ( \version_compare( PHP_VERSION, '7.1' ) >= 0 ) );
\define( 'KINT_PHP72', ( \version_compare( PHP_VERSION, '7.2' ) >= 0 ) );
\define( 'KINT_PHP73', ( \version_compare( PHP_VERSION, '7.3' ) >= 0 ) );
\define( 'KINT_PHP74', ( \version_compare( PHP_VERSION, '7.4' ) >= 0 ) );
\define( 'KINT_PHP80', ( \version_compare( PHP_VERSION, '8.0' ) >= 0 ) );

CodePudding user response:

The "real-time modifiers" are all valid PHP unary operators:

Thus, it is perfectly allowable to prefix function calls with these operators, as long as the function returns a type of value that the operator would normally work on:

function foo() {
    return 0;
}

// All of these work just fine, and generate no errors:
-foo();
 foo();
!foo();
~foo();

As far as I can tell at a glance, what Kint does inside its functions is use debug_backtrace() to get the source file where the function was called from. It then opens that file, reads it, locates the calling line, and parses it to determine what (if any) "real-time modifiers" were used to prefix the function call. I.e., consider the source:

function d($var) {
    // dump $var
}

~d($GLOBALS);

The code inside the d() function normally can not tell that its return value is about to be modified by the ~ operator. (And it shouldn't! Doing so completely violates lexical and logical scoping.) However, Kint escapes this scoping, re-parses the source file, finds the ~, and then uses that as a means to modify the output that the code inside the function generates.

This technique is very confusing, constitutes a huge performance hit, a scoping violating, and presents a security concern... all to provide an inferior implementation of a basic feature that the language already has -- function parameters. I would never let this module anywhere near any of my production servers.

That said, my recommendation would be to forget trying to override Kint's runtime functionality with noops. Instead build your pipeline so that Kint can not be deployed:

  • Make sure you use --dev in your compose require.
  • Make sure you use --no-dev in your deploy scripts.
  • Add a check to your bootstrap or front controller to immediately abort if Kint is loaded.
  • Use the "forbidden functions" sniff in PHPCS before deployment, to detect any usages of Kint functions that were left over in the source.
  • Related