bernhard Posted February 7, 2019 Share Posted February 7, 2019 Hey everybody, hope you are doing fine! Today I had an error that was quite hard to debug... It was an AJAX request sent via RockGrid that should trash a page. It was working fine as superuser, but not for my client. So I thought it was related to access control. Then I did some hard var_dump() and die() in the AJAX and found the problem: Line 29/30 refer to a saveReady hook that obviously gets called when trashing the page. Somehow (don't know why, actually) the page has output formatting turned ON and the date conversion fails. No problem so far, a getUnformatted() solves this issue and I get a nice response: But I wonder how I could make the "response" more informative when something goes wrong. At the moment it just fails with an empty ajax response. Can I somehow "catch" the notice error? There is no Exception thrown. It's just an echo'ed warning. Thank you very much for your help! PS: The trash button code is simple, and you can see it here: https://github.com/BernhardBaumrock/FieldtypeRockGrid/blob/master/plugins/buttons/trash.php And it is invoked here: https://github.com/BernhardBaumrock/FieldtypeRockGrid/blob/master/InputfieldRockGrid.module.php#L223-L241 Link to comment Share on other sites More sharing options...
Autofahrn Posted February 7, 2019 Share Posted February 7, 2019 Maybe output buffering can help in this case? Just call ob_start before executing the code and finally use ob_get_contents to check for some unexpected output. Which PHP version are you running? 1 1 Link to comment Share on other sites More sharing options...
Autofahrn Posted February 7, 2019 Share Posted February 7, 2019 Here we go: // Out of Band PHP Error grabber $Errs = ''; global $Errs; function regular_error_handler($errno, $errstr, $errfile, $errline) { global $Errs; $Errs .= "<li><b>Custom error:</b> [$errno] $errstr<br>Error on line $errline in $errfile</li>"; } set_error_handler('ProcessWire\regular_error_handler'); // assuming to operate in ProcessWire namespace // Execute some PHP $DateOutput = date('H:i', 'SomeBogusData'); $Zero = 0; $Result = 1 / $Zero; // Finally check for any out of band echo messages from PHP if($Errs != '') $content .= "Execution failed with<ul>{$Errs}</ul>"; 2 Link to comment Share on other sites More sharing options...
bernhard Posted February 7, 2019 Author Share Posted February 7, 2019 Thx, the problem is that this echo'ed output only appears when I do a var_dump($data); die(); above this block: https://github.com/BernhardBaumrock/FieldtypeRockGrid/blob/master/InputfieldRockGrid.module.php#L243-L252 Otherwise the response fails because it's not a valid JSON (because of the prepended error messages). I guess this is only in debug mode, but I'd really be interested how to catch (thx for the hint regarding the typo ? ) such errors. Link to comment Share on other sites More sharing options...
Autofahrn Posted February 7, 2019 Share Posted February 7, 2019 There is no difference for regular HTML or AJAX reply. If the error happens, PHP sends that out of band string back to the client. If you are waiting for a JSON, then this message prepends the JSON reply. Catching it with that error handler allows you to put the message into you JSON object before transmission. 2 Link to comment Share on other sites More sharing options...
bernhard Posted February 7, 2019 Author Share Posted February 7, 2019 Hm... one of us does not understand ? I'm sending the request via ajax The server executes the request and it fails I get an AJAX error that breaks my batch-operation (that fires one request after each other) It should be like this: Send request Request fails, so return a valid json (eg "error": "error while trying to trash page xy") execute all following batches So what I want is to catch the error before the response is sent back to the client. Link to comment Share on other sites More sharing options...
adrian Posted February 7, 2019 Share Posted February 7, 2019 Typically Tracy reports on AJAX errors - if not in the AJAX debug bar, usually in the Tracy error logs. Sometimes they do seem to get through but usually they show up in the browser console's network > response tab 1 Link to comment Share on other sites More sharing options...
Autofahrn Posted February 7, 2019 Share Posted February 7, 2019 1 hour ago, bernhard said: So what I want is to catch the error before the response is sent back to the client. Sure, I thought about something like this: $payload = $this->input->post('data'); $func = $actions[$action][0]; $options = $actions[$action][1]; $data = []; set_error_handler(...); // Setup handler // Execute whatever requested if(count($options)) $response = $func->__invoke($payload, $options); else $response = $func->__invoke($payload); if($Errs != '') // Errors happened? $data[] = (object)[ 'error' => $Errs ]; else // log data and response $data[] = (object)[ 'action' => $action, 'payload' => $payload, 'response' => $response, ]; You may even want to add a try..catch around the function invocation. I'll check later but don't see a reason why that should not work. 2 Link to comment Share on other sites More sharing options...
Autofahrn Posted February 7, 2019 Share Posted February 7, 2019 (edited) Works for me (tried to keep it as compact as possible): <?php namespace ProcessWire; /* Quick test to capture out of band ECHO messages from PHP */ class WontNameItFoo { public function Quote() { return('The best is yet to come...'); } public function Einstein() { return('E = mc²'); } } $PhpErrors = ''; // This will be filled from our error handler global $PhpErrors; function php_error_handler($errno, $errstr, $errfile, $errline) { global $PhpErrors; // Add an LI element to our error string $PhpErrors .= "<li><b>RUNTIME ERROR #{$errno}:</b> $errstr in ".basename($errfile)."({$errline}):<br>"; $PhpErrors .= '<pre>'.file($errfile)[$errline-1].'</pre></li>'; } if($this->config->ajax) // Handling an AJAX request? { header('Content-type: application/json; charset=utf-8'); // Define response header set_error_handler('ProcessWire\php_error_handler'); // Register Error Handler try { // Perform some PHP processing without any kind of sanitation for testing only // In a productive environment proper parameter verification should be implented! $ajaxDate = 'Date: ' . date('l jS \of F Y h:i:s A', $input->post('timestamp')); $ajaxDiv = $input->post('dividend') . ' / ' . $input->post('divisor') . ' = ' . (string)($input->post('dividend') / $input->post('divisor')); $wnif = new WontNameItFoo(); $func = $input->post('function'); $ajaxFunc = $wnif->$func(); // This is brute force catching of fatal PHP errors... } catch(\Throwable $t) { php_error_handler($t->getCode(), $t->getMessage(), $t->getFile(), $t->getLine()); // $PhpErrors .= '<li><pre>'.$t->getTraceAsString().'</pre></li>'; // in case you like to see the stack trace } if($PhpErrors != '') // Errors captured from our error handler? $TheData = [ 'errors' => $PhpErrors ]; // Only return errors else $TheData = [ // No error, return proper result 'ajax-result' => $ajaxResult, 'ajax-division' => $ajaxDiv, 'ajax-date' => $ajaxDate, 'ajax-func' => $ajaxFunc, ]; if($input->get->callback) // JSONP? (not used here, would require GET request, useful for cross-domain query) echo $input->get->callback."(".json_encode($TheData).")"; else echo json_encode( $TheData ); // Regular AJAX response die(); } // Setup regular HTML content including script // You may replace "$content =" with echo, depending on your output scenario $content = "<div><h1>{$page->title}</h1> <form id='the-form' action='#'> Timestamp: <input id='timestamp' name='timestamp' type='text' value='SomeBogusData'><button type='button' onclick='setNow(\"#timestamp\");'>Now</button><br/> Divide <input name='dividend' type='text' value='1234'> by <input name='divisor' type='text' value='0'><br/> Function <select name='function'><option value='Einstein'>The Einstein</option><option value='Quote'>Personal Quote</option><option value='FourtyTwo'>The answer to life, the universe and everything</option></select></br> <button type='submit'>Execute</button> </form> <div id='ajax-status'></div> <div id='ajax-division'></div> <div id='ajax-date'></div> <div id='ajax-func'></div> </div> <script> function setNow(target) { $(target).val(Math.round((new Date()).getTime() / 1000)); } $(document).ready(function() { $('#ajax-status').html('Ready...'); $('#the-form').submit(function(event) { event.preventDefault(); // No default action $.ajax({ url: '.', type: 'post', data: $( '#the-form' ).serialize() }) .done(function( jsonData ) { if(jsonData['errors']) { $( '#ajax-status' ).html( 'ERROR: <ul>' + jsonData['errors'] + '</ul>' ); } else { $( '#ajax-status' ).html( 'SUCCESS!' ); // console.log(jsonData); for (var key in jsonData) { $('#'+key).html(jsonData[key]); } // Distribute result into DIVs } }) .fail(function() { $( '#ajax-status' ).append( 'FAILED' ); }); }); }); </script> "; See it live: https://www.team-tofahrn.de/ajax-test/ Edited February 8, 2019 by Autofahrn Updated with exception handler, cleanup 3 Link to comment Share on other sites More sharing options...
adrian Posted February 8, 2019 Share Posted February 8, 2019 Nice one @Autofahrn Here are the results of your script without the set_error_handler() but with Tracy handling everything instead: 2 Link to comment Share on other sites More sharing options...
Autofahrn Posted February 8, 2019 Share Posted February 8, 2019 Just pimped the code with a catch-all handler. Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now