Output buffer error handling - php

Let us assume that an easy snippet is used for templating and is using output control (ob)
public function capture($file, array $args = array())
{
extract($args, EXTR_SKIP);
ob_start();
require $file; //'foo.php'
return ob_get_clean();
}
And foo.php that has an error (handled by an error handler and shutdown handler)
<?php
echo "before";
echo $someVariable; //$someVariable is undefined here
echo "after";
Output
before <- would like to avoid
some message from the error handler
The question: is it possible to avoid any output from the file upon error?
Yes,
there are similar questions out there that I read/analysed but none of them gave me a clear answer whether this is or isn't.
Errors inside of output buffer
How to see php error in included file while output buffer? (#marc-b - probably it isn't)
I understand that you should not want to handle this kind of errors in your own code since it has to be clean & tested but still you might get some, e.g. typo, undefined variable, etc.

If you use the shutdown handler rather than the error handler it can clear the output because the error handler can only clear the output before it so anything outputted after it will still render.
<?php
function error_handler()
{
if(error_get_last()) {
ob_get_clean();
echo 'An error has occured.';
}
}
register_shutdown_function('error_handler');
function capture()
{
ob_start();
require 'foo.php';
return ob_get_clean();
}
echo capture();
// foo.php
<?php
echo 'before';
echo $variable;
echo 'after';
?>
This will only output 'An error has occured.'
however using set_error_handler it will output 'An error has occured.after' unless you add a DIE() or something similar to the error handler.

Related

undefined function when function is present but late

I have this file structure in my php project:
splitted.php (ERROR)
<?php
require_once "include1.php";
require_once "include2.php";
include1.php
<?php
a();
include2.php
<?php
function a(){ echo "success"; }
It will gives error Uncaught Error: Call to undefined function a().
But if we combined them with the same exact sequence, it doesnt gives error.
combined.php (SUCCESS)
<?php
a();
function a(){ echo "success"; }
Why it gives error when we splitted the code but run without issue when we combine it? How to fix this error without altering the structure and the sequence of the file?

Stop PHP exception from escaping html

I have a custom exception class, something like:
class AwesomeException extends \Exception {
public function __toString(){
return "<strong>This is important!</strong>";
}
I then throw new \AwesomeException("Blah blah");
& instead of showing:
This is important!
in bold,
The output includes:
<strong>This is Important!</strong>
When I view source, I see:
<strong>This is Important!</strong>
I tried overriding getMessage(), but that's final so it doesn't work.
I could have a global try/catch with custom output, something like:
try {
//do all of the things
}
catch (\Exception $e){
echo $e;
echo $e->getTraceAsString();
}
But I'd really like throw $e to print the original __toString with html, rather than escaping things. I'd like the AwesomeException class to enforce this somehow.
I can also echo html from inside my __toString(), but that breaks the expectation of __toString()
I'm in PHP 7.4
EDIT
I'm thinking this might be apache? My .htaccess routes to a deliver.php file, so I've written the very start of the file to be:
<?php
ini_set('display_errors', 1);
error_reporting(E_ALL);
class AwesomeException extends \Exception {
public function __toString(){
return "<strong>This is important!</strong>";
}
}
throw new \AwesomeException();
echo 'done throwing';
exit;
And I get the output source:
<br />
<b>Fatal error</b>: Uncaught <strong>This is important!</strong>
thrown in <b>/path/to/file/deliver.php</b> on line <b>11</b><br />
I ran phpinfo() & did ctrl+f for 'error', 'exception', 'htmlentities' & a couple others and ended up finding the html_errors directive.
So I:
ini_set('html_errors',0);
And now my html in exceptions is being printed unescaped. It no longer bolds my file name or "Fatal Error", though.

Is there any way to skip fatal error from include file in php?

If I include a file in to php. If there is any fatal error in that php then is there any way to skip that .
<?php
include "somefile.php";
echo "OK"; // Is there any way to print this OK If there is any fatal error on somefile.php
?>
I need to include this somefile.php file. It may return fatal error
for some host. I want to skip this file for those host.
Please Advice me.
With this, you can define your own continuation function that will take over in case of a fatal error. This uses register_shutdown_function() to intercept the fatal error.
Usage:
function my_continuation_func($filename, $arg2) {
// On fatal error during include, continue script execution from here.
// When this function ends, or if another fatal error occurs,
// the execution will stop.
}
include_try('my_continuation_func', array($filename, $arg2));
$data = include($filename);
$error = include_catch();
If a fatal error occurs (like a parse error), script execution will continue from my_continuation_func(). Otherwise, include_catch() returns true if there was an error during parsing.
Any output (like echo 'something';) from the include() is treated as an error. Unless you enabled output by passing true as the third argument to include_try().
This code automatically takes care of possible working directory changes in the shutdown function.
You can use this for any number of includes, but the second fatal error that occurs cannot be intercepted: the execution will stop.
Functions to be included:
function include_try($cont_func, $cont_param_arr, $output = false) {
// Setup shutdown function:
static $run = 0;
if($run++ === 0) register_shutdown_function('include_shutdown_handler');
// If output is not allowed, capture it:
if(!$output) ob_start();
// Reset error_get_last():
#user_error('error_get_last mark');
// Enable shutdown handler and store parameters:
$params = array($cont_func, $cont_param_arr, $output, getcwd())
$GLOBALS['_include_shutdown_handler'] = $params;
}
function include_catch() {
$error_get_last = error_get_last();
$output = $GLOBALS['_include_shutdown_handler'][2];
// Disable shutdown handler:
$GLOBALS['_include_shutdown_handler'] = NULL;
// Check unauthorized outputs or if an error occured:
return ($output ? false : ob_get_clean() !== '')
|| $error_get_last['message'] !== 'error_get_last mark';
}
function include_shutdown_handler() {
$func = $GLOBALS['_include_shutdown_handler'];
if($func !== NULL) {
// Cleanup:
include_catch();
// Fix potentially wrong working directory:
chdir($func[3]);
// Call continuation function:
call_user_func_array($func[0], $func[1]);
}
}
Fatal means fatal ...
There is no way to recover from a fatal error.
You can use register_shutdown_function.
<?php
function echoOk()
{
echo "OK";
}
register_shutdown_function(function ()
{
$error = error_get_last();
// to make sure that there is any fatal error
if (isset($error) &&
($error['type'] == E_ERROR
|| $error['type'] == E_PARSE
|| $error['type'] == E_COMPILE_ERROR
|| $error['type'] == E_CORE_ERROR))
{
echoOk();
}
});
include "somefile.php";
echoOk();
But you can do it only once. Any further fatal error will stop execution.
PHP won't tolerate with Fatal Errors. Best to check the included file and solve it.
Actually, you can try looking at register-shutdown-function, but it's not recommended to run away from your problems.
Yes, there is. It can be done through a simple if statement
You Have:
<?php
include "somefile.php";
echo "OK"; // Is there any way to print this OK If there is any fatal error on
?>
Try This:
<?php
if(include "somefile.php"){
// echo do something if success
}else{
echo "OK";
}
edit: I missed the word fatal. As stated, you can't recover from a fatal error. If it is just an exception the hastly writen response below will work.
Including another php module is the same as that code being inserted inline, so a simple try-catch statement should work:
<?php
try {
include "somefile.php";
} catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "\n";
}
echo "OK";
?>
Try to set a set_error_handler() function that doesn't die on fatal errors, but instead Apache crashed. In other words, PHP needs to die so that the system doesn't.
See this LINK
Fatal Error means there is something seriously wrong with the including code. As #Orangepill said there is no way to stop this fatal error message popping up. Please go through your coding and find the error.

How to prevent ob_start() output when an error occur?

Please, consider the following example:
template.php:
<?php
echo $vars['arr'];
echo " -------- ";
echo $vars['obj'];
?>
test.php:
<?php
$file = "template.php";
$vars = array( 'arr' => array(), 'obj' => new StdClass() );
var_dump( json_encode($vars) );
function loadFile($file, $vars)
{
try
{
if (is_array($vars) && !empty($vars)) {
extract($vars);
}
ob_start();
include $file;
return ob_get_clean();
}
catch (Exception $e)
{
return false;
}
}
loadFile($file, $vars);
?>
This code will output:
string(19) "{"arr":[],"obj":{}}"
PHP Catchable fatal error: Object of class stdClass could not be converted to string in template.php
The problem here is, in the template.php I am considering $vars to be an array() however 1 of the elements is an Object as you can see from the json output.
Adding a simple checking in the template to verify if the ekement is an array or not would solve the problem, however I would need to this to multiple elements, elements, so, not very good =) so, I trying to find a way to prevent the error in the moment of binding the template and $vars.
Thank you
simply turn error_reporting off while parsing:
$old_level = error_reporting(0); // no errors, preserve old error reporting level
ob_start();
include $file;
$content = ob_get_clean();
error_reporting($old_level); // reset error reporting level
return $content;
Note: This will only hide errors that aren't very critical.
In order to catch a Catchable Fatal Error, see this question: How can I catch a "catchable fatal error" on PHP type hinting?
You need to register an error handler using set_error_handler:
function handleError($errno, $errstr, $errfile, $errline) {
// handle error
// return true, so the normal php handler doesn't get called
return true;
}
set_error_handler('handleError');
If you want to integrate you handler cleanly into other code which also sets error_handlers, you might consider restoring the error handler afterwards using restore_error_handler
You might try checking to see if the values are an object first, and then using the PHP function to convert to an array.
Note, this section of your code:
if (is_array($vars) && !empty($vars)) {
extract($vars);
}
After that, if I'm reading your code correctly, you have a variable possibly called $vars that could be an object. So, you could do this:
if (is_object($vars)) {
$vars = get_object_vars($vars);
}
That should convert it to what you need. Of course, you may need to experiment a bit to make sure it fits your scenario. Thanks!
You can use __toString() method in your class to avoid the error. You can set the __toString() method with any value you need, maybe return an array.
http://www.php.net/manual/en/language.oop5.magic.php#object.tostring
Another option, is use array_walk :
function checkObj(&$vars){
if (is_object($vars)) {
$vars = get_object_vars($vars);
}
}
array_walk($vars, "checkObj");
Since you are using ob_start(), you can pass it a call-back that will check for any errors. It should even work with fatal errors. Inside your call-back use something like "error_get_last" to check for any error and just return false if it's not null, like so:
WARNING: I've not tested this code, but I use something similar myself.
<?php
$file = "template.php";
$vars = array( 'arr' => array(), 'obj' => new StdClass() );
var_dump( json_encode($vars) );
function loadFile($file, $vars)
{
try
{
if (is_array($vars) && !empty($vars)) {
extract($vars);
}
ob_start('errorHandler');
include $file;
return ob_get_clean();
}
catch (Exception $e)
{
return false;
}
}
function errorHandler($pBuffer)
{
$lastError = error_get_last();
// Uh-oh, an error has occurred.
if ($lastError !== NULL)
{
return FALSE;
}
// No problems.
return $pBuffer;
}
loadFile($file, $vars);
?>

phpUnit - mock php extended exception object

I'm testing some legacy code that extends the default php exception object. This code prints out a custom HTML error message.
I would like to mock this exception object in such a way that when the tested code generates an exception it will just echo the basic message instead of giving me the whole HTML message.
I cannot figure out a way to do this. It seems like you can test for explicit exceptions, but you can't change in a general way the behavior of an exception, and you also can't mock up an object that extends a default php functionality. ( can't think of another example of this beyond exceptions... but it would seem to be the case )
I guess the problem is, where would you attach the mocked object?? It seems like you can't interfere with 'throw new' and this is the place that the object method is called....
Or if you could somehow use the existing phpunit exception functionality to change the exception behavior the way you want, in a general way for all your code... but this seems like it would be hacky and bad....
EDIT: here is some code to make things clearer:
class FooTest extends PHPUnit_Framework_TestCase{
public function testBar(){
include '/path/to/file.php'; //generates exception
$this->assertTrue($baz);
}
}
...
//overridden exception class
class Foo_Exception extends ErrorException{
...
so, my question, is there a way to deal with this overriden class, without doing it on a case by case basis? what if I'm not testing the behavior of the exception, just the code that causes the exception?
I would first write a test that captures the exception generation behavior:
include '/path/to/file.php'; //generates exception
public function testCatchFooException() {
try {
$this->assertTrue($baz);
}
catch (Exception $expected) {
$this->assertEquals('This is expected html from exception', $expected->getMessage());
return;
}
$this->fail('An expected Exception has not been raised Foo_Excpetion.');
}
Now you can do several things with this coverage test. You can either fix up the exception, or fix the code that causes the exception.
Another thing you can do is wrap the entire file.php in a class:
class FooClass {
function runFoo() {
include '/path/to/file.php'; //generates exception
}
}
Then add tests while using extract method until you isolate exception.
[EDIT]
Here is some serious procedural legacy code:
<?php
require_once 'helper.php'; //helper file
function countNewMessages($user_id) {
}
function countNewOrders() {
}
function countNewReturns() {
}
function getDB($init = NULL) {
}
function getDisplay() {
}
getDisplay();
?>
And here is the wrapped class:
<?php
require_once ''; //helper file
class Displayer {
function countNewMessages($user_id) {
}
function countNewOrders() {
}
function countNewReturns() {
}
function getDB($init = NULL) {
}
function getDisplay() {
}
}
?>
And now I can test it:
function testGetDisplay() {
$display = new Displayer();
$this->assertEquals('html code', $display->getDisplay());
}
And test the individual functions in it. And if I can further sprout methods on it.
The above test would be considered a coverage test. There may be bugs in it, but that is what it does. So as I sprout methods the get more code coverage from tests by sprouting that I can make sure I don't break the output.
The extened PHP exception object "prints" a costum HTML error page? You mean its error message is an entire HTML page? That's not very clever...
What you can do about it is to replace the default exception handler (see this function), call getMessage on the exception and parse the HTML error page to extract the message. Then you can print the error message and kill the script. Like this (in PHP 5.3):
set_exception_handler(
function (Exception $e) {
die(parse_html_error_page($e->getMessage()));
}
);
OK, I misunderstood the question. If the script you're testing catches the error and then echoes an error page, then this has nothing to do with exceptions. You can use the ob_ family:
ob_start();
include $file;
$contents = ob_get_contents();
if (result_is_error($contents))
die(extract_error_from_result($contents));
else
echo $contents;
ob_end_clean();

Categories