So basically I want to be able to somehow do a few if statements to check what exception is being thrown in order for me to echo out some appropriate error messages, e.g.
IF exception is a duplicate record exception in MySQL
THEN echo "User Already exists";
So below I have caught the exception and it prints the default exception error message however this doesn't look good for the user so I want to check what the error is, then print out my own appropriate error message that users would be able to recognise... such as 'User Already Exists'.
try{
$query = "INSERT INTO user VALUES ('', 0, $safe_email, '$hashed_password')";
$result = $db->query($query);
echo '<script type="text/javascript"> alert(\'You have successfully registered.\n A confirmation email has been sent to you.\'); window.location = \'index.php\'; </script>';
}
catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "\n";
}
You should check if the user exists in a "non exceptional" way first, since it's impossible, or at least pretty tricky, to get relevant and consistent information out of the MySQL exception.
For example, if you suddenly decide to add an id field to your table and set it as a primary key, your algorithm would break. If you manage to parse the MySQL error by identifying the error number or something, you're also very tightly coupled to your database and it might be tricky to switch to MariaDB or Postgres or whatever...
Leave the exceptions for the truly exceptional cases, such as
User A and B tries to sign up as "Adam" at roughly the same time
Your server checks UserModel->usernameExists("Adam") of something like that, it returns false in both cases.
Your registration controller continues the registration process for both users, user A gets registered but user B receives an exception, which is actually exceptional and due to this stupid race condition).
If user A registers two weeks before user B tries to register, however, UserModel->usernameExists("Adam") would just return true for user B, and you could show a nice error - or just show that the username is taken, in the registration form.
Related
As you know, a transaction is composed of multiple prepared statements that is executed one by one, if one of that statements fails, it will throw a PDOException, you can catch it and get the error message, but this error message is for the "developer", I need to show an specific error to the final user like: "The transaction failed because ....", that '....' is the reason I need to show to the client.
I have an idea to solve this and it is using an array named 'query_trace', that array will put a key for the executed query and a result of that execution like:
// prepare statement, bindParams....
$query_trace['query1_insert_user'] = "OK"
// prepare statement, bindParams....
$query_trace['query2_insert_user_more_information'] = "OK"
// prepare statement, bindParams....
$query_trace['query3_create_user_account'] = "FAILED"
(That FAILED string is stablished in the catch:
} catch (PDOException $e)
{ $query_trace['query3_create_user_account'] = "FAILED" }
return $query_trace; ...
Then in the controller when I get the query_trace response, I will check:
if ($query_trace['query1_insert_user'] != "OK")
echo "The data for the user is not valid";
else
if ($query_trace['query2_insert_user_more_information'] != "OK")
echo "The additional information of the user is not valid";
else
if ($query_trace['query3_create_user_account'] != "OK")
echo "The username is not valid or actually exists";
else
echo "The transaction was great!";
The question is that this is a good way and/or PDO offers some functions/methods to "auto-trace" this?
Thanks.
Judging by error messages, you don't need neither a transaction nor multiple statements.
Just validate the user input before doing any inserts, then insert the user data into a single user talbe, without any transactions.
The only possible case that you would want to report to a user is a duplicate username. In this particular case you may want to catch the error, check whether it's a duplicate key, and if so - report that report to the user. Otherwise just re-throw the exception and let a site-wider error-handler to tell a user that something went wrong.
This makes little sense since a transaction turns a bunch of request to one "atomic" request. So trying to get information about the sub atomic elements is quiet odd.
So I'm not aware of any "easy way" to do so.
You may also do a SELECT before every statement (but it might get tricky if the SELECT is the statement that failed). It would probably work for PROCEDURE too.
SELECT "The user creation failed";
INSERT INTO user (...) VALUES (?,...,?);
SELECT "The user subinfos save failed";
INSERT INTO user_subinfos...;
Note that user will only know which statement failed, not why it failed.
First, I’m not a programmer by nature, so I’m having trouble understand what’s happening in the below script (put together from a number of resources for a user registration form):
if (mysqli_query($dbconnect, $sql))
{
header("Location: registered.php");
}
else
{
$_SESSION['error_reg']['email'] = "This email address is already registered.";
}
In this snippet, $dbconnect contains the MySQL connection script (using mysql) and $sql contains the sql table insertion code. Finally, the session info injects an error message into another script that’s under the form.
My question revolves around how the script knows if the email address exists in the table (it works, it really does) without having to select the row from the table. I’ve been seriously scratching my head about how it is work when I haven’t explicitly coded it to do so.
I hope that this isn’t vague enough… There’s really not much more to the script aside from some form field validation and capturing the form variables for insertion into the table (in $sql).
Thanks!
My question revolves around how the script knows if the email address exists in the table
It actually doesn't know in this case. It assumes. This is actually a pretty poor example of that described functionality. This code is assuming that any failure of the database query means that the email address is already registered.
If there is any failure in executing the query (a syntax error, a database engine failure, anything), then mysqli_query($dbconnect, $sql) is going to return false. This code interprets that false value as the specific error message it displays.
It can't possibly work. mysqli_query() has two possible return values: a statement handle/object if the query succeeded, or boolean false if the query outright failed.
A query which returns NO rows (e.g. select .. where 1=0) is NOT a failure. It's a perfectly valid query, which simply happens to have an empty result set.
A proper code sequence would be:
$result = mysqli_query(...) or die(mysqli_error($dbconnct));
if (mysqli_num_rows($result) == 0) {
return "not registered";
} else {
return "already exists";
}
I am working with Databases, to update, delete, or insert. At the moment I am only doing some errors on some. Should I use an exception on EVERY database query I run, or just a general error?
Let me visual show you.
Exception
$query = $database->prepare("INSERT INTO test(test) VALUES(1)");
$result = $query->execute();
try{
if(!result): throw new exception(""); else: //success endif;
}
Or
$query = $database->prepare("INSERT INTO test(test) VALUES(1)");
$result = $query->execute();
if(!$result): return 0; else: //success endif;
Then (for my case an MVC, check if that function is == 0 then redirect to an error page
Or no error notifications:
$query = $database->prepare("INSERT INTO test(test) VALUES(1)");
$result = $query->execute();
Which one of these is recommended, or not recommended if any?
Whenever something can go wrong, and that would affect something you find important, you should act on the error.
This doesn't always mean producing an error page. You would only do that if the error has an effect on the visitor of your site. For instance, when a registration is submitted, but cannot be entered into the database:
"Sorry, we have a technical problem, your registration can, at the
moment, not be accepted. An email with your details has been sent to
to us. Will we contact you soon.".
There are other errors that do not need feedback like this, but which you would like to know about. For instance when you count how often a page is visited. Make a log, send a email, or something similar, but do not bother your visitor with error messages.
The second case: if(!$result): return false;
is important to know in order to update your db, instead of interrupting the code.
Errors are useful at the development stage.
This is a case that sth else will happen.
I'm trying to interact with an Azure Queue using a REST proxy courtesy of the Windows Azure SDK for PHP. Whilst there are plenty of code samples here, I want to check whether a queue exists so that I can create it if necessary, before adding a message to it.
try {
// setup connection string for accessing queue storage
$connectionString = 'DefaultEndpointsProtocol=' . PROTOCOL . ';AccountName=' . ACCOUNT_NAME . ';AccountKey=' . ACCOUNT_KEY;
// create queue REST proxy
$queueRestProxy = ServicesBuilder::getInstance()->createQueueService($connectionString);
// create message
$queueRestProxy->createMessage(QUEUE_NAME, 'Hello World!');
} catch(ServiceException $e){
// Handle exception based on error codes and messages.
// Error codes and messages are here:
// http://msdn.microsoft.com/en-us/library/windowsazure/dd179446.aspx
$code = $e->getCode();
$error_message = $e->getMessage();
echo $code.": ".$error_message."<br />";
}
Creating a queue is as simple as this...
$queueRestProxy->createQueue(QUEUE_NAME);
Should I simply include the queue creation code prior to creating a message or is there a more efficient way to ascertain whether the queue exists before interacting with it?
Normally in other Windows Azure SDKs I have seen methods like createQueueIfNotExists and I'm surprised that this method is missing from PHP SDK. Basically the way this function works is that it tries to create a queue. If the queue by the same name exists in storage, storage service throws a Conflict (409) error.
Since this function is not there, you could do the same i.e. try to create the queue inside its own try/catch block and check the error code. If the error code is 409, you continue otherwise you rethrow the exception. Something like the code below:
try {
// setup connection string for accessing queue storage
$connectionString = 'DefaultEndpointsProtocol=' . PROTOCOL . ';AccountName=' . ACCOUNT_NAME . ';AccountKey=' . ACCOUNT_KEY;
// create queue REST proxy
$queueRestProxy = ServicesBuilder::getInstance()->createQueueService($connectionString);
try {
// now try to create the queue.
$queueRestProxy->createQueue(QUEUE_NAME);
} catch(ServiceException $e){
$code = $e->getCode();
//Now check if the $code is 409 - Conflict. If the error code is indeed 409, you continue otherwise throw the error
}
// create message
$queueRestProxy->createMessage(QUEUE_NAME, 'Hello World!');
} catch(ServiceException $e){
// Handle exception based on error codes and messages.
// Error codes and messages are here:
// http://msdn.microsoft.com/en-us/library/windowsazure/dd179446.aspx
$code = $e->getCode();
$error_message = $e->getMessage();
echo $code.": ".$error_message."<br />";
}
P.S. I have not tried to execute the code, so it may throw errors. This is just to give you an idea.
I've posted an answer below for completeness and to make it easy for people to see the answer at a glance.
Should I simply include the queue creation code prior to creating a
message or is there a more efficient way to ascertain whether the
queue exists before interacting with it?
There are two ways to approach this...
Include the createQueue statement prior to creating a message, but wrap this statement in a try-catch block as directed by Guarav Mantri's answer i.e. ignore 409 errors, but throw an exception for any other types of error.
For information, when you include a createQueue statement prior to creating a message...
if a queue of the same name already exists and the metadata
associated with the existing queue is the same as that passed to the
createQueue statement then the queue will not be created and the
Queue REST Proxy will internally receive a 204 (No Content) status code, but this
response code is not made available to the programmer. So,
essentially, the createQueue statement doesn't cause an
error/exception to be raised in this scenario.
if a queue of the same name already exists and the metadata
associated with the existing queue isn't the same as that passed
to the createQueue statement then the queue will not be created and
the Queue REST Proxy will receive a 409 (Conflict) status code and will raise an
exception which allows the programmer to access this response code and the associated QueueAlreadyExists message.
Source: Create Queue (REST API) - see Remarks section
Create a queueExists function and call it to decide whether or not queue creation is necessary. Here is one way to implement such a function...
public function queueExists($queueRestProxy, $queueName) {
$result = FALSE;
$listQueuesResult = $queueRestProxy->listQueues();
$queues = $listQueuesResult->getQueues();
foreach($queues as $queue) {
if ($queue->getName() === $queueName) {
$result = TRUE;
break;
}
}
return $result;
}
Hope this helps someone!
I have a very simple INSERT statement being executed from a PHP script running on a Linux Apache web server. I can run the query fine from within SQL Management Studio and it normally runs fine from PHP as well. However, every once in awhile I get an error message from my PHP script that the query failed and the mssql_get_last_message() function returns 'The statement has been terminated'.
What sources can cause this message to be returned from SQL Server?
You have found one of the most annoying parts of SQL Server. There are situations where an error can be raised, and SQL will generated two error messages: the first to explain what the error was, and the second to say something useful like "The statement has been terminated" (which, technically, is error number 3621). The thing is that SQL, and most everything else that touches it--such as PHP--can only see/pick up/process/or otherwise utilize that last unlcear error message. The one that's actually useful gets lost.
The quick way to figure out what's going on is to run the sequence of commands leading up to the error from SSMS. This, apparently, will not work for you.
A fussier way to figure it out is to fire up SQL Profiler to track the Exception event, and then run your process. This should show all errors that occured. Tossing in relevant other events (SP:Starting, SP:StmtStarting, SQL:BatchStarting, whatever is applicable to the code your submitting to the database) will show which command is raising the error.
To get a numeric error code from mssql you can do a select that looks something like
SELECT ##ERROR AS ErrorCode
Which SHOULD return the correct error code.
You can also try this code which is posted on PHP.NET.
function query($sQuery, $hDb_conn, $sError, $bDebug)
{
if(!$rQuery = #mssql_query($sQuery, $hDb_conn))
{
$sMssql_get_last_message = mssql_get_last_message();
$sQuery_added = "BEGIN TRY\n";
$sQuery_added .= "\t".$sQuery."\n";
$sQuery_added .= "END TRY\n";
$sQuery_added .= "BEGIN CATCH\n";
$sQuery_added .= "\tSELECT 'Error: ' + ERROR_MESSAGE()\n";
$sQuery_added .= "END CATCH";
$rRun2= #mssql_query($sQuery_added, $hDb_conn);
$aReturn = #mssql_fetch_assoc($rRun2);
if(empty($aReturn))
{
echo $sError.'. MSSQL returned: '.$sMssql_get_last_message.'.<br>Executed query: '.nl2br($sQuery);
}
elseif(isset($aReturn['computed']))
{
echo $sError.'. MSSQL returned: '.$aReturn['computed'].'.<br>Executed query: '.nl2br($sQuery);
}
return FALSE;
}
else
{
return $rQuery;
}
}
You can use the code in the message to know which is the error. For example:
[2627: The statement has been terminated.]
In this case, the error code is 2627, so if you execute the sql below you'll know the message
SELECT msg.text
FROM sys.messages msg
INNER JOIN sys.syslanguages lng ON lng.msglangid = msg.language_id
WHERE msg.message_id = 2627
AND lng.alias = 'English'
Violation of %ls constraint '%.*ls'. Cannot insert duplicate key in
object '%.*ls'. The duplicate key value is %ls.
This is a way to know the right message error. In my example the error is violation of primary key