I'm doing my first own database class at the moment and currently I'm doing the prepare function.
What this function does is to take in an SQL-query and then an array containing the variables for the statement. I'm having problems with binding the parameters to the statement.
This is how the function looks like now
public function prepare($query, $var) {
$types = '';
foreach($var as $a) {
$type = gettype($a);
switch($type) {
case 'integer':
$types .= 'i';
break;
case 'string':
$types .= 's';
break;
default:
exit('Invalid type: ' . $a .' ' . $type . '(' . count($a) . ')');
break;
}
}
$stmt = self::$connection->prepare($query);
$stmt->bind_param($types, $var); // How do I do here??
$stmt->execute();
$result = $stmt->get_result();
while($row = $result->fetch_assoc()) {
print_r($row);
}
}
Everything works as I want it to (I know this function could do some polishing but it does what it needs to do). I have commented the line where I'm having trouble figuring out what to do. $var is an array and if I recall things correctly the variables needs to be passed seperately seperated with a comma. This is where I'm clueless.
The very idea of your own database class is great.
Very few people here do share it, for some reason prefers raw api calls all over their code.
So, you're taking great step further.
However, here are some objections:
Don't use mysqli for your first own database class if you're going to use native prepared statements.
Use PDO instead. It will save you a ton of headaches.
Despite of the fact this function works all right for you, it makes not much sense:
switch($type) code block is useless. Mysql can understand every scalar value as a string - so you can bind every value as s with no problem.
most integers coming from the client side have string type anyway.
there are some legitimate types like float or NULL or object that can return a string. So, automation won't work here. If you want to distinguish different types, you have to implement type hinted placeholders instead.
Never use exit in your scripts. throw new Exception('put here your error message') instead.
This is not actually a prepare function as it does execute as well. So, give it more generic name
But now to your problem
It is direct consequence of using mysqli. It is a nightmare when dealing with prepared statements. Not even only with binding but with retrieving your data as well (because get_result() works not everywhere, and after creating your application locally you will find it doesn't work on the shared hosting). You can make yourself an idea looking at this bunch of code served for this very purpose - to bind dynamical number of variables.
So, just keep away from mysqli as far as as you could.
With PDO your function will be as simple as this
public function query($query, $var=array())
{
$stmt = self::$connection->prepare($query);
$stmt->execute($var);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// and then used
$data = $db->("SELECT 1");
print_r($data);
You can take a look at my class to get some ideas.
Feel free to ask any questions regarding database classes - it's great thing, and I am glad you're going this way.
To answer questions from the comments.
To let you know, you're not the only user of the site. There are also some innocent visitors. Unlike you, they don't need no error messages, and they get scared with some strange behavior and lack of familiar controls.
exit() with error message does many evil things
throws an error message out, revealing some system internals to a potential attacker
scaring innocent user with strange message. "What's that? Who is invalid? Is it mine fault or what? Or may be it's a virus? Better leave the site at all" - thinks them.
killing the script in the middle, so it may cause torn design (or no design at all) shown
killing the script irrecoverably. while thrown exception can be caught and gracefully handled
When connecting to PDO, no need to throw anything here as the exception already thrown by PDO. So, get rid of try ... catch and just leave it one line:
self::$connection = new PDO($dsn, $user, $pass);
then create a custom exception handler to work in 2 modes:
on a development server let it throw the message on the screen.
on a live server let it log the error while showing generic error page to the user
Use try ... catch only if you don't want to whole script die - i.e. to handle recoverable issue only.
By the way, PDO don't throw exception on connect by default. You have to set it manually:
$opt = array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION );
self::$connection = new PDO($dsn, $user, $pass, $opt);
Related
I try to handle or catch possible errors with PHP/MySQL for security reasons and would like to know if I'm doing it right.
The first case: I use it as a function and call it always when I need a database connection.
The second case: I am not sure how to handle it. I put all prepared statements in if conditions but that's pretty awkward.
And what about $stmt->execute? This could also fail and to handle this also in an if condition can really get confusing.
I hope there is a better way to go.
First:
function pdo () {
try {
$pdo = new PDO('mysql:host=localhost;dbname=dbname', 'user', 'pw');
}
catch(PDOException $e) {
header("Location: handle_error.php");
exit;
}
return ($pdo);
}
Second:
if ($stmt = $pdo->prepare("SELECT a FROM b WHERE c = :c")) {
$stmt->execute(array(':c' => $c));
$result = $stmt->fetch();
echo 'All fine.';
}
else {
echo 'Now we have a problem.';
}
Your current code has some flaws. Let me give you few pointers.
You do not need the function you have created, at least not in the current form. Every time you call this function it creates a new PDO object, which can hinder your script's performance. Ideally you would want to have only one connection throughout the execution of your whole script.
When creating new PDO connection you need to remember 3 things: to set proper connection charset, enable error reporting, and disable emulated prepares.
Proper charset is important. Without it your data could get corrupted or even leave you vulnerable to SQL injection. The recommended charset is utf8mb4.
Enabling error reporting saves you the trouble of manually checking every single function call for failures. You just need to tell PHP to trigger exceptions when an error occurs and find a suitable way of logging the errors on your server (read more: My PDO Statement doesn't work)
Emulated prepares are enabled by default, but the truth is you are better off without them. If your database supports native prepared statements, and most do, then use them instead.
The full code should look something like this:
$options = [
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_EMULATE_PREPARES => false,
];
$pdo = new \PDO('mysql:host=localhost;dbname=dbname;charset=utf8mb4', 'user', 'pw', $options);
Don't catch the exceptions unless you know what to do with them and are positively sure you need to do so. Exceptions are best left be; let PHP handle them together with the other exceptions/errors/warnings. After all why make an exception just for PHP exceptions? All PHP errors should be handled the same. Whatever you do never print (including die, exit, echo, var_dump) the error message manually on the screen. This is a huge security issue if such code ever makes its way into production.
If your PDO connection is set to throw exceptions on errors, you never need to use if statements to check return code of prepare() or execute().
We have been going through some old code of ours and we have found some code that looks something like:
try
{
$stmt = $db->prepare($query);
$stmt->bindvalue(1, $id, PDO:ARAM_INT);
$stmt->execute();
$row = $stmt->fetchColumn();
}
catch(PDOException $e)
{
echo "There was an issue with query: ";
print_r($db->errorInfo());
}
Which at first glance we thought looked fine (Even many answers on Stack Exchange give this as example code). Then we looked at the PHP documentation for the errorInfo function and it states that:
PDO::errorInfo() only retrieves error information for operations performed directly on
the database handle. If you create a PDOStatement object through
PDO:repare() or PDO::query() and invoke an error on the statement
handle, PDO::errorInfo() will not reflect the error from the statement
handle
Which, if we understand it correctly, means that if anything goes wrong in any of the statement operations we do, we will not actually print out the error code we are expecting after "There was an issue with the query: ". Is this correct?
In light of this, we started looking for the proper way to do this, we started by looking at the PDOException class documentation which suggests that we might do something like:
try
{
$stmt = $db->prepare($query);
$stmt->bindvalue(1, $id, PDO:ARAM_INT);
$stmt->execute();
$row = $stmt->fetchColumn();
}
catch(PDOException $e)
{
echo "There was an issue with query: ";
print_r($e->errorInfo());
}
My questions are:
Is the above way the proper way of doing this? If not, what IS the proper way of doing it?
Is there any more useful information avaliable by using $db->errorInfo() and $db->errorCode ( or $stmt->errorInfo and $stmt->errorCode ) beyond what you can see from PDOException?
If there IS anything more detailed available-and-useful from those detailed calls, then is there a way to differentiate, by examining the PDOException, whether it was thrown by PDO or by PDOStatement?
The exception may be thrown by either $db->prepare or any of the $stmt operations. You do not know whence the error originated, so you should not guess. The exception itself contains all the information about what went wrong, so yes, consulting it and only it is the only sensible thing to do.
Moreover, it's usually nonsense to try..catch directly around the database call (unless you have a clear plan about something you want to do if this particular database operation fails). In your example, you're merely outputting the error and are continuing as if nothing happened. That's not sane error handling. Exceptions explicitly exist to abort and jump ship in case of a severe error, which means the part of your code which should actually be catching the exception should live several layers up and not have access to $db or $stmt at all (because it's in a different scope). Perhaps you should't be catching the exception at all and have it terminate your entire script (again, unless you expected an error to occur and have a clear plan how to handle it and how to recover your application into a known state). So looking only at the information in the exception itself is, again, the only sensible thing to do.
If there IS anything more detailed available-and-useful from those detailed calls, then is there a way to differentiate, by examining the PDOException, whether it was thrown by PDO or by PDOStatement?
This is only useful if, again, you have any sort of plan for recovery and that plan differs depending on where the error occurred. First of all, I doubt that, but if that's indeed the case, then you'd do something like this:
try {
$stmt = $db->prepare($query);
} catch (PDOException $e) {
// do something to save the day
}
try {
$stmt->bindValue(...)
..
} catch (PDOException $e) {
// save the day another way
}
In other words, you isolate the try..catch statements to smaller parts of your code that you want to distinguish. But, really... if $db->prepare failed, what are you going to do? You can't continue with the rest of the code either way. And what are you going to do differently than if a $stmt method failed? As one atomic unit, you were unable to query the database, period.
The PDOException::$code will give you the more detailed SQLState error code, which may tell you something useful (e.g. unique constraint violation), which is useful information to work with on occasion. But when inspecting that it's pretty irrelevant which specific line threw the error.
I am working on a project with a friend, we are building our own login and registration system for our site which we are creating. I am questioning his skills at coding in PHP with this following code statement:
try {
$stmt = $db->prepare($query);
$results = $stmt->execute($params);
}
catch() {
trigger_error('The query has failed.');
}
I know that the SQL Query which we are going to perform is going to work for logging in the user, that is not the issue here and the reason why that part of the code is not being displayed within the code block above.
This is the very first time which I have saw someone use trigger_error() with PDOExeception $error statement, which was how I was taught to code to begin with.
Should we continue our core login, registration, and all SQL statements this way by using a Try, Catch, and Trigger_Error? Should I change it over to PDOExeception $error?
Neither.
Trigger error makes not a slightest sense here, as uncaught Exception already an error. So, to catch an error only to throw an error is a tautology. Moreover, uncaught Exception contains indispensable stack trace while just ordinary error doesn't.
Neither echo with catch is required. this is but a calloused delusion of PHP folks.
Neither writing four additional lines of code for the every query execution makes no sense as well. I even wrote a dedicated article on the matter - so, I wouldn't repeat myself
So, just rewrite this code to
$stmt = $db->prepare($query);
$results = $stmt->execute($params);
which is all you actually need
I'm writing a library which requires a database connection, so it requires users to pass a PDO object as a parameter of the constructor.
But as many of you know, PDO has 3 different error reporting mechanisms: Silent mode, warning mode and exception mode (http://www.php.net/manual/en/pdo.error-handling.php).
The problem is that each mode requires a different type of error handling code. Accepting that I can't force the user to use my preferred mode, i think that my options are:
Setting my preferred mode on the inputted PDO object, and worrying about nothing
Cloning the inputted PDO object and then setting my preferred mode
Writing error handling code for all 3 modes and then detecting and employing the appropriate one with the help of PDO::getAttribute() method
Setting my preferred mode before each method call and then revoking it after each one
So 1 can break user's code, 2 looks like unnecessary duplication, 3 is terribly awkward, and 4 is not-so-terribly-awkward-but-still-awkward and still susceptible to breaking user's code.
So i'm asking to library writers out there, how do you handle this?
Ok, not sure if this is the right site for this sort of question, but first, let me give you the short run-down of your options, and tell you what IMO is the better option. Then I'll explain all of it in future edits.
That's not the way forward. The user is passing an instance, which you then change, behind the users' back. Don't change what isn't yours
Don't clone a DB connection. Just don't
A library shouldn't deal with errors that are the result of the user's code. That's the user's problem/fault, they should then also deal with it. No way a lib can anticipate on every possible abuse
No, really... This is just a silent way of doing what you're doing in point 1: changing an object you never really owned.
What, then, would I do? Simple: Provide an API for DB connections that could, in its heart have a PDO instance, but then at least the user has a clear API, and knows what the result of possible errors are (for example PDO + setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) => your API will always throw exceptions
Now, why is your first option not a viable approach? (again: this is all my opinion)
Suppose I were to use your code, and have something like this:
//code
$this->db = new PDO($dsn, $usr, $pass, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENT));
$this->dependency = new Your\Lib\Stuff($this->db);
//code
$this->db->query('bad query');
Now if you set PDO to throw exceptions, I'm not catching any. My code wasn't writtin in a way to deal with PDOException instances, so it'll cause the entire app to grind to a halt. What's more, who's to say that I'm not going to add this line, while debugging, when that happens:
$this->dependency = new Your\Lib\Stuff($this->db);
$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);//override your setting
This isn't a safe path to go down, so don't.
Next: cloning PDO just won't work. If you have a silly person who wrote his own class to extend from PDO, this is possible:
class BadIdea extends PDO
{
public function __clone()
{//disable clone
return false;
}
}
In this case $db = new BadIdea() will still pass as an instance of PDO (test function foo(PDO $arg){echo 'argument is instance of PDO';} and then call foo(new BadIdea), it will work). Now you can't clone, and your lib fails.
A lib or framework should be written generically enough so you can reuse it. If you're writing the code, while thinking of a particular use-case in mind, you're probably going find yourself editing the code every time you wish to use it again.
Unless you're going to be creating your own lib (in which case the user wouldn't have to pass a self-made DB connection anyway), error handling shouldn't be the task of your lib.
Your code should just throw exceptions when something happens that is unexpected. You can't be expected to write code that deals with all of these situations:
$yourInstance->pass('Invalid query');
$yourInstance->select('INSERT ...');//valid query, wrong method
$your instance->query(array('invalid', 'argument'));
Or worse:
try
{
$yourInstance->beginTransaction();
$yourInstance->query($q1);
$q2 = $anotherObj->composeComplexQuery();//might be the cause of Exceptions, too
$yourInstance->query($q2);
$yourInstance->commit();
} catch(){}
When you deal with errors inside of your lib, then how will your user be able to deal with situations like these? a transaction is a concious decision made by the user, any exception that might occur during a transaction, be it an error in the queries or an exception thrown by third-party methods can result in the transaction having to be rolled back.
Your lib's scope isn't wide enough to pick up on the exceptions that are thrown in the user's code.
I've recently changed all my mysql.* to PDO. I've been having a few issues and trying to get used to it.
My question is how to properly call an error when there is an issue in the sql statement. Normally I wouldn't use try, catch but is there another alternative?
Suppose I have the following:
private function init()
{
$query = $this->_PDO->prepare("SELECT * FROM here WHR name='john'");
if($query->execute())
{
$this->_sql_rows = $query->rowCount();
}
else
{
print_r($query->errorInfo());
}
}
This checks whether the execute method worked and if not, output errors. In this case it should as there is a spelling mistake. Normally when I do this, I never see any errors come out and must always use the method above to output an error. Is this a reliable and appropriate way of handling such errors?
Nope, it is not.
Just like almost every PHP user, you have quite vague and uncertain idea on error handling. And the code mostly looks like a dummy code from sandbox example. Speaking of your current approach, frankly - it's just terrible. On a live site it will only scare an innocent user, while webmaster would have no idea what's going on.
So, first of all you have to make your mind what is error handling you are looking for.
There are different possible scenarios, but most convenient is just to follow the way PHP handles all other errors. To do that just set PDO in exception mode. This way you'll be always notified of all the errors occurred. So - just add this line right after connect.
$dbh->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
and thus your function become short and clean
private function checkUser($user)
{
$stmt = $this->_PDO->prepare("SELECT 1 FROM users WHERE name=?");
$stmt->execute(array($user));
return $stmt->fetchColumn();
}