Skip to content
Advertisement

How to get proper debug context from production PHP code? print_r vs var_export vs var_dump corner cases

Requirements

We have production PHP code running on a webserver. In some sitations we want to enrich debug output (going e.g. to error.log) with context information. The context can have arbitrary fields and structure.

Our goal is to find a general purpose debug producing function which does not generate warnings in any of the corner cases. It is acceptable if the output is only partial.

TL;DR

All three PHP standard functions cause warnings with certain contexts:

  • print_r & var_dump fail to handle mysqli objects with closed connections
  • var_export fails to handle recursion

Question

Is there a way to return the context of arbitrary objects as human readable string?

Test Cases

// Dealing with recursion:
$recObj = new stdClass();
$recObj->recursion = $recObj;

print_r   ($recObj);  // works
var_export($recObj);  // throws: "Warning: var_export does not handle circular references"
var_dump  ($recObj);  // works


// dealing with mysqli
$mysqli = mysqli_connect('mysql', 'root', 'myPass', 'mysql');
print_r   ($mysqli);  // works
var_export($mysqli);  // works as in "does not throw warnings" but all values are null
var_dump  ($mysqli);  // works

// Try again with closed connection
mysqli_close($mysqli);
print_r   ($mysqli);  // throws: "Warning: print_r(): Couldn't fetch mysqli"
var_export($mysqli);  // works (again all values are null)
var_dump  ($mysqli);  // throws: Warning: var_dump(): Couldn't fetch mysqli

As per the relevance of a closed mysqli connection: If you do your context printing in an error handler or registered shutdown function (which is a good location to do so), the mysqli object will already have auto-closed the connection once you reach that handler.

As you can see, none of the build-in output methods gives the desired result of just being able to return whatever context you throw at it.

Options considered

  1. It might be possible to suppress the warnings using the @notation. However, the registered error handlers and shutdown functions still get called with the error and would require custom logic to ignore that particular error. This might hide real errors and also gets quite annoying if dealing with 3rd party error tracking systems like sentry.io
  2. The serialize() function does not produce warnings in any of the cases but it lacks in human-readability.
  3. The json_encode() function can be much more readable than serialize but it does not return anything in the recursion test case….

Advertisement

Answer

The comment from @BlackXero is correct and works for me.

I did not find a build-in printing function which does not cause errors / warnings when containing a mysqli object with a closed connection (which I would actually classify as bug / unwanted behavior).

We ended up adding the Symfony Vardumper via

composer require symfony/var-dumper

and writing a little helper function for displaying proper and nice output both from cli scripts or the browser:

use SymfonyComponentVarDumperClonerVarCloner;
use SymfonyComponentVarDumperDumperCliDumper;
use SymfonyComponentVarDumperDumperHtmlDumper;

class Debug {
    /**
     * Method provides a function which can handle all our corner-cases for producing 
     * debug output.
     *
     * The corner cases are:
     * - objects with recursion
     * - mysqli references (also to closed connections in error handling)
     *
     * The returned result will be:
     *  - formatted for CLI if the script is run from cli
     *  - HTML formatted otherwise
     *      - The HTML formatted output is collapsed by default. Use CTRL-left click to 
     *        expand/collapse all children
     *  - You can force html|cli formatting using the optional third parameter
     *
     * Uses the Symfony VarDumper composer module.
     *
     * @see https://github.com/symfony/var-dumper
     * @see https://stackoverflow.com/questions/57520457/how-to-get-proper-debug-context-from-production-php-code-print-r-vs-var-export
     * @param mixed       $val    - variable to be dumped
     * @param bool        $return - if true, will return the result as string
     * @param string|null $format null|cli|html for forcing output format
     * @return bool|string
     */
    public static function varDump($val, $return = false, $format = null) {
        if (is_null($format)) {
            $format = php_sapi_name() == 'cli' ? 'cli' : 'html';
        }
        $cloner = new VarCloner();
        if ($format === 'cli') {
            $dumper = new CliDumper();
        } else {
            $dumper = new HtmlDumper();
        }

        $output = fopen('php://memory', 'r+b');
        $dumper->dump($cloner->cloneVar($val), $output);
        $res = stream_get_contents($output, -1, 0);

        if ($return) {
            return $res;
        } else {
            echo $res;
            return true;
        }
    }
}

That method

  • can handle all input I pass to it without errors or warnings
  • format nicely for both CLI and HTML
  • return the result as a string for forwarding it to external error tracking systems like sentry

So it ticks all boxes I asked for in the initial question.

Thanks @BlackXero for understanding the question correctly and pointing me in the right direction.

User contributions licensed under: CC BY-SA
9 People found this is helpful
Advertisement