How should I approach error handling in PHP? Specifically user input - php

I've been using PHP for quite a long time now, and I do enjoy it. However, it has come to my attention that my error handling is not up to scratch and for the new project I am working on, I want to adopt a proper error-handling method.
I'm wondering how to handle user input specifically, but also more generally any errors.
I have a case as below as an example:
function check_email($email){
if(empty($email)){
$error='You must enter an email address';
error_log($error);
header('Location: page.php?error='.$error);
exit();
}
if( *doesn't match regex* ){
$error='Not a valid email format';
error_log($error);
header('Location: page.php?error='.$error);
exit();
}
}
I want to both be able to revert to the user with the error message, and also log it in the error_log document.
The above method works fine, but it is super messy code, or at least it feels messy. Any ideas how I can clean this up? I want to be efficient with it because easy-to-write error catchers mean I will write more of them instead of being lazy
Edit: yes, I could wrap that in a function of it's own
function er($error){
error_log($error);
header ('Location:...);
}
Surely there is a more elegant, native solution though?

I have utilised comments below to come up with two individual functions for soft errors and returning user errors
For user errors:
function return_error($msg){
$_SESSION['error']=$msg;
header("location:javascript://history.go(-1)");
exit;
}
For soft errors / unusual behaviour (make_file() function just makes a file if not exist)
function soft_error($e){
$trace=debug_backtrace(-1);
if(isset($trace,$trace[0],$trace[0]['file'],$trace[0]['line'])){
$file=BASE_DIRECTORY.'logs/soft_error.log';
make_file($file);
$log=fopen($file,'a');
fwrite($log,'['.date('Y-m-d H:i:s').'] File: '.$trace[0]['file'].' - Line: '.$trace[0]['line'].' - Error: '.$e.PHP_EOL);
fclose($log);
}
}
For hard errors, there is error_log() or they will be logged automatically based on server settings, so that is fine

Related

Using the die() function excessively

Is it bad programming practice to use the die(); function excessively when validating data in PHP?
I am building a class where I have used that function more than a dozen times, so that if the validation test fails the entire script dies. Is that good practice, is there a better way I should be doing it? Does this practice vary between languages, are there any programming-wide conventions or best-practices to learn from?
Here's an example:
if($error = $this->checkDate($start)){
echo "START: ".$error."\r\n";
die();
}
if($error = $this->checkDate($end)){
echo "END: ".$error."\r\n";
die();
}
if($start>$end){
$error = "The start date must come <i>after</i> the end date!";
echo $error;
die();
}
if(($end-$start)<(3*24*3600) || ($end-$start)>(3*30*24*3600)){
$error = "The period must be at <i>least</i> 3 days long, and at <i>most</i> 3 months long. This date spans about ".round(($end-$start)/(3600*24*30))." months.";
echo $error;
die();
}
etc, etc, etc...
PS - How is "best-practice" not a tag already??
If you are building a bigger system you probably want more output even if it fails, like templating and such. The way you terminate here would make that hard to accomplish in a clean way.
If your application is intended to be really small, so you would not need to do anything after this validation, you are probably good, but then again you should always build code that is easily extendable in case you need to do so later.
If it were up to me, I would probably throw an exception instead and then handle the exception apropriatly at a higher nesting level.
But then again you will need to make the call yourself based on what your actual intent with this application is. Maybe it is appropriate, but probably not.
There are actually two critical flaws in your approach:
as a matter of fact, there should be not a single die() statement in the production code.
also, no class method should output a word.
Speaking of form validation - your idea of it makes user experience hard and outdated. A good web-site will always show on arror
a form itself
all the entered data
all the errors at once, to let user fix them all, not submit the form again and again until they get to the last error.

PHP exit() vs if - else statement

Is it a good or bad practice to authenticate and then just exit() the function or to wrap the whole result of the authentication in an if statement? Example
function foo($uid)
{
$allowed = $auth->checkIfAllowed($uid);
if ($allowed == false) exit();
//continue with senstive code here
}
}
OR
function foo($uid)
{
$allowed = $auth->checkIfAllowed($uid);
if ($allowed == true)
{
// do sensitive stuff
}
}
I would like to take this opportunity to talk about exit; (as others have stated both work, the second is more explicit then the first, and give you the opportunity to send a nice error message to the user). My main beef (I have several with exit;) is that people should stop using it in libraries, i.e. code that can/will be used in other projects... You know how irritating it is to debug those? Throw exceptions, trigger fatal errors, but give me something with a description.
/rant
Your examples are equivalent.
However, it's not usually useful to the end user to just exit the script abruptly. Instead, send your user a useful error message printed in HTML rather than the plain text you would get from a die() call, for example.
function foo($uid)
{
$allowed = $auth->checkIfAllowed($uid);
if ($allowed == false)
{
$errormsg = "You are not allowed to view this page";
}
else
{
//continue with senstive code here
}
}
Later, print the error in HTML, rather than just aborting the script:
<div class='error'><?php echo $errormsg; ?></error>
Either or. I don't think it'll make a difference. It relatively the exact same thing. In programming there are many ways to program things, never on right way in most instances.
They are absolutely the same. The indentation and coding style is the only difference. In both cases the sensitive code won't execute unless the authentication is done successfully.
It's usually better to be expressive in your code though, so I'd recommend the second method.

How to handle errors for debug purposes on PHP? (public strings?)

What I want to do is when an if condition doesn't go as it should, instead of echo'ing the my custom error message in else { }, storing the error message somewhere else and retrieving it from another page.
For example, this is my page with the if condition:
if ($something < 4){
echo 'yes it is less than four';
else { echo 'no it isn\'t less than four';}
I want to for example store these error messages in strings and give them numbers:
if ($something < 4){
$debug11 = 'yes it is less than four';
echo '11';
else { $debug10 = 'no it isn\'t less than four'; echo '10'; }
then let's assume there's a debug.php file with php class that can echo these messages but in order to do so it needs to know what $debug11 is, can it do that without including that php page? is that what public strings are for? or should I just define all of them in debug.php
the point of all this is that jquery will call this file.php and get a message like 11 or 10 which in this case is success or failure then I will be able to know why it failed with debug.php. numbers are easier since I may play with text messages a lot and easier to confirm with numbers than text in if conditions.
You want to store error-messages and read this messages by another script.
It means you need a storage.
As a storage, you can use files, or memcache, or APC, or queues.
Create logger, which will write messages to the storage, and then in debug.php you will read list of messages from the storage.
I recommend to use Memcache, set_error_handler and trigger_error.
I'm not sure what you mean by 'public strings', but if you are looking at accessing a variable between 2 pages, you would need to persist them into a session variable at least.
Also you might be better off using PHP assertions to check for error conditions within your code (I think that's what your trying to achieve here):
Assertions should be used as a debugging feature only. You may use them for sanity-checks that test for conditions that should always be TRUE and that indicate some programming errors if not or to check for the presence of certain features like extension functions or certain system limits and features.
Try logging to file: http://nl3.php.net/manual/en/errorfunc.configuration.php#ini.error-log. You can supply a custom log file in which you can find all your errors.
If you put in a error handler you should be able to create debug messages and store them in another file.
Write own logging mechanism and put log messages in a file.
As above I am not 100% sure what you are trying to do, however instead of using variables for your custom error messages it may be better to use Constants. The benefits of them are that the values can't be rewritten unlike your variable where you can change the value within your script.
Your code would look something like this:
define("ERROR1", "It wont Work!");
define("ERROR2", "It still wont Work!");
define("ERROR3", "It must be broken!");
if ($something < 4){
echo '11';
} else {
echo ERROR1; // Prints "It wont Work!"
}
You can store these Constants in your debug.php file and use them on any page you include the file on.
Hope this helps.

How to gracefully die?

So I've just read Why to never use 'or die'.
I am more confused then ever. I am validating a complicated form and I go though many nested levels of if statements and what not and I am passing a variable to the form which is called $status which can only be 'new' or 'edit'. Then when the user submits the form to be validated again the form passes along the $status value as a hidden field ($_POST). I want to make sure that the user cannot accidentally change this so I want to catch the error should something other than 'new' or 'edit' pass though. (Although I would like to completely eliminate the possibility of the user affecting this variable in an ideal world.)
So I figured that I would use DIE() in an if statement
<nested ifs>
Select ($status){
Case 'edit':
break;
Case 'new':
break;
default:
//using example from article
trigger_error("An error that should not occur has occurred", E_USER_ERROR);
break;
}
</nested ifs>
I don't really understand how this is cleaner than die()? Essentially I would like to call another function which displays the user some options of what they can do at this juncture to fix the error, but I want the code to absolutely stop running as I don't want an if statement further down to continue parsing anything and generating an error when it finds something other than 'new' or 'edit'.
I'm not sure how clear I am so please feel free to ask me to elaborate on any unclear points.
(or better yet, can a hidden user field get hacked? How to prevent? :P)
trigger_error() triggers an error which is handled by an error handler.
With trigger_error() you can gracefully handle errors, for example:
set_error_handler('ErrorHandler');
function ErrorHandler($errno, $errmsg, $filename, $linenum, $vars)
{
print '<pre style="line-height: 2em;">';
printf("==> Error in `%s' line %s: %s\n\n", $filename, $linenum, $errmsg);
debug_print_backtrace();
print '</pre>';
exit($errno);
}
This is a simple example, but our company website displays a friendly error page and sends me an email that I'm an idiot and messed up somewhere :-)
The advantage over die() or exit() should be clear :-)
exit() can still be used when you need to exit. For example when you generate a template, output that, and want code execution to stop. Or when you send a header('Location: ...'); header and want to make sure the execution stops ... Just don't use it for handling unexpected situations (i.e. errors).
trigger_error() also gives you a better degree of control. You can send E_USER_NOTICE when you want execution to stop but display a notice, and E_USER_ERROR when you want execution to stop.
In addition, you can write more complex error handler functions where some IP's see a error, and the rest do not ... Kindda useful for development.
Be a but careful with overly complicated error handlers though, what happens is an error occurs inside an error handler ... ? You may have seen Inception :)
The article you linked to explains why you shouldn't use or die, as in:
$result = mysql_query($query) or die('A MySQL query occurred: ' . mysql_error());
Obviously this is a bad idea, as it could output sensitive information about your MySQL structure to a malicious user.
However, I think there is nothing wrong (and I think the writer of the article would agree with me) to use die in the way you are using it, although presenting the user with a list of options would, indeed, be the preferred way to handle this.

PHP: Notice: Undefined index where the session variable is defined

I am making a registration system with an e-mail verifier. Your typical "use this code to verify" type of thing.
I want a session variable to be stored, so that when people complete their account registration on the registration page and somehow navigate back to the page on accident, it reminds them that they need to activate their account before use.
What makes this problem so hard to diagnose is that I have used many other session variables in similar ways, but this one is not working at all. Here's my approach:
/* This is placed after the mail script and account creation within the same if
statement. Things get executed after it, so I know it's placed correctly. */
$_SESSION['registrationComplete'] = TRUE;
// I've tried integer 1 and 'Yes' as alternatives.
Now to check for the variable, I placed this at the top of the page.
echo $_SESSION['registrationComplete']; // To see if it's setting. This gives the
// undefined index notice.
if (isset($_SESSION['registrationComplete'])) {
// Alternatively, I have nested another if that simply tests if it's TRUE.
echo $_SESSION['registrationComplete']; // When echo'd here, it displays nothing.
echo '<p>Congratulations, Foo! Go to *link to Bar*.</p>';
}
Now, I used to have the page redirect to a new page, but I took that out to test it. When the page reloads from submit, my message in the if statement above appears and then I get an Notice: Undefined index: registrationComplete blah blah from the echoing of the session var!
Then if I ever go back to the page, it ignores the if statement all together.
I have tested for typos and everything, clearing session variables in case old ones from testing were interfering, but I am having no luck. A lot of Googling just shows people suppressing these errors, but that sounds insane! Not only that, but I am not getting the same persistence of session variables elsewhere on my site. Can someone point out if I'm doing something blatantly wrong? Help! Thanks!
FYI, I read several related questions and I am also a beginner, so I may not know how to utilize certain advice without explanation.
As requested, more code, heavily annotated to keep it brief
var_dump($_SESSION);
// It's here to analyze that index message. I guess it's not important.
echo $_SESSION['registrationComplete'];
if (isset($_SESSION['registrationComplete'])) {
// The golden ticket! This is what I want to appear so badly.
echo 'Congratulations, Foo! Go to *link to Bar*.';
}
// Explanation: I don't want logged in users registering.
// The else statement basically executes the main chunk of code.
if (isset($_SESSION['user_id'])) {
echo 'You are logged in as someone already.';
}
else {
if (isset($_POST['submitRegister'])) {
// Code: Database connection and parsing variables from the form.
if (!empty($email) && !empty($email2) && $email == $email2 && !empty($displayName) && !empty($password) && !empty($password2) && $password == $password2) {
// Code: Query to retrieve data for comparison.
if (mysqli_num_rows($registrationData) == 0) {
// Code: Generates the salt and verification code.
// Code: Password hashing and sending data to verify database.
// E-mail the verification code.
$_SESSION['registrationComplete'] = 'yes';
}
else {
// Some error handling is here.
$registerError = 'The e-mail address you entered is already in use.';
}
}
// the elseif, elseif, and else are more error handling.
elseif ($email != $email2) { $registerError = 'Your e-mails did not match'; }
elseif ($password != $password2) { $registerError = 'Passwords didn\'t match.'; }
else { $registerError = 'Filled out completely?'; }
// If the registration was submitted, but had errors, this will print the form again.
if (!isset($_SESSION['registrationComplete'])) { require_once REF_DIR . REF_REGISTERFORM; }
// IMPORTANT! it turns out my code did not work, I forgot I had the same statement elsewhere.
else { echo 'Congratulations, Foo! Go to *link to Bar*.'; }
}
// Creates form.
else { require_once REF_DIR . REF_REGISTERFORM; }
}
This came down to the basics of debugging/troubleshooting.
Understand as much as you can about the technique/library/function/whatever that you're trying to use.
Inspect the salient bits and make sure that they are what you expect or what they should be. (There's a slight difference between those two, depending on the situation.)
If that doesn't bring you towards a solution, step back and make sure you're understanding the situation. This may mean simplifying things so that you're only dealing with the issue at hand, i.e. create a separate, simpler test case which exposes the same problem. Or, it may simply mean that you stop coding and work through the flow of your code to make sure it is really doing what you think it is doing.
A typical issue with sessions not working is forgetting to use session_start() (near or at the top) of any page which uses sessions.
One of my favorite snippets of PHP code, for debugging:
print '<pre>';
var_dump($some_variable);
print '</pre>';
I try to use print for debugging and echo for regular output. It makes it easier to spot debugging code, once it's goes beyond a few trivial bits of output.
Meanwhile, var_dump will print a bit more info about the variable, like it's type and size. It's important to wrap it in <pre></pre> so that it's easier to read the output.
Try
if (!empty($_SESSION['registrationComplete'])) {
If you get the warning after the message is printed, this cannot come from the variable echoing because according to your code it would be thrown before printing that message. Are you sure you don't use $_SESSION['registrationComplete'] beyond the if statement? Try to add exit or die() before the closing bracket of the if and see if the notice disappears.

Categories