I've read that exceptions shouldn't be used for directing the flow of your application but should be used to recover the application to a stable state when something "exceptional" happens, for example, when you fail to connect to a database.
An example of where an exception shouldn't be used would be a user providing an incorrect login. It wouldn't be an exception since it's expected that that will happen.
I'm not sure whether the following case is exceptional or not:
I'm currently designing a simple blog. A "post" is assigned to just one "category". In my posts table I have a category_id field with a foreign key constraint.
Now, I'm using Laravel so if I try to delete a category that currently has posts in it, I believe it should throw an \Illuminate\Database\QueryException because of the foreign key constraint. Therefore should I rely on this and write code like:
try {
$category->delete();
}
catch (\Illuminate\Database\QueryException $e) {
// Tell the user that they can't delete the category because there are posts assigned to it
}
or since I know how I want my blog to work, should I use:
if ($category->posts->isEmpty()) {
$category->delete();
}
else {
// Tell the user they can't delete...
}
Any advice would be appreciated. Thanks!
It is very opinion based, so i'll give you my opinion:
Exceptions are powerfull, because you can attach a lot of information with it - and you can send them "up the callstack" without having hundrets of methods checking the return value of any call and returning whatever to their own caller.
This allows you to easily handle an error at the desired layer in the callstack.
A Method should return, what is the result of the call (even if it's void). If the call fails for any reason, there should be no return value.
Errors should not be transported by returnvalues, but with exceptions.
Imagine a function doing db-queries: KeyAlreadyExistsException, InvalidSyntaxException and NullPointerException - most likely you want to handle this "errors" at very different parts in your code.
(One is a code-error, one is a query-error, one is a logical-error)
Example one, easy "handling":
try{
method1(1);
}catch (Exception $e){
//Handle all but NullpointerExceptions here.
}
---
method1($s){
try{
method2($s+2);
} catch (NullPointerException $e){
//only deal with NPEs here, others bubble up the call stack.
}
}
---
method2($s){
//Some Exception here.
}
Example two - you see the required "nesting", and the stack-depth is only 2 here.
$result = method1(1);
if ($result === 1){
//all good
}else{
//Handle all but NullpointerExceptions here.
}
---
method1($s){
$result = method2($s+2);
if ($result === 1){
return $result;
}else{
if ($result === "NullPointerException"){
//do something
}
}
method2($s){
//Exception here.
}
Especially for maintainance, Exceptions have huge advantages: If you add a new "Exception" - the worstcase will be an unhandled exception, but code execution will break.
If you add new "return errors", you need to make sure, that every Caller is aware of these new errors:
function getUsername($id){
// -1 = id not found
$name = doQuery(...);
if (id not found) return -1;
else return $name;
}
vs
function getUsername($id){
$name = doQuery(...);
if (id not found) throw new IdNotFoundException(...);
return $name;
}
Now consider the handling in both cases:
if (getUsername(4)=== -1) { //error } else { //use it }
vs
try{
$name = getUsername(4);
//use it
}catch (IdNotFoundException $e){
//error
}
And now, you add the return code -2: First way would assume the username to be -2, until the error code is implemented. Second way (Another Exception) would cause the execution to stop with an unhandled exception somewhere way up in the callstack.
Dealing with return values (of any kind) for error-transportation is error prone and errors might vanish somewhere, turning into a "wrong" interpreted result.
Using exceptions is safer: You either have a return value to use, or a (handled or unhandled) exception, but no "wrong values" due to autocasts etc.
Related
I've a question.
I need to handle the Mysql error (like duplicate key, foreign constraints...) for example:
$id=$db->insert ('user', $data);
if(!$id){
switch($db->getLastErrno()){
throw new Exception(... );
}
}
Here i use error number to select the right Exception.
But for the same error, what i've to do?
I think to use error string, for example:
if($db->getLastErrno()==1452 && strpos($db->getLastError(), "contraint name created on db")>-1){
throw new Exception(... );
}
But i don't know why, i think it's a little bit trash. Does anyone have other solutions?
Put the if statement inside the case. You don't need to repeat the error number test.
switch ($db->getLastErrno()) {
case 1452:
if (strpos($db->getLastError(), "constraint name created on db") !== false) {
throw new Exception(...);
}
break;
...
}
I am trying to check for toll-free numbers, and it is working as expected. However, my problem is that some countries don't have the TollFree numbers.
Using the same code on these countries throws a 404 error and stops the code there.
The only way I could think of is making a massive if statement and adding each country manually which offers toll-free option, but I don't like this solution at all as it will be hardcoded. Is there a way to overcome this issue, so it works for the countries that has the .json and ignore the ones that doesn't (instead of crashing the code)?
$twilio = new Client(env('TWILIO_ID'), env('TWILIO_TOKEN'));
$iso = 'CY';
$params = ["excludeLocalAddressRequired" => "true"];
$tollFreeNumbers = $twilio->availablePhoneNumbers($iso)->tollFree->read($params);
This is the response:
"[HTTP 404] Unable to fetch page: The requested resource /2010-04-01/Accounts/ACxxxxx/AvailablePhoneNumbers/CY/TollFree.json was not found"
Using this code will crash with CY but will work with UK, US, CA and many more. Should I add an if statement with hardcoded countries? (I really dislike this solution, but this is what I can think of). What I mean is:
if ($iso == 'GB' || $iso == 'US' || $iso == 'CA') { // and many more
$tollFreeNumbers = $twilio->availablePhoneNumbers($iso)->tollFree->read($params);
}
Twilio developer evangelist here.
Rather than guarding up front with a conditional (which could become out of date as we add toll free numbers in other countries in the future), why not catch the error and return a message to the user to say that toll free numbers are not available in the country they are searching in.
Something like:
try {
$tollFreeNumbers = $twilio->availablePhoneNumbers($iso)->tollFree->read($params);
} catch (Exception $e) {
$tollFreeNumbers = [];
$message = "Toll free numbers are not available in this country.";
}
Let me know if that helps at all.
Why not just wrap it in a try catch?
try {
$tollFreeNumbers = $twilio->availablePhoneNumbers($iso)->tollFree->read($params);
} catch(\Exception $e) {
$tollFreeNumbers = [];
}
I'm curious as to when use operation status and when exceptions. Say I have a class TextProcessor with methods getText(), processText() and sendText(). All these methods perform operations on private data of the class. I need to make sure that all operations go smoothly. I've got two options - each method can return operation status (0 - success, >0 error codes) or throw exception inside a method. It seems that exceptions is more convinient way to control method's execution, because I'd need to do the following when operation statuses are returned:
$result = textProcessor->getText();
if ( $result !== 0 ) {
return $result;
}
$result = textProcessor->processText();
if ( $result !== 0 ) {
return $result;
}
$result = textProcessor->sendText();
if ( $result !== 0 ) {
return $result;
}
or this way
if ( textProcessor->getText() !== 0 && textProcessor->processText() !== 0 && textProcessor->sendText() !== 0 ) {
return processingErrors::textProcessorError;
}
It all seems much simpler with exceptions:
try {
textProcessor->getText();
textProcessor->processText();
textProcessor->sendText();
} catch (textProcessorException $e) {
return $e->getMessage();
}
1) So which is better to use in my situation - operation statuses or exceptions?
2) In general, when do I use operation statuses (return codes) and when exceptions?
Exceptions are best used when some requirements of operation is unexpectedly absent. For example, I expect to be able to connect to a database. If I cannot, then I cannot serve the application even in a degraded state. I throw an exception saying so because I cannot continue. If I could continue, that might be something I log but not throw an exception for.
That said, using them for control flow, for me, is totally valid. Having all the return codes as you describe seems less than optimal and not as readable as the exception option. When code is not readable it is not as easily maintained.
I have the following code which results sometimes in a silent failure:
public function updateCoreGameTableIfNecessary($coreEm)
{
$game = $this->getCoreGameRecord($coreEm);
if (!$game) {
$game = new Game();
$game->setHomeSchool($this->getHomeSchool()->getCoreSchool($coreEm));
$game->setDatetime($this->getDatetime()->format('Y-m-d H:i:s'));
$game->setDate($this->getDatetime()->getTimestamp());
$game->setTime($this->getDatetime()->format('H:i:s'));
$game->setSport($coreEm->getRepository('VNNCoreBundle:Sport')->findOneByName($this->getSport()->getName()));
$game->setSeason($coreEm->getRepository('VNNCoreBundle:Season')->findCurrent());
$game->setEventType(strtolower($this->getEventType()->getName()));
$game->setMeetName($this->getMeetName());
$game->setRemoteUnique(md5(rand(0, 100000)));
$game->setNotes($this->getRecap());
$game->setHomeConfId(0); // This field is no longer used, so value doesn't matter.
$game->setAwayConfId(0); // This field is no longer used, so value doesn't matter.
$game->setConfStatus(''); // This field is going away as well.
}
if ($this->getEventType()->getName() == 'Game') {
$game->setHomeScore($this->getHomeScore());
$game->setAwayScore($this->getAwayScore());
$game->setAwaySchool($this->getAwaySchool()->getCoreSchool($coreEm));
} else {
$game->setPlace($this->getPlace());
$game->setPoints($this->getHomeScore());
}
$game->setOwnerId($this->getUser()->getSchool()->getCoreSchool($coreEm)->getId());
$coreEm->persist($game);
$coreEm->flush();
return $game->getId();
}
It always starts with a $this that's already saved. For certain instances of $this (i.e. certain records in the database), $game won't get saved. I won't get an error or anything like that. It will just fail silently.
Any suggestions for debugging? I guess I'll try to figure out what's different about those certain records, but it seems like an insert should never silently fail, for any reason.
What's the best practice to handle errors if using objects?
A) Before the method of object is called and not even getting to execute method if there's some error, or
B) Just pass parameters and perform error checking in method itself, returning error code or something.
Please pick your option and short description, why?
Thanks orlandu63, it is good practice, but what about non-fatal errors, such as user should provide a title for something, and he/she didn't?
class Sample {
var $err_no_title = 1;
function createNewRecord ($title) {
if (!$title) return $this->err_no_title;
}
}
Or use exceptions for these kind of errors also?
If you're using OO, you might as well use Exceptions. My answer is a mix of both A and B:
class DatabaseConnectionException extends Exception {}
class Database {
public function connect($user, $pass, $db) {
//Connection stuff.
if($baduser) {
throw new DatabaseConnectionException('Username (' . $user. ') is invalid.')
}
if($badpass) {
//''
}
}
}
$db = new Database;
try {
$db->connect($user, $pass, $db);
catch (DatabaseConnectionException $e) {
die('I cannot connect to the database:' . $e);
}
What are the advantages to this? I don't know, but it seems right.
You can read more on it on http://php.net/exceptions and google.
Regarding your second part,
First of all your example will treat it more of an error than a "warning" since you exit the function and thus don't create a record if you have no title. This shows that method B is flawed. So method A all the way.
To choose from options you offer it would be B, but don't use error codes and throw exceptions instead. All the logic (even validation of inputs) should be encapsulated in the function.
The reasons are:
The function may change and so may change requirements for inputs.
User of your function may not always know, what the inputs should be like.
You surely don't want to repeat the validation code everywhere you use the function.
Be careful though, as exceptions are raised by object oriented code only. For example this code does not fire an exception:
<?php
$number = $number / 0;
?>
Your example would be like:
<?php
class Sample {
function createNewRecord ($title) {
if (!$title) throw new Exception('Title required');
}
}
...
try {
$mysample->createNewRecord($title);
} catch ($ex) {
echo "Could not create record. Please try again. (Reason: $ex)";
}
...
?>
In the first place, this sort of thing should be validated in the user interface. So it would be A, but B needs to be there to. So final verdict would be: both.