0

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....
Christopher Lörken
  • 2,740
  • 18
  • 17
  • 1
    does mysqli object with a closed connection contain any useful info? – Your Common Sense Aug 16 '19 at 07:45
  • 1
    You can also use [Symfony Var Dumper](https://github.com/symfony/var-dumper) it supports recursive functions as well as it also returns. – BlackXero Aug 16 '19 at 07:48
  • @YourCommonSense No mysqli it does not contain useful info. But my question is intended to get a general purpose function which does not create warnings. I can live with partial output. I will make this point clearer in my post.. – Christopher Lörken Aug 16 '19 at 08:53
  • I don't really get whether this question is mysqli specific or not. if it is, then you can use mysqli_ping before accessing the nysqli object. If it is not, then please remove the mysqli tag as it makes the question very confusing – Your Common Sense Aug 16 '19 at 09:01
  • @YourCommonSense It is mysqli specific in so far as it is mysqli which breaks all the commands due to some inconsistent way of handling a simple print_r or var_dump when the connection is closed. The question is which debug function supports a corner-case like this. – Christopher Lörken Aug 16 '19 at 11:19
  • 1
    There are no "debug functions". Functions you are talking about are simply output or formatting related. And no, there are no functions that would suppress errors. – Your Common Sense Aug 16 '19 at 11:31

1 Answers1

0

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 Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;

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.

Christopher Lörken
  • 2,740
  • 18
  • 17