Custom error handler for any exception - php

Problem is that i do all according this topic http://www.yiiframework.com/doc/guide/1.1/en/topics.error, but when in my controller i raise exception( throw new Exception("error"); ), yii don't route to my custom error controller and use system default. How to handle such exceptions?

You have to use one of Yii's own Exception classes: CException, CDbException or CHttpException. From the link you gave: http://www.yiiframework.com/doc/guide/1.1/en/topics.error#raising-exceptions
// if post ID is invalid
throw new CHttpException(404,'The specified post cannot be found.');

in case of exception from mysql run your queries as
$insertCommand = $model->getCommandBuilder()->createSqlCommand($sql,$baseParams+$relParams);
try {
$insertCommand->execute();
} catch(CDbException $e) {
// Here's a way to get to the error code for the statement in question.
// These codes are standardized ANSI SQL "SQLSTATE" error codes.
$sqlErrorCode = $insertCommand->pdoStatement->errorCode();
// And to get to the class part of it, simple grab the two first characters.
// The class should be the same regardless of DB vendor, while the rest of the code can differ.
// For example one particular error was reported by PostgreSQL as 23505 but MySQL only said 23000.
$sqlErrorCodeClass = substr($sqlErrorCode, 0, 2);
}

Related

How to retrieve the result of a Firebird INSERT ... RETURNING in Yii

I am trying to use Firebird 2.1 with Yii (using plugin http://www.yiiframework.com/extension/yii2-firebird/) but I have problems doing insert (save) commands, the error message is:
SQLSTATE[HY000]: General error: -502 Cursor is not open
I have found that Yii generates insert statement with returning clause, e.g.:
INSERT INTO CONTRACTS (contract_no) VALUES (10002) RETURNING contract_no
And I guess that the problem is the following: Yii tries to read results from this insert command but there are problems with Yii-Firebird plugin which closes cursors immediately after pdoStatement->execute. The exception is generated in yii/db/Command.php file function protected function queryInternal($method, $fetchMode = null) whose code reads:
$this->pdoStatement->execute();
if ($method === '') {
$result = new DataReader($this);
} else {
if ($fetchMode === null) {
$fetchMode = $this->fetchMode;
}
try {
$result = call_user_func_array([$this->pdoStatement, $method], (array) $fetchMode);
$this->pdoStatement->closeCursor();
} catch (Exception $ex) {
Yii::trace('Fetch error', 'yii\db\Command::query');
}
}
I have the following questions:
Does anyone uses Firebird with Yii, what the experience is?
How to explain code:
call_user_func_array([$this->pdoStatement, $method], (array) $fetchMode);
and where to correct it? I guess that this method should be overriden in Yii-Firebird plugin with the aim to check whether the cursor is open and, if necessary, open the cursor?
Answer for the first question.
With the YII2 I use "edgardmessias/yii2-firebird": "^0.7.1", extension for Firebird 2.1.1883.
I found that it better to work not with ActiveDataProvider, but with
$dataProvider = new ArrayDataProvider([
in the SearchModel. That way are not so much errors while searching with relation.
But main problem is case sensitive search. I always needed to write in the search field same letters what record have, Big or small.

Laravel. Catch Exception thrown on App::make('foo')->bar()

I have a PriceController that updates the prices of my items. In my app, a Price is a set or PriceSegments (or Rules).
So my PriceController#update does:
foreach (Input::get('price_segment_id') as $price_segment_id)
{
try {
\App::make('BF\Controllers\PriceSegmentController')->update($price_segment_id);
} catch(BF\Exceptions\ValidationException $e) {
$errors[] = $e->get_errors();
}
}
And my PriceSegmentController#update does the update of each segment as follow:
$priceSegment = $this->repository->find($id);
if($priceSegment) {
// UPDATE $priceSegment with Input, etc.
$this->validator->validate($priceSegment->toArray());
$priceSegment->save();
}
If I do the try-catch in PriceSegmentController it works as expected, but I would like to do the try-catch un PriceController in order to collect all the error and being able to inform them all at once.
The problem is the Exception is not catched and I do not know why. I guess is something related with the behaviour of App::make('foo') but I did not figure it out.
Some help please?
Thanks
EDIT: The structure of my code is based on this example: http://www.sitepoint.com/data-validation-laravel-right-way/ and I am using my own Exceptions extending the Exception PHP class
I think the problem occurs because you are using the default validation mechanism in laravel as described here.
What you can do instead is to catch exceptions in PriceSegmentController and:
either wrap them with a custom exception type
or create a custom exception using exceptions you catch in that controller
and throw your custom exception further up in the food chain.
I hope it helps.

Laravel catch exception and add message to a messagebag

I have a repository that throws an exception if it can't find a record in the database. Rather than redirect to another page I just want to display a warning alert as the record is not critical to the page but is an "exceptional event".
It's probably best to demonstrate with code:
// FxRateRepositoy
public function getRate(/** args **/)
{
$rate = FxRate::where(.... //query to get the rate
if (!rate)
throw new NonExistentCurrencyException(//message);
return $rate;
}
In my start/global.php I have a handler:
App::error(function(NonExistentCurrencyException $e)
{
Session::flash('alert', $e->getMessage());
return \\ ??
});
What to return? I must return a response or the exception continue uncaught. I want to continue to the intended page but with the alert flashed in the session. Is this possible without having to use try catch blocks in every place this method is called?
Ass an additional question, assuming this exception may be thrown multiple times in one request, what's the best way to accumulate alert messages and display them? I'm thinking something akin to the validation messageBag. Can I just use the global $errors variable or should I create a new, specific messagebag for this purpose?
The problem is that if you return nothing from App::error Laravel will display it's default error page. On the other side you can't return a response because you don't know what response it should be in the error handler.
I suggest you handle it in the controller itself.
You can catch the exception there and flash the message or don't throw an exception at all:
$rate = FxRate::where(.... //query to get the rate
if (!rate){
Session::flash('alert', 'Whoops');
}
Also the findOrFail() and firstOrFail methods might be of use. They throw an ModelNotFoundException if the query yields no results:
try {
$rate = FxRate::where(....)->firstOrFail()
// and so on
} catch (Illuminate\Database\Eloquent\ModelNotFoundException $e){
Session::flash('alert', 'Whoops');
}
As for a messages system, take a look at the laracasts/flash package

Detect if an exception has been thrown manually without using a custom exception class

I got a try-catch block in my php application like this:
try {
if ($userForgotToEnterField) {
throw new Exception('You need to fill in your name!');
}
...
doDifferentThingsThatCanThrowExceptions();
...
} catch (ExpectedException $e) {
$template->setError('A database error occured.');
} catch (Exception $e) {
$template->setError($e->getMessage());
}
I would like to only output $e->getMessage() for the exceptions I have manually thrown with a custom error text and not the ones that have been thrown by the other code as these might contain sensitive information or very technical info that the user should not see.
Is it possible to differentiate from a manually thrown exception and a random exception thrown by some method without using a custom exception class?
I agree that it might be best to just write your own exceptions. If for whatever reason you don't want to, you could set a custom error message and a custom error code (the second parameter for the Exception constructor.) Check each thrown Exception if the error code is yours, and display only those:
public Exception::__construct() ([ string $message = "" [,int $code = 0[, Exception $previous = NULL ]]] )
and then use getCode
I've thought about this a bit and I'd say that what you are doing DOES call for a custom exception class. If you want to get around it (which in the end is going to be more confusing), you would basically create a global (or same-scope) variable that all exceptions can modify, and in your throw block flag it.
$threwCustomException = false;
try {
if ($userForgotToEnterField) {
throw new Exception('You need to fill in your name!');
$threwCustomException = true;
}
...
doDifferentThingsThatCanThrowExceptions();
...
} catch (ExpectedException $e) {
$template->setError('A database error occured.');
} catch (Exception $e) {
if($threwCustomException){
//Whatever custom exception handling you wanted here....
}
$template->setError($e->getMessage());
}
That's the best I can think of. However, this is a bad idea, and it's the whole reason you are allowed to create your own exception classes. I know you're not looking for this answer, but since you look like you're trying not to create a TON of extra code, I would just extend Exception to "CustomException" or some other name specific to your project, and throw that for all cases, and handle it that way.
Hope that helps.

Detect mysql update/insertion failure due to violated unique constraint

This is kind of similar to this question:
PHP MySQL INSERT fails due to unique constraint
but I have a different twist. Let's say I have a table with only one column. The column's name is "title" and it has a unique constraint placed on it.
First I insert a row where title = "something". The next time I try to insert "something" it will fail due to a unique key constraint (which is good). What I'd like to do is allow it to fail, and check the error code provided by mysql to ensure it failed due to a unique key constraint. (i.e. let the database handle the uniqueness, and I just handle the error code and tell the user that title already exists when the result comes back).
Is there a way to do this?
Now that it's the year 2015, there are very few reasons not to be using PHP's PDO implementation.
The proper, modern, "OO" method for detecting and handling an insertion failure due to a key constraint violation is as follows:
try {
//PDO query execution goes here.
}
catch (\PDOException $e) {
if ($e->errorInfo[1] == 1062) {
//The INSERT query failed due to a key constraint violation.
}
}
The PDOException object has a lot more to say about the specific nature of the error, too (more detail than one could possibly ever want or need, seemingly).
http://dev.mysql.com/doc/refman/5.5/en/error-messages-server.html
http://php.net/manual/en/function.mysql-errno.php
I've had to do this in the past, and it's not fun:
if( mysql_errno() == 1062) {
// Duplicate key
} else {
// ZOMGFAILURE
}
A note on programming style (Credits to jensgram from this answer)
You should always seek to avoid the use of magic numbers. Instead, you could assign the known error code (1062) to a constant (e.g. MYSQL_CODE_DUPLICATE_KEY). This will make your code easier to maintain as the condition in the if statement is still readable in a few months when the meaning of 1062 has faded from memory :)
I believe the error code for duplicate keys is 1586. If you were to attempt to execute a query and then, on failure, check the error code using mysql_errno()/mysqli::errno() and compare it to 1586, that should do it. If it's not 1586, check what it actually is by echoing the error code after your query.
Why not just do a select first to see if the entry already exists. Or suppress an error altogether by using INSERT ON DUPLCATE KEY UPDATE, or even use the mysql IGNORE keyword. Why purposely cause an error?
Topic is of interest for fellow PHP/Mysql users so let me outline a solution.
Please note
There is no magical portable way to do it
situation is not unique to PHP, if you want to detect DB2 unique key constraint violation with openJPA - you have to restore to similar kind of handling
Suppose you have a form - where you have a field "Name"
1) In the DB table
Add a unique constraint like -
alter table wb_org add constraint uniq_name unique(name);
2 ) The form handler script
The form handler script should pass the data to DB layer and if there are any errors, the DB layer would signal it as an DBException (An exception defined by us). we wrap the code sending data to DB layer in a try-catch block (only relevant code is shown)
try{
....
$organizationDao = new \com\indigloo\wb\dao\Organization();
$orgId = $organizationDao->create($loginId,$fvalues["name"]) ;
....
} catch(UIException $ex) {
....
// do UI exception handling
} catch(DBException $ex) {
$errors = array();
$code = $ex->getCode();
$message = $ex->getMessage();
// look for code 23000, our constraint name and keyword duplicate
// in error message thrown by the DB layer
// Util::icontains is just case-insensitive stripos wrapper
if( ($code == 23000)
&& Util::icontains($message,"duplicate")
&& Util::icontains($message,"uniq_name")) {
$errors = array("This name already exists!");
} else {
// Not sure? show generic error
$errors = array(" Error: doing database operation!") ;
}
// log errors
Logger::getInstance()->error($ex->getMessage());
Logger::getInstance()->backtrace($ex->getTrace());
// store data in session to be shown on form page
$gWeb->store(Constants::STICKY_MAP, $fvalues);
$gWeb->store(Constants::FORM_ERRORS,$errors);
// go back to form
$fwd = base64_decode($fUrl);
header("Location: " . $fwd);
exit(1);
}catch(\Exception $ex) {
// do generic error handling
}
Please note that you have to find the ex->getCode() for your situation. Like in above, the PDO layer is actually throwing back the SQLSTATE 23000 as ex->code ( where the actual mysql error code is 1062). The code can vary from DB to DB also. Same way ex->message can also vary. It would be better to wrap this check in one place and fiddle using a configuration file.
3) inside DB layer (using PDO)
static function create($loginId, $name) {
$dbh = NULL ;
try {
$dbh = PDOWrapper::getHandle();
//Tx start
$dbh->beginTransaction();
...
// do DB operations
//Tx end
$dbh->commit();
$dbh = null;
} catch(\Exception $ex) {
$dbh->rollBack();
$dbh = null;
throw new DBException($ex->getMessage(),$ex->getCode());
}
4) Back on the form (after hitting form Handler => DB Layer => Form Handler error handler => Form)
Extract error messages set in session and display them on the form.
5) DBException class
<?php
namespace com\indigloo\exception {
class DBException extends \Exception {
public function __construct($message,$code=0, \Exception $previous = null) {
// PDO exception etc. can return strange string codes
// Exception expects an integer error code.
settype($code,"integer");
parent::__construct($message,$code,$previous);
}
}
}
?>
6) icontains utility method
static function icontains($haystack, $needle) {
return stripos($haystack, $needle) !== false;
}
Can we do this without exceptions and PDO?
7) without PDO and using only mysqli
Get error code and error message from mysqli and throw DBException from DB layer
Handler the DBException same way.
8) Can we do this w/o using exceptions?
I am writing this without any experience of actually doing it in live code. So please let me know if you do not agree. Also, please share if you have a better scheme. if you just want a catch-it-all generic sort of handler then yes.
inside the DB layer: raise errors using trigger_error instead of throwing exceptions. inside trigger_error method - use some MAGIC_STRING + DB_CODE
define a custom error handler for form handler page
inside your custom error_handler for form handler : look for MAGIC_STRING + code
if you get MAGIC_STRING + code then
set appropriate message in session
forward to form page
display a custom message set in session
The problem I find with trigger_error and error_handlers is that
you cannot trap them in the flow of execution like you can do with exceptions. However this is not a problem in our case because our error_handler for page just needs to redirect to form page.
I do not know a way to raise specific error codes (what code I want) with trigger_error method. If only it were possible to raise an error with code X and our message Y. So far as I know you cannot do that. That is why we are restoring to parsing every error_message that our error_handler receives.
I do not have much experience working with error codes (I have been raised on exceptions) - so maybe someone else can enlighten us.
The code samples are from my public github repo https://github.com/rjha/website - code that I am writing for create a website builder to launch thousands of sites from same DB. The code above is used to check unique name for a website.
From PHP Documentation on the function mysql_errno:
Returns the error number from the last MySQL function,
or 0 (zero) if no error occurred.
Also, from MySQL Documentation on Constraint Violation Errors, error code 893 corresponds to:
Constraint violation e.g. duplicate value in unique index
So, we can then write something like this to do the work:
if (!$result) {
$error_code = mysql_errno();
if ($error_code == 893) {
// Duplicate Key
} else {
// Some other error.
}
}
If you know some SQL, try this solution (tested)
$username = "John";
$stmt = $pdo->prepare("
INSERT INTO users (
username
) SELECT * FROM (
SELECT :username
) AS compare
WHERE NOT EXISTS (
SELECT username
FROM users
WHERE username = :username
) LIMIT 1;
");
$stmt->bindParam(":username", $username);
if ($stmt->execute()) {
if ($stmt->rowCount() == 0) {
echo "Dublicate Username, ".$username." already exists.";
} else {
echo $username." not in use yet.";
}
}

Categories