E_NOTICE ?== E_DEBUG, avoiding isset() and @ with more sophisticated error_handler
Which better ways exist to avoid an abundance of isset()
in the application logic, and retain the ability to see debug messages (E_NOTICE) when required?
Presumption first: E_NOTICE is not an error, it's a misnomer and should actually be E_DEBUG. However while this is true for unset variables (PHP is still a scripting language), some file system functions etc. throw them too. Hence it's desirable to develop with E_NOTICEs on.
Yet not all debug notices are useful, which is why it's a common (unfortunate) PHP idiom to introduce isset()
and @ throughout the application logic. There are certainly many valid use cases for isset/empty, yet overall it seems syntactic salt and can actually obstruct debugging.
That's why I currently use an error_reporting bookmarklet and a dumb on/off switch:
// javascript:(function(){document.cookie=(document.cookie.match(/error_reporting=1/)?'error_reporting=0':'error_reporting=1')})()
if (($_SERVER["REMOTE_ADDR"] == "127.0.0.1")
and $_COOKIE["error_reporting"])
{
error_reporting(E_ALL|E_STRICT);
}
else {/* less */}
However that still leaves me with the problem of having too many notices to search through once enabled. As workaround I could utilize the @ error suppression operator. Unlike isset() it does not completely kill debugging options, because a custom error handler could still receive suppressed E_NOTICEs. So it might help to separate expected debug notices from potential issues.
Yet that's likewise unsatisfactory. Hence the question. Does anyone use or know of a more sophisticated PHP error handler. I'm imagining something that:
- outputs unfiltered errors/warnings/notices (with CSS absolute positioning?)
- and AJAX-whatnot to allow client-side inspection and suppression
- but also saves away a filtering list of expected and "approved" notices or warnings.
Surely some framework must already have a user error handler like that.
- Basically I'm interested in warning / notice management.
- Full E_NOTICE supression is really not desired.
- E_NOTICES are wanted. Just less of them. Per default highlight the ones I might care about, not the expected.
- If I run without ?order= parameter, an expected NOTICE occours. Which due to be expected I do not need to informed about more than once.
- However when in full debugging mode, I do wish to see the presence of undefined variables through the presence (or more interesti开发者_C百科ngly absence) of said debug notices. -> That's what I think they are for. Avoiding isset brings language-implicit print statements.
- Also realize this is about use cases where ordinary PHP form handling semantics are suitable, not application areas where strictness is a must.
Oh my, someone please help rewrite this. Lengthy explanation fail.
It is possible to develop a large PHP application that never emits any E_NOTICEs. All you have to do is avoid all situations where a Notice can be emitted, the vast majority of which are un-initialized variables and non-existist array keys. Unfortunately, this clashes with your wish to avoid isset()
- and by extension array_key_exists()
- because they are designed for handling that exact problem.
At best, you can minimize their use by careful framework building. This generally means (for example) an input layer which is told what GET
variables to expect and what to default missing ones to. That way the page-specific code will always have values to look at. This is, in general, a worthwhile technique that can be applied to a variety of APIs. But I question whether this should be a high-priority design goal.
Unlike some other languages, PHP distinguishes between a variable not existing and containing a generally "empty" value (usually null
). It is probably a design artifact from an earlier version, but it nonetheless is still present, so you cannot really avoid it.
I am using isset()
only for $_GET
and $_SERVER
variables, where the data comes from outside the control of my application. And I am using it in some other situation when I don't have time to write a proper OOP solution to avoid it, but I'm sure that it can be avoided in most if not all places. For example it's better to use classes instead of associative arrays, this way you don't need to check the existence of an array key.
My advices are:
- Avoid using the
@
operator. - Use Xdebug. First, it prints easily readable and easily noticeable messages about every notice/warnig, and it prints a very useful stack trace on exceptions (you can configure it to print out every method parameter and every local variable (
xdebug.collect_params=4
andxdebug.show_local_vars=on
configuration parameters). Second, it can disable the@
operator withxdebug.scream=1
config value. You can use Xdebug for profiling and for code coverage analysis as well. It's a must have on your development machine. - For debugging, I am also using FirePHP, because it works with Firebug, and is able to print messages to the Firebug console, so it can be used for AJAX debugging as well.
- With a custom error handler, you can catch and filter any error and warning, and you can log them into a file or display them with FirePHP, or you can use for example jGrowl or Gritter to nicely display them on the web page.
I am using a modified version of the example in the PHP manual:
<?php
//error_reporting(0);
set_error_handler("errorHandler");
function errorHandler($errno, $errstr, $errfile, $errline)
{
echo "errorHandler()<br />\n";
// filter out getImageSize() function with non existent files (because I'am avoiding using file_exists(), which is a costly operation)
if ( mb_stripos($errstr, 'getimagesize') !== false )
return true;
// filter out filesize() function with non existent files
if ( mb_stripos($errstr, 'filesize') !== false )
return true;
// consoleWriter is my class which sends the messages with FirePHP
if (class_exists('consoleWriter'))
consoleWriter::debug(array('errno'=>$errno, 'errstr'=>$errstr, 'errfile'=>$errfile, 'errline'=>$errline, 'trace'=>debug_backtrace()), "errorHandler");
switch ($errno) {
case E_USER_ERROR:
$out .= "<b>FATAL_ERROR</b> <i>$errno</i> $errstr<br />\n";
$out .= "Fatal error on line $errline in file $errfile";
echo "</script>$out"; // if we were in a script tag, then the print is not visible without this
//writeErrorLog($out);
echo "<pre>";
var_export(debug_backtrace());
echo "</pre>";
exit(1);
break;
case E_USER_WARNING:
$out .= "<b>WARNING</b> <i>$errno</i> $errstr<br />\n";
$out .= "On line $errline in file $errfile<br />\n";
break;
case E_USER_NOTICE:
$out .= "<b>NOTICE</b> <i>$errno</i> $errstr<br />\n";
$out .= "On line $errline in file $errfile<br />\n";
break;
default:
$out .= "<b>Unknown</b> <i>$errno</i> $errstr<br />\n";
$out .= "On line $errline in file $errfile<br />\n";
break;
}
if (!class_exists('consoleWriter'))
echo $out;
//writeErrorLog($out);
//addJGrowlMessage($out);
// Don't execute PHP internal error handler
return true;
}
function testNotice($a)
{
echo $a;
}
testNotice();
One more advice is not to use the closing ?>
tag at the end of the php-only files, because it can cause headers already sent
errors on configurations where the output buffering is disabled by default.
Well, if you wait for PHP 7, you'll have access to the null coalesce ternary operator, which, in addition to having the coolest operator name in existence (I'm naming my next kid "Null Coalesce") will let you do this:
$var = $some_array[$some_value] ?? "default value";
Which replaces the ubiquitous (and ugly)
$var = isset( $some_array[$some_value] ) ? $some_array[$some_value] : "default_value";
try xdebug
- http://www.xdebug.org/docs/stack_trace
lots of isset
checking does not harm u,
in fact, it encourage declare variables before use it
I think following the best practice is not waste of time. That's true, a notice is not an error, but with correct variable declaration and validation your code could be more readable and secure. But it's not so complex to write a user-defined error handler with debug_backtrace sort the E_NOTICE(8) with a regexp.
I had a similar desire. So I started using custom error handlers.
http://php.net/manual/en/function.set-error-handler.php
You can then create your own filters/mechanisms for displaying/logging errors/notices.
Cheers!
PHP is definitely broken around this making code less readible. "null" means "undefined" - simple enough.
Here is what I do when I run into this problem making code unreadable:
/**
* Safely index a possibly incomplete array without a php "undefined index" warning.
* @param <type> $array
* @param <type> $index
* @return <type> null, or the value in the index (possibly null)
*/
function safeindex($array, $index) {
if (!is_array($array)) return null;
return (isset($array[$index])) ? $array[$index] : null;
}
// this might generate a warning
$configMenus = $config['menus'];
// WTF are you talking about!! 16 punctuation marks!!!
$configMenus = (isset($config['menus']) ? $config['menus'] : null;
// First-time awkward, but readible
$configMenus = safeindex($config, 'menus');
Cross posting this answer here. Does this help spam-checker?
The best way to avoid isset()
in my opinion is to define your variables before you use them. I dislike isset()
not so much because it's ugly but because it promotes a bad programming practice.
As for error handling itself, I put all that information out to the server logs. I also use php -l
on the command line to syntax check the programs before hand. I make pretty messages for the users by default.
You might look into one of the various frameworks to see if any of them would work for you. Most of them I've looked at have error handling routines to make things easier than what PHP offers out of the box.
EDIT:
@mario - my response to your comment was getting too long :-). I don't advocate defining types or going to some kind of strict format like Java or C. I just advocate declaring the variable in the context that it's used. ( $foo = null;
is not the same as leaving the variable blank).
I think this is more of a problem with global variables in a lot of cases, especially the super globals for getting GET and POST data. I really wish that PHP would drop super globals in favor of an class for getting input data. Something like this (super simple but hey you wanted something concrete: :) )
<?php
class PostData {
private $data;
public function __construct() {
$this->data = $_POST;
unset($_POST);
}
public function param($name, $value = null) {
if( $value !== null ) {
$this->data[$name] = $value;
}
if( isset( $this->data[$name] ) ) {
return $this->data[$name];
}
return null;
}
}
?>
Include the class then you can get and set POST data from the param()
method. It would also be a nice way to incorporate validation into the input data. And as a bonus, no checking everything for isset()
(it already is).
It's kind of an outdated answer now, but I originally used a flexible log dispatcher back then, https://github.com/grosser/errorhandler (Not exactly what I was looking for IIRC, but at least a little more sophisticated than alternating between complete and partial supression.)
Anyway, I'm meanwhile using a $_GET->int["input"]
wrapper for the most common cases. Which is just a trivial ArrayAccess wrapper, implicitly catches non-existant vars, that allows easier notice recovery. (Just a by-product. Primarily meant for immediate filtering though.)
And for another project I'm even using a preproccessor macro IFSET@($var)
, to allow enabling/disabling or log-redirection depending on build parameters.
精彩评论