I have a script that will run on server daily to download data from a resource with no HTML output. By default PHP will output my script errors to php_error.log and to the output window in NetBeans.
I have created a user error handler:
// user defined error handling function
function userErrorHandler($errno, $errmsg, $filename, $linenum, $vars)
{
error_log($errmsg, 3, "logs/aem.log");
switch ($errno) {
case E_ERROR:
exit("Error grave $errmsg at $filename:$linenum");
break;
case E_USER_ERROR:
exit("Error grave $errmsg at $filename:$linenum");
break;
}
}
error_reporting(0);
set_error_handler('userErrorHandler');
I include this file in my main script and everything goes fine.
While I develop the app I would like to continue seeing the error messages on Output screen in Netbeans as well as keeping the error log files (as the default handler does). I have tried changing the value of error_reporting or adding additional error_log functions trying the following values:
error_reporting(E_ALL | E_STRICT);
error_reporting(-1);
error_log($errmsg, 0);`
But I never get the error on the output unless I remove include('mycustomhandler'); from the file.
How can I emulate the behavior of the standard error handler?
You should try to use output to stderr (I guess netbeans parses it). For example:
fprintf( STDERR, "Normal error message %s\n", "With nested info");
For logging errors you may use error_log() (maybe will handle output to netbeans too) or try to parse error_log from php.ini (ini_get()).
However I guess php uses internaly syslog(). So your error handler should look like:
// Logging
function userErrorHandler($errno, $errmsg, $filename, $linenum, $vars){
$logLevel = 0;
$label = '';
$exit = false;
switch( $errno){
case E_ERROR:
case E_USER_ERROR:
$label = 'Critical error';
$exit = true;
$logLevel = LOG_ERR;
break;
// ...
}
$message = "$label: on line ... file, error string...";
if( $logLevel){
syslog( $logLevel, $message);
}
if( !ini_get( 'display_error') && $exit){
die( 'Fatal error occured.');
}
if( $label){
echo $message; // You're responsible for how error is displayed
}
if( $exit){
die();
}
}
error_reporting( E_ALL);
Ask questions in comments if I didn't hit what you were asking
Related
I am getting the below error in one of the file in Production, where the function is defined twice. I tried to recreate the issue, getting in a different file.
Fatal error: Cannot redeclare foo() (previously declared in
/home/content/45/8989001/html/test/test.php:5) in
/home/content/45/8989001/html/test/test.php on line 10
To suppress this error, its advised to make an entry to php.ini file, but I don't have access to it as its shared hosting.
Alternatively, its suggested to make an entry to existing php file inside the <?php ?> tags. I did the below change.
error_reporting(0); // This is not working, still error is displayed
error_reporting(E_NONE); // This is not working, still error is displayed
ini_set('display_errors', 0); // This is not working, still error is displayed
My complete code,
<?php
function foo() {
return "foo";
}
function foo() {
return "foo";
}
// error_reporting(0); // This is not working, still error is displayed
// error_reporting(E_NONE); // This is not working, still error is displayed
ini_set ( 'display_errors', 0 ); // This is not working, still error is displayed
echo "hello";
?>
Problem: How to suppress this error in Production, instead log to some file. or at-least suppress the error?
Note: The error is fixed in prod, but how to suppress it to avoid user seeing error in some other file next time
Update1:
After the below change too, the error is the same.
<?php
ini_set ( 'display_errors', 0 );
function foo() {
return "foo";
}
function foo() {
return "foo";
}
// error_reporting(0); // This is not working, still error is displayed
// error_reporting(E_NONE); // This is not working, still error is displayed
// ini_set ( 'display_errors', 0 ); // This is not working, still error is displayed
echo "hello";
?>
Update2:
<?php
register_shutdown_function( "fatal_handler" );
function fatal_handler() {
echo "inside fatal_handler";
$errfile = "test";
$errstr = "shutdown this test";
$errno = E_CORE_ERROR;
$errline = 0;
$error = error_get_last();
echo $error;
if( $error !== NULL) {
$errno = $error["type"];
$errfile = $error["file"];
$errline = $error["line"];
$errstr = $error["message"];
$newline = "\r\n";
define ( 'senderName', 'Error' );
define ( 'senderEmail', 'admin#abc.com' );
$headers = "From: " . senderName . " <" . senderEmail . "\r\n";
$subject_admin = 'Error-Fix it';
$body_admin = "Dear Admin, $newline$newline An error occured $newline" . "error number : " . $errno . $newline . " error file : $errfile" . $newline . "error line :" . $errline . $newline . "error string : $errstr" . $newline;
$body_footer = " **** This is an an auto-generated mail. kindly do not reply to this mail. Thank You. ****" . $newline . $newline;
$body_admin = $body_admin . $newline . $newline . $newline . $body_footer;
$to_admin1 = 'mymail#gmail.com';
mail ( $to_admin1, $subject_admin, $body_admin, $headers );
//error_mail(format_error( $errno, $errstr, $errfile, $errline));
}
}
function foo() {
return "foo";
}
function foo() {
return "foo";
}
// error_reporting(0); // This is not working, still error is displayed
// error_reporting(E_NONE); // This is not working, still error is displayed
// ini_set ( 'display_errors', 0 ); // This is not working, still error is displayed
echo "hello";
?>
update 3
Still getting same error
<?php
register_shutdown_function ( "fatal_handler" );
function fatal_handler() {
echo 'YAY IT WORKED';
}
function foo() {
return "foo";
}
function foo() {
return "foo";
}
// error_reporting(0); // This is not working, still error is displayed
// error_reporting(E_NONE); // This is not working, still error is displayed
// ini_set ( 'display_errors', 0 ); // This is not working, still error is displayed
echo "hello";
?>
Update4
<?php
//error_reporting(0);
function fatalErrorHandler() {
echo 'YAY IT WORKED';
}
# Registering shutdown function
register_shutdown_function('fatalErrorHandler');
// let force a Fatal error -- function does not exist
functiontest();
echo "hello";
?>
<!--
output:
Fatal error: Call to undefined function functiontest() in ...test2.php on line 12
YAY IT WORKED -->
<?php
error_reporting(0);
function fatalErrorHandler() {
echo 'YAY IT WORKED';
}
# Registering shutdown function
register_shutdown_function('fatalErrorHandler');
// let force a Fatal error -- function does not exist
functiontest();
echo "hello";
?>
<!-- output:
YAY IT WORKED -->
Final update:
This resolved my intermediate question
register_shutdown_function is not getting called
If I were you I would call my host and ask them to make the changes to my phpini. They may have special things setup.
Now you should never just hide errors. You send yourself an email and fix right away. The error you are trying to surpress is fatal and the script will no longer run, so hiding it only results in a blank page, the user stkill cannot continue.
It is a fatal error and you cannot recover from it. The way to hide fatal error properly in my opinion is to create your own error handler function.
And you would use something like this to catch the fatal errors.
http://php.net/manual/en/function.register-shutdown-function.php
The function needs to be on every page in order to catth the error. I have a error.php that I require in my db class, which is on every page.
//This function gets called every time your script shutsdown
register_shutdown_function( "fatal_handler" );
function fatal_handler() {
$errfile = "unknown file";
$errstr = "shutdown";
$errno = E_CORE_ERROR;
$errline = 0;
$error = error_get_last();
if( $error !== NULL) {
$errno = $error["type"];
$errfile = $error["file"];
$errline = $error["line"];
$errstr = $error["message"];
//send error email
error_mail(format_error( $errno, $errstr, $errfile, $errline));
}
}
UPDATE
**OP said the function is not being called. **
The above should work.
Here is basically what I use in production, it should tottally work.
ON_SCREEN_ERRORS and SEND_ERROR_EMAIL are my own config constants for development.
//Set error handler function (See function below)
set_error_handler("StandardErrorHandler");
/**
* Will take an error string and if ON_SCREEN_SQL_ERRORS is set to on, it will display the error on the screen with the SQL in a readable format and
* if SEND_SQL_ERROR_EMAILS is set to on, it will dendthe error email with the SQL in a readable format.
*
* PHP will pass these parameters automatically on error.
*
* #param string $errno The error type code.
* #param string $errstr The error string.
* #param string $errfile The file that the error occured in.
* #param string $errline The line number that the error occured.
*/
function StandardErrorHandler($errno, $errstr, $errfile, $errline) {
$Err = $errstr.'<br />'.GetErrorType($errno).'on line '.$errline.' in '.$errfile.'<br />';
if (ON_SCREEN_ERRORS===TRUE)
{
err($Err);
}
if ($errno =='256' and SEND_ERROR_EMAILS === TRUE)
{
$Path = "http://". $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
// Custom error function
gfErrEmail($Err, $Path, 'SQL Error');
}
}
//Set the Fatal Error Handler function (See function below)
register_shutdown_function("FatalErrorHandler");
/**
* This function gets called on script shutdown, it will check if the last error is a fatal error. You cannot catch fatal errors,
* but using this function we will be notified about it and be able to fix it.
* If error is fatal, and if ON_SCREEN_FATAL_ERRORS is set to ON, this function will display the fatal error on the screen.
* If error is fatal, and if SEND_FATAL_ERROR_EMAILS is set to ON, this function will send error email.
*
*/
function FatalErrorHandler() {
$error = error_get_last();
if($error !== NULL) {
//check if error is of fatal, compliler or other non recoverable error types
if ($error["type"] =='1' || $error["type"] =='4' || $error["type"] =='16' || $error["type"] =='64' || $error["type"] =='4096')
{
$errno = GetErrorType($error["type"]);
$errfile = $error["file"];
$errline = $error["line"];
$errstr = $error["message"];
$Err = '<strong>'.$errno.'<br/></strong>'.$errstr.'<br />'.$errno.' on line '.$errline.' in '.$errfile.'<br />';
if (ON_SCREEN_ERRORS===TRUE)
{
err($Err);
}
if (SEND_ERROR_EMAILS === TRUE)
{
$Path = 'http://'. $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
//Custom function
gfErrEmail($Err, $Path, $errno);
}
}
}
}
/**
* This function receives the error code and returns the specified string.
* The return strings are what the error message will display.
*
* #return string The error title
*/
function GetErrorType($Type)
{
switch($Type)
{
case 1:
// 'E_ERROR';
return 'Fatal Error ';
case 2:
// 'E_WARNING';
return 'Warning ';
case 4:
// 'E_PARSE';
return 'Compile Time Parse Error ';
case 8:
// 'E_NOTICE';
return 'Notice ';
case 16:
// 'E_CORE_ERROR';
return 'Fatal Start up Error ';
case 32:
// 'E_CORE_WARNING';
return 'Start Up Warning ';
case 64:
//'E_COMPILE_ERROR';
return 'Fatal Compile Time Error ';
case 128:
// 'E_COMPILE_WARNING';
return 'Compile Time Warning ';
case 256 :
// 'E_USER_ERROR' - USED FOR SQL ERRORS - DO NOT USE THIS ERROR CODE to TRIGGER_ERROR()
return 'SQL Error ';
case 512:
// 'E_USER_WARNING';
return 'Warning - Thrown using trigger_error() ';
case 1024:
// 'E_USER_NOTICE';
return 'Notice - Thrown using trigger_error() ';
case 2048:
// 'E_STRICT';
return 'Strict Error (PHP suggest changes to your code which will ensure the best interoperability and forward compatibility of your code.) ';
case 4096:
// 'E_RECOVERABLE_ERROR';
return 'Catchable Fatal Error (This error can be caught, use a Try Catch) ';
case 8192:
// 'E_DEPRECATED';
return 'Warns you of depreciated code that will not work in future versions of PHP. ';
case 16384:
// 'E_USER_DEPRECATED';
return 'Programmer Triggered Error - Thrown using trigger_error() ';
}
return "Error Type Undefined ";
}
I have received many PHP Notice in log, but i want to know what page URL where happened Notice, how can i log this info?
[29-Nov-2012 13:58:29] PHP Notice: Array to string conversion in /usr/home/sdf/data/www/sdfsdf.com/core/test.php on line 156
I want to log any info, when get NOTICE, how to log it?
Try to correlate the timestamps in the php error log with your web server access log.
Or, you could tail -f the log and trigger random pages.
Or, set a custom error handler to log all sorts of data.
http://php.net/manual/en/function.set-error-handler.php
function myErrorHandler( $errno, $errstr, $errfile, $errline ){
if (!(error_reporting() & $errno)) {
// This error code is not included in error_reporting
return;
}
switch ($errno) {
case E_USER_ERROR:
echo "<b>My ERROR</b> [$errno] $errstr<br />\n";
echo " Fatal error on line $errline in file $errfile";
echo ", PHP " . PHP_VERSION . " (" . PHP_OS . ")<br />\n";
echo "Aborting...<br />\n";
exit(1);
break;
case E_USER_WARNING:
echo "<b>My WARNING</b> [$errno] $errstr<br />\n";
break;
case E_USER_NOTICE:
echo "<b>My NOTICE</b> [$errno] $errstr<br />\n";
break;
default:
echo "Unknown error type: [$errno] $errstr<br />\n";
break;
}
/* Don't execute PHP internal error handler */
return true;
}
set_error_handler( "myErrorHandler" );
What you could do is to compare you php notice times to your webservers access log in order to find out the URL which caused that notice.
The notice actually does not look like URL dependent.
It indicates, that you're trying to cast a "type" array to a string which happens when you do something like this:
$array = array( 'foo', 'bar' );
echo $array;
$string = 'test' . $array;
printf ( 'foo %s', $array );
To find this error in particular, you could do this to figure out what's wrong (including printing a backtrace using debug_backtrace):
if (is_string($var)) {
//Then it's OK - do whatever you were doing on line 156
} else {
//Something's wrong! Let's log it to some file!
ob_start();
var_dump($var);
print_r(debug_backtrace());
echo "\n----------------------------------------------\n";
$debugContent = ob_get_clean();
$logHandle = fopen("wrong_var_type.log", "a");
if ($logHandle !== false) {
fwrite($logHandle, $debugContent . "\n");
}
#fclose($logHandle);
}
Alternatively, use a logger:
https://stackoverflow.com/a/10800296/247893
You need to set up something to capture error/debug info as it happens, but not show it to the entire world. Not sure if you have session management built in to this application, but you may want to add some basic controls for this exercise.
Sometimes errors are harder to track down if you use functions that are included on multiple pages. The error may appear to happen on the parent page, but is actually triggered in the function, which is included on another page. The error line numbers can be misleading in this case.
If you have intermittent errors that you aren't able to immediately isolate, it may help to get some feedback on what's happening in your script(s). Here's a rough example of how to do some basic debugging in functions:
function get_func_argNames($funcName)
{
$f = new ReflectionFunction($funcName);
$result = array();
foreach ($f->getParameters() as $param)
{
$result[] = $param->name;
}
return $result;
}
function myCoolFunction($arg1, $arg2, $arg3)
{
$debug = false;
$php_function_args = implode(', ',get_func_argNames(__FUNCTION__));
$_debug_txt = "<b><span style='color:blue;'>function</span> <span style='color:darkblue;'>" .__FUNCTION__. "</span></b>($php_function_args)";
if ($debug)
{
EmailAppDev($_debug_txt);
}
// myCoolFunction
$x = $arg1 + $arg2 + $arg3;
return $x
}
Ideally you'll have a session account that can control who $debug is enabled for.
If you aren't using functions, you'll need to set up something similar in strategic areas of your scripts, to find out when and where things are going sour.
Without having your entire app to look at it's pretty hard to give specifics.
m maintaining a project, that has to be compatible through PHP 4.X to 5.2. The project as several errors wich are not very well handled.
What I want to do is redirect user to a "nice" error page, and log the error in the database. In order to keep track of the user, the file, and the error message. Here is what I've tried so far:
set_error_handler("myErrorHandler");
/**
*My function to handle errors
*/
function myErrorHandler( $errno , $errstr , $errfile="nofile" , $errline=0 , $errcontextArray=NULL ){
session_start();
if (!isset($errno)|!isset($errstr)) {
exit(0);
}
if (!mysql_ping()) {
require '../Connection/databaseinfo.php';
}
$id_user = $_SESSION['ident'];
$errstr = GetSQLValueString($errstr, "text");
$errfile = GetSQLValueString($errfile, "text");
$errline = GetSQLValueString($errline, "int");
$sql = "insert into error_history
(id_user, message, file, line)
values ($id_user, $errstr, $errfile, $errline)";
mysql_query($sql) or die (mysql_error());
// header("location:error.php"); ---> I can't (see comment below)
echo "<script type='text/javascript'> window.location.href='error.php';</script>";//Redirection dégueulasse //TODO: trouver mieux
return true;
}
Why not header()? Because the error can be triggered anywhere in a page, and thus, having the headers already sent.
Why not header() along with obstart(), ob_flush()? Because there are lot of pages already in production, and I can't modify them all.
My questions are:
Am I doing it wrong?
Is there a better way to handle the
redirection?
This is how to handle fatal errors in php 5.2:
<?php
register_shutdown_function('shutdownFunction');
function shutDownFunction() {
$error = error_get_last();
if($error['type'] >= 1){
mail('cc#cc.de', 'Error: XY',"Error msg:"."Server Error:".implode("\n", $error));
}
}
?>
I'd like to be able to catch die() and exit() messages. Is this possible? I'm hoping for something similar to set_error_handler and set_exception_handler. I've looked at register_shutdown_function() but it seems to contain no context for the offending die() and exit() calls.
I realize that die() and exit() are bad ways to handle errors. I am not looking to be told not to do this. :) I am creating a generic system and want to be able to gracefully log exit() and die() if for some reason someone (not me) decides this is a good idea to do.
Yes you can, but you need ob_start, ob_get_contents, ob_end_clean and register_shutdown_function
function onDie(){
$message = ob_get_contents(); // Capture 'Doh'
ob_end_clean(); // Cleans output buffer
callWhateverYouWant();
}
register_shutdown_function('onDie');
//...
ob_start(); // You need this to turn on output buffering before using die/exit
#$dumbVar = 1000/0 or die('Doh'); // "#" prevent warning/error from php
//...
ob_end_clean(); // Remember clean your buffer before you need to use echo/print
According to the PHP manual, shutdown functions should still be notified when die() or exit() is called.
Shutdown functions and object destructors will always be executed even if exit() is called.
It doesn't seem to be possible to get the status sent in exit($status). Unless you can use output buffering to capture it, but I'm not sure how you'd know when to call ob_start().
Maybe override_function() could be interesting, if APD is available
As best as I can tell this is not really possible. Some of the solutions posted here may work but they require a lot of additional work or many dependencies. There is no way to easily and reliable trap the die() and exit() messages.
Why do not use custom error handling instead? If not, you could always use LD_PRELOAD and C Code injection to catch it :) Or recompile php with your customizations :P
If you use the single point of entry method. (index.php) I can recommend this for your error handling:
Short version:
ob_start();
register_shutdown_function('shutdownHandler');
include('something');
define(CLEAN_EXIT, true);
function shutdownHandler() {
if(!defined("CLEAN_EXIT") || !CLEAN_EXIT) {
$msg = "Script stopped unexpectedly: ".ob_get_contents();
//Handle premature die()/exit() here
}
}
Additional steps and more detailed:
Roughly my way of doing it. I have even more going on than I show here (handling database transactions/rollback/sending e-mails/writing logs/displaying friendly error messages/user error reporting/etc), but this is the basic idea behind all of it).
Hope it helps someone.
<?php
//Some initialization
//starting output buffering. (fatalErrorHandler is optional, but I recommend using it)
ob_start('fatalErrorHandler');
//Execute code right at the end. Catch exit() and die() here. But also all other terminations inside PHPs control
register_shutdown_function('shutdownHandler');
//handling other errors: Also optional
set_error_handler('errorHandler');
try {
//Include of offensive code
include(...);
}
catch (Exception $ex) {
//Handling exception. Be careful to not raise exceptions here again. As you can end up in a cycle.
}
//Code reached this point, so it was a clean exit.
define(CLEAN_EXIT, true);
//Gets called when the script engine shuts down.
function shutdownHandler() {
$status = connection_status();
$statusText = "";
switch ($status) {
case 0:
if (!defined("CLEAN_EXIT") || !CLEAN_EXIT) {
$msg = "Script stopped unexpectedly: ".ob_get_contents();
//Handle premature die()/exit() here
}
else {
//Clean exit. Just return
return;
}
case 1: $statusText = "ABORTED (1)"; break;
case 2: $statusText = "TIMEOUT (2)"; break;
case 3: $statusText = "ABORTED & TIMEOUT (3)"; break;
default : $statusText = "UNKNOWN ($status)"; break;
}
//Handle other exit variants saved in $statusText here ob_get_contents() can have additional useful information here
}
// error handler function (This is optional in your case)
function errorHandler($errno, $errstr, $errfile, $errline) {
$msg = "[$errno] $errstr\nOn line $errline in file $errfile";
switch ($errno) {
case E_ERROR: $msg = "[E_ERROR] ".$msg; break;
case E_WARNING: $msg = "[E_WARNING] ".$msg; break;
case E_PARSE: $msg = "[E_PARSE] ".$msg; break;
case E_NOTICE: $msg = "[E_NOTICE] ".$msg; break;
case E_CORE_ERROR: $msg = "[E_CORE_ERROR] ".$msg; break;
case E_CORE_WARNING: $msg = "[E_CORE_WARNING] ".$msg; break;
case E_COMPILE_ERROR: $msg = "[E_COMPILE_ERROR] ".$msg; break;
case E_COMPILE_WARNING: $msg = "[E_COMPILE_WARNING] ".$msg; break;
case E_USER_ERROR: $msg = "[E_USER_ERROR] ".$msg; break;
case E_USER_WARNING: $msg = "[E_USER_WARNING] ".$msg; break;
case E_USER_NOTICE: $msg = "[E_USER_NOTICE] ".$msg; break;
case E_STRICT: $msg = "[E_STRICT] ".$msg; break;
case E_RECOVERABLE_ERROR: $msg = "[E_RECOVERABLE_ERROR] ".$msg; break;
case E_DEPRECATED: $msg = "[E_DEPRECIATED] ".$msg; break;
case E_USER_DEPRICIATED: $msg = "[E_USER_DEPRICIATED] ".$msg; break;
default: $msg = "[UNKNOWN] ".$msg; break;
}
//Handle Normal error/notice/warning here.
$handled = ...
if ($handled)
return true; //handled. Proceed execution
else
throw Exception($msg); //Be careful. this might quickly become cyclic. Be sure to have code that catches and handles exceptions. Else die() here after logging/reporting the error.
}
function fatalErrorHandler(&$buffer) {
$matches = null;
//Checking if the output contains a fatal error
if (preg_match('/<br \/>\s*<b>([^<>].*)error<\/b>:(.*)<br \/>$/', $buffer, $matches) ) {
$msg = preg_replace('/<.*?>/','',$matches[2]);
//Handle Fatal error here
return "There was an unexpected situation that resulted in an error. We have been informed and will look into it."
}
//No fatal exception. Return buffer and continue
return $buffer;
}
Catching exits is useful in automated tests. The way I do it is I throw a special runtime exception instead of calling exit directly.
<?php
class CatchableExit extends RuntimeException
{
}
class Quitter
{
public function run($i)
{
echo "Quitter called with \$i = $i \n";
throw new CatchableExit();
}
}
class Runner
{
public function run()
{
trigger_error('I am a harmless warning', E_USER_WARNING);
trigger_error('And I am a notice', E_USER_NOTICE);
for ($i = 0; $i < 10; $i++) {
$q = new Quitter();
try {
$q->run($i);
} catch (CatchableExit $e) {
}
}
}
}
function exception_handler(Throwable $exception)
{
if ($exception instanceof CatchableExit) {
exit();
}
}
set_exception_handler('exception_handler');
$runner = new Runner();
$runner->run();
yes: write a function and use that instead.
function kill($msg){
// Do your logging..
exit($msg);
}
Disclaimer; I'm fully aware of the pitfalls and "evils" of eval, including but not limited to: performance issues, security, portability etc.
The problem
Reading the PHP manual on eval...
eval() returns NULL unless return is
called in the evaluated code, in which
case the value passed to return is
returned. If there is a parse error in
the evaluated code, eval() returns
FALSE and execution of the following
code continues normally. It is not
possible to catch a parse error in
eval() using set_error_handler().
In short, no error capture except returning false which is very helpful, but I'm sur eI could do way better!
The reason
A part of the site's functionality I'm working on relies on executing expressions. I'd like not to pass through the path of sandbox or execution modules, so I've ended using eval. Before you shout "what if the client turned bad?!" know that the client is pretty much trusted; he wouldn't want to break his own site, and anyone getting access to this functionality pretty much owns the server, regardless of eval.
The client knows about expressions like in Excel, and it isn't a problem explaining the little differences, however, having some form of warning is pretty much standard functionality.
This is what I have so far:
define('CR',chr(13));
define('LF',chr(10));
function test($cond=''){
$cond=trim($cond);
if($cond=='')return 'Success (condition was empty).'; $result=false;
$cond='$result = '.str_replace(array(CR,LF),' ',$cond).';';
try {
$success=eval($cond);
if($success===false)return 'Error: could not run expression.';
return 'Success (condition return '.($result?'true':'false').').';
}catch(Exception $e){
return 'Error: exception '.get_class($e).', '.$e->getMessage().'.';
}
}
Notes
The function returns a message string in any event
The code expression should be a single-line piece of PHP, without PHP tags and without an ending semicolon
New lines are converted to spaces
A variable is added to contain the result (expression should return either true or false, and in order not to conflict with eval's return, a temp variable is used.)
So, what would you add to further aide the user? Is there any further parsing functions which might better pinpoint possible errors/issues?
Chris.
Since PHP 7 eval() will generate a ParseError exception for syntax errors:
try {
$result = eval($code);
} catch (ParseError $e) {
// Report error somehow
}
In PHP 5 eval() will generate a parse error, which is special-cased to not abort execution (as parse errors would usually do). However, it also cannot be caught through an error handler. A possibility is to catch the printed error message, assuming that display_errors=1:
ob_start();
$result = eval($code);
if ('' !== $error = ob_get_clean()) {
// Report error somehow
}
I've found a good alternative/answer to my question.
First of, let me start by saying that nikic's suggestion works when I set error_reporting(E_ALL); notices are shown in PHP output, and thanks to OB, they can be captured.
Next, I've found this very useful code:
/**
* Check the syntax of some PHP code.
* #param string $code PHP code to check.
* #return boolean|array If false, then check was successful, otherwise an array(message,line) of errors is returned.
*/
function php_syntax_error($code){
if(!defined("CR"))
define("CR","\r");
if(!defined("LF"))
define("LF","\n") ;
if(!defined("CRLF"))
define("CRLF","\r\n") ;
$braces=0;
$inString=0;
foreach (token_get_all('<?php ' . $code) as $token) {
if (is_array($token)) {
switch ($token[0]) {
case T_CURLY_OPEN:
case T_DOLLAR_OPEN_CURLY_BRACES:
case T_START_HEREDOC: ++$inString; break;
case T_END_HEREDOC: --$inString; break;
}
} else if ($inString & 1) {
switch ($token) {
case '`': case '\'':
case '"': --$inString; break;
}
} else {
switch ($token) {
case '`': case '\'':
case '"': ++$inString; break;
case '{': ++$braces; break;
case '}':
if ($inString) {
--$inString;
} else {
--$braces;
if ($braces < 0) break 2;
}
break;
}
}
}
$inString = #ini_set('log_errors', false);
$token = #ini_set('display_errors', true);
ob_start();
$code = substr($code, strlen('<?php '));
$braces || $code = "if(0){{$code}\n}";
if (eval($code) === false) {
if ($braces) {
$braces = PHP_INT_MAX;
} else {
false !== strpos($code,CR) && $code = strtr(str_replace(CRLF,LF,$code),CR,LF);
$braces = substr_count($code,LF);
}
$code = ob_get_clean();
$code = strip_tags($code);
if (preg_match("'syntax error, (.+) in .+ on line (\d+)$'s", $code, $code)) {
$code[2] = (int) $code[2];
$code = $code[2] <= $braces
? array($code[1], $code[2])
: array('unexpected $end' . substr($code[1], 14), $braces);
} else $code = array('syntax error', 0);
} else {
ob_end_clean();
$code = false;
}
#ini_set('display_errors', $token);
#ini_set('log_errors', $inString);
return $code;
}
Seems it easily does exactly what I need (yay)!
How to test for parse errors inside eval():
$result = #eval($evalcode . "; return true;");
If $result == false, $evalcode has a parse error and does not execute the 'return true' part. Obviously $evalcode must not return itself something, but with this trick you can test for parse errors in expressions effectively...
Good news: As of PHP 7, eval() now* throws a ParseError exception if the evaluated code is invalid:
try
{
eval("Oops :-o");
}
catch (ParseError $err)
{
echo "YAY! ERROR CAPTURED: $err";
}
* Well, for quite a while then... ;)
I think that best solution is
try {
eval(/* ... */);
} catch (Throwable $t) {
//...
}
It catches every error and exception, including Call to undefined function etc
You can also try something like this:
$filePath = '/tmp/tmp_eval'.mt_rand();
file_put_contents($filePath, $evalCode);
register_shutdown_function('unlink', $filePath);
require($filePath);
So any errors in $evalCode will be handled by errors handler.