6
\$\begingroup\$

I'm a pretty visual person and I like to see all the cool information about objects, arrays and different variables in PHP. In the beginning I would just echo '<pre>', var_dump($var), '</pre>'; liberally throughout my code. Then I was introduced to MVC and started grasping the concept of OOP and the Front Controller design pattern. Now, after having built my own little personal framework for academic reasons and understanding the need for reusable code a little part of my soul is eaten away every time I have to manually type this out.

This was my attempt at an OOP-style debugger that will spit out various information about the passed variable. The debug() method is the only public method and it takes two parameters, 1 being the variable you want to know more about and the other an optional boolean, default true, to toggle echoing a message to Firebug/Webkit Inspector console or straight to the browser screen.

My question is: Does this look horribly bad? Are there ways that my code can be improved? Is my logic illogical?

/**
 * Returns useful information about variables or objects for debugging purposes
 *
 * @package sprayfire
 * @subpackage core
 */
class SprayFireDebugger {

    /**
     * The original variable that needs to be debugged
     *
     * @var mixed
     */
    private $_debugVar;

    /**
     * True if the debug message should be sent to console.log and false if it
     * doesn't
     *
     * @var bool
     */
    private $_doConsoleLog;

    /**
     * The final message that should be echod
     *
     * @var mixed
     */
    private $_debugMessage;

    /**
     * SprayFireDebugger should never rely on __construct dependencies
     *
     * @return void
     */
    public function __construct() {

    }

    /**
     * Will emit useful information about the given variable.  If the second
     * parameter is set to false the message gets displayed on the screen
     * as compared to the debug console
     *
     * @param mixed $var
     * @param bool $toJsConsole
     * @return void
     */
    public function debug($var, $toJsConsole = true) {

        try {

            $this->_debugVar      = $var;
            $this->_doConsoleLog  = $toJsConsole;

            if (is_object($this->_debugVar)) {
                $this->_debugObject();
            } else {
                $this->_debugMessage = $this->_debugVar;
            }

            $this->_send();
            $this->_cleanUpDebug();

            return true;

        } catch (Exception $Exception) {

            // here to catch exceptions from _debugObject()
            error_log($Exception->__toString());
            $this->_cleanUpDebug();
            return false;

        }

    }

    /**
     * Will determine if an object is an exception or not and return
     * appropriate information for an object
     *
     * @return void
     */
    private function _debugObject() {

        $exc = 'Exception';
        $Reflection = new ReflectionClass($this->_debugVar);

        if ($Reflection->getName() === $exc) {
            return $this->_debugException();
        }

        if ($Reflection->getParentClass()) {
            if ($Reflection->getParentClass()->getName() === $exc) {
                return $this->_debugException();
            }
        }

        $debugInfo = $Reflection->export($this->_debugVar, true);

        $this->_debugMessage = $debugInfo;

        return;

    }

    /**
     * Takes an Exception object and turns it into an appropriate array to
     * be sent to the console or screen
     *
     * @return void
     */
    private function _debugException() {

        $debugInfo = array(
            'message' => $this->_debugVar->getMessage(),
            'code' => $this->_debugVar->getCode(),
            'file' => $this->_debugVar->getFile(),
            'line' => $this->_debugVar->getLine(),
            'trace' => $this->_debugVar->getTrace()
        );

        $this->_debugMessage = $debugInfo;

        return;

    }

    /**
     * Sends $this->_debugMessage to the screen or a JS console
     *
     * @return void
     */
    private function _send() {

        if ($this->_doConsoleLog) {
            $this->_sendToConsole();
        } else {
            $this->_sendToScreen();
        }

        if (empty($this->_debugMessage)) {
            throw new Exception('There was no error message to show.  This likely means something is royally messed up!', E_USER_ERROR);
        }

        echo $this->_debugMessage;

        return true;

    }

    /**
     * Echos out a script that logs a JSON ecoded $this->_debugMessage in the
     * browser console window.  If you are testing with IE please ensure you
     * pass false to $this->debug()
     *
     * @return void
     */
    private function _sendToConsole() {

        $consoleMsgMarkup = '<p style="position: absolute; bottom: 0; right: 1em;" class="sprayfire-debug-message">Debug info sent to console!</p>';

        $scriptMarkup = '<script type="text/javascript">';
        $scriptMarkup .= 'console.log(' . json_encode($this->_debugMessage) . ')';
        $scriptMarkup .= '</script>';

        $this->_debugMessage = $consoleMsgMarkup . $scriptMarkup;

        return;

    }

    /**
     * Echo the message to the main browser window
     *
     * @return void
     */
    private function _sendToScreen() {

        ob_start();
        var_dump($this->_debugMessage);
        $varDump = ob_get_clean();

        if (!$varDump) {
            throw new Exception('The output buffer was not initiated for dumping to the screen!', E_USER_ERROR);
        }

        $screenMarkup = '<pre>' . $varDump . '</pre>';

        $this->_debugMessage = $screenMarkup;

        return;

    }

    /**
     * Reset the class properties to be ready for the next debug call
     *
     * @return void
     */
    private function _cleanUpDebug() {

        $this->_debugVar      = NULL;
        $this->_doConsoleLog  = NULL;
        $this->_debugMessage  = NULL;

        return;

    }

}

// End SprayFireDebugger

The code appears to work right, but I'm also a solo-developer with no kind of peer review. I guess I don't know what I don't know. Thanks for taking a look, I appreciate any feedback.

\$\endgroup\$
5
\$\begingroup\$

Does this look horribly bad?

It actually looks great:

  • Perfectly consistent
  • Code is self documented, simple and broken into appropriate blocks (your functions make sense)
  • phpDoc everywhere!

I would go as far as saying it's the second most well written piece of php code I've seen on my fairly limited time on Code Review. And it's actually useful. I'm using a similar class on a 2*10^6 sloc project and it's extremely helpful. You should explore integrating xdebug into your class, for some bonus points.

Are there ways that my code can be improved?

There always are. But your code is at a point that you should probably spent time on other stuff, than trying to go the extra mile.

A very minor suggestion: I absolutely hate prefixing private / protected methods and members with an underscore. This:

private $_debugVar;

doesn't work for me. It's a well known convention that's used all around, but what happens if at a later point you decide to make one of those public? Proponents of the underscore prefix always say it's a readability thing. Well it might be, but it doesn't matter:

  • All half decent IDEs have code explorers / outlines with different coloring schemes for different visibility scopes. Most of those views are sortable on visibility too.
  • All half decent IDEs will only expose public methods / members via code completion when using the class.

Is my logic illogical?

Nope.

\$\endgroup\$
0
\$\begingroup\$

You've got a great idea with the console logging. I'll have to set that to my debugging stuff at some point. A few suggestions as far as the verbosity and approach:

Currently, you're going to have to do this:

$debugger = new SprayFireDebugger(); // Instantiate the object
$debugger->debug($test_var_3); // Method call on the object with a variable
$debugger->debug($test_var_8); // Duplicate that work for another variable.

Every single time you want to debug. You're going to be introducing a variable to the global scope or you're going to have at least two lines per debug + 1 for every new variable you want to debug. Another problem is that without any "environmental" configuration, you're going to run into this problem: If you leave a debug variable in your code, it will debug on the live servers. As a result of all this, you have a very verbose process that you're going to have to be adding and removing all the time and commenting out or deleting whenever you make the code live.

Here's the simple function that I use:

function debug($args) {
    if (DEBUG) { // Don't make anything public on live servers.
        $vals = func_get_args(); // Every new argument will be a new debug.
        foreach($vals as $val){ // Loop over each argument
                // Do whatever you want to output the debugging.
            echo "<pre class='debug' style='font-size:12pt;background-color:white;color:black;position:relative;z-index:10'>";
            var_dump($val);
            echo "</pre>";
        }
    }
}

As such, here are my suggestions:

  • Use a function wrapper to decrease verbosity (I use debug()).
  • Account for multiple variables as arguments, e.g. (debug($something, $somethingelse, $another)).

  • Prevent debugging on live servers (e.g. using a DEBUG constant setting).

In your case, you've got a great idea with the unobtrusive console logging. As such, your function signature would have to be modified from my suggestion to add:

  1. two split function wrappers, e.g. debug($arg, $arg5, $arg8, $arg7) and console_debug($arg, $arg5, $arg8)
  2. or an additional, always there argument debug(true, $arg, $arg5, $arg8)
  3. or to always use an array debug(array($arg, $arg5, $arg8), true)

I recommend the first option for absolute minimum complexity when you're debugging.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.