(I have no idea whether the title is descriptive or not. I'm not sure where the problem is, so it's kind of difficult to come up with a good title.)
So here's the thing. I'd like to insert values (from an HTML form) into a MySQL database using PDO.
So far so good. I managed to get the $_POST['xyz'] values and successfully inserted them into the DB. But now I'd like to make sure that there's only one row with the same email address ($email) and same question id ($qid). I did that by checking to row count, as you can see in the code. Not sure if that's a good way to do it or not.
Now I can successfully NOT insert two rows with similar email addresses, but for some reason I cannot insert any rows with any other email address. Been trying to figure out what I am doing wrong, but can't. So here's the code. Hope you can see what I did there (because I can't).
try {
$cnnxn = new PDO("mysql:host=$db_host;dbname=$db_name", $db_username, $db_password);
} catch (PDOException $e2) {
die("ERROR: " . $e2->getMessage());
}
$query2 = $cnnxn->prepare("SELECT count(*) as cnt FROM grdj_replies WHERE email = :email AND question_id = :qid");
$query2->bindParam(':email', $email);
$query2->bindParam(':qid', $qid);
$isQueryOk = $query2->execute();
if ($isQueryOk) {
$count = $query2->fetchColumn();
} else {
trigger_error('Error executing statement.');
}
$query2->closeCursor();
if ($count > 0){
echo '<div class="tools-alert tools-alert-red"><p>Sähköpostiosoitteellasi <strong>'.$email.'</strong> löytyy jo tallennettu vastaus tähän tehtävään. Jos haluat muuttaa vastausta, seuraa sähköpostiosoitteeseesi lähetetyn viestin ohjeita.<p>';
echo '<p>(Klikkaa tästä, jos haluat lähettää ohjeet uudestaan osoitteeseen '.$email.'.)</p></div>';
}
else {
$cnnxn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTIONS);
$cnnxn->exec("SET NAMES utf8");
$query = $cnnxn->prepare("INSERT INTO grdj_replies (question_id, last_name, first_name, email, question_number, answer, status, accesstoken) VALUES (:qid, :lastname, :firstname, :email, :questionnumber, :answer, :status, :accesstoken)");
$query->bindParam(':qid', $qid);
$query->bindParam(':lastname', $last_name);
$query->bindParam(':firstname', $first_name);
$query->bindParam(':email', $email);
$query->bindParam(':questionnumber', $question_number);
$query->bindParam(':answer', $answer);
$query->bindParam(':status', $status);
$query->bindParam(':accesstoken', $accesstoken);
$query->execute();
if ($query !== false)
{
print "<div class=\"tools-alert tools-alert-green\">Vastauksesi on tallennettu!</div>";
}
$query->closeCursor();
$cnnxn = null;
}
Firstly, you are using ->bindParam() where ->bindValue() would be adequate (perhaps, more appropriate).
You ask whether your input processing is a good way to do things: I can't see that code. Rather than using superglobals, use filter_input() / filter_input_array()
The problem you describe might not be in the PHP code: take a closer look at the database indexing. Have you uniquely indexed grdj_replies by qid (e.g. do you have a primary key on "qid" alone)? Are there any other inadequate indexes which are blocking you from inserting additional rows into the table, for the same qid? Perhaps you should uniquely index jointly by qid, email.
You apply error handling to the creation of the PDO object (database connection); in cases like these, I often find it helpful to apply error handling (try/catch, $error->getMessage()) to the execution of the SQL statement (this way, you get to see what the database is trying to tell you. Has it thrown an excuse/explanation for not running the query?)
I suggest you to use a MySQL unique index :
ALTER TABLE `grdj_replies`
ADD UNIQUE INDEX `UNIQUE_IDX` (`email`, `question_id`);
Now MySQL will make sure there's no other row with the same email and question_id before inserting a new row, you don't have to check it "manually".
In the case there is already the same data, the INSERT request would fail.
Related
I have to deal with large mysql DB. Sql queries with lot of calculations (in select clause) and several kind of conditions in where clauses. So, I decided to use row/direct sql queries to deal with DB by using $db = ConnectionManager::getDataSource('default');
If I use this, how I prevent sql injection in mysql query? "mysql_real_escape_string" no longer exists. Is there any way to use PDO within CakePHP?
You can use this in your controller (or component)
// Initiate PDO connection
$this->_pdocon = $this->WhateverYourModel->getDataSource()->getConnection();
try {
// Select Query
$company = "What";
$stmt = $this->_pdocon->prepare('SELECT * FROM `agents` WHERE `company` LIKE :company LIMIT 2');
$stmt->bindValue(':company', $company, PDO::PARAM_STR);
// Start transaction
$this->_pdocon->begin();
// Loop through the events
if( $stm->execute() ) {
while ($row = $stmt->fetchAll(PDO::FETCH_ASSOC)) {
$stmt2 = $this->_pdocon->prepare("INSERT INTO `company`
(`id`, `name`, `identityno`, `modified`, `created`)
VALUES
(NULL, :name, :identityno, NOW(), NOW())");
$stmt2->bindValue(':name', $row['name'], PDO::PARAM_STR);
$stmt2->bindValue(':identityno', $row['id'], PDO::PARAM_INT);
$stmt2->execute();
}
}
// Commit transaction
$this->_pdocon->commit();
// Get last insert Id
$row_id = $this->_pdocon->lastInsertId();
var_dump($row_id);
} catch (PDOException $e) {
// Rollback transaction
$this->_pdocon->rollback();
echo "! PDO Error : " . $e->getMessage() . "<br/>";
}
This is what I ended-up. Using PDO has been solved thousands of issues. Now the system is fast and no memory exhaust error. And I can not putting all issues, errors what I got, in my question. It's good to giving direct answer rather trying to changing questions in here!
A large part of the point of cakePhp is not to do this. Therefore I would recommend not doing this.
Cakephp has a its own implementation for accessing a DB and you should use it if at all possible. Is there a particular reason you want to go around it?
if you realy want to, you can still use mysqli but I cant recommend it.
So I have a piece of code that will check if you have followed a user or not. And basically let you follow them if you haven't. So here it is
if($_SESSION['loggedIn'] == true){
$result = $con->prepare("SELECT * FROM followers WHERE follow_from = :username AND follow_to = :post_id");
$result->bindParam(':username', $username);
$result->bindParam(':post_id', $follower);
$result->execute();
$reprint = $result->fetchAll(PDO::FETCH_ASSOC);
}
print_r($reprint);
if($reprint < 1){
$stmt = $con->prepare("INSERT INTO followers (follow_from, follow_to) VALUES (:ff, :ft)");
$stmt->bindValue(':ff', $follower, PDO::PARAM_STR);
$stmt->bindValue(':ft', $username, PDO::PARAM_STR);
$stmt->execute();
}
else{
echo 'Error';
exit();
}
//Display follower
$stmt1 = $con->prepare("SELECT COUNT(*) AS count FROM followers WHERE follow_to = :username");
$stmt1->bindValue(':username', $username, PDO::PARAM_STR);
$stmt1->execute();
$likes = $stmt1->fetchAll(PDO::FETCH_ASSOC);
print_r($likes);
So when I run it once. I get the else statement echoed. My question is why does this happen? In the database I have no record, so I'd expect it to go in once. I get no errors at all. loggedIn is true. And variables are being passed through successfully.
Any ideas?
You're misusing the result you get from fetchAll(). It's an associative array, not a scalar value. It could, as you've probably guessed, be empty.
But, more significantly than that, your code has a potential race condition. What happens if two different sessions are trying to set this same followers row? (Admittedly, in a small system that is unlikely, but in a large system it might happen).
What you actually do is just the INSERT operation. If your followers row has a unique key on the (follow_from,follow_to) columns, then, if that row is already there you'll get a 'Duplicate entry' error on the INSERT. Otherwise it will just happen. You can just ignore the 'Duplicate entry' error, because all you want is for that row to make it into that table.
So your code would go like this:
$stmt = $con->prepare("INSERT
INTO followers (follow_from, follow_to)
VALUES (:ff, :ft)");
$stmt->bindValue(':ff', $follower, PDO::PARAM_STR);
$stmt->bindValue(':ft', $username, PDO::PARAM_STR);
$result = $stmt->execute();
if ($result) {
/* this follow pair was successfully added */
} else {
/* MySQL may return the error 'Duplicate entry' */
if (false == stripos($stmt->errorCode,'Duplicate')){
echo 'Something failed in the insert: ' . '$stmt->errorCode';
}
else {
/* this follow pair was already in your table */
}
}
Pro tip: Don't use SELECT * in software; it can mess up query optimization; it often sends more data than you need from the server to your program, and it makes your program less resilient if your change your table definitions.
Pro tip: If you must count rows matching a particular WHERE statement, use COUNT() rather than fetching the rows and counting them in the client. What if you get a million rows?
You'd want to use count($reprint) other that a direct comparison. $reprint is an array, not a number
if(count($reprint) < 1)
{
$stmt = $con->prepare("INSERT INTO followers (follow_from, follow_to) VALUES (:ff, :ft)");
$stmt->bindValue(':ff', $follower, PDO::PARAM_STR);
$stmt->bindValue(':ft', $username, PDO::PARAM_STR);
$stmt->execute();
}
else
{
echo 'Error';
exit();
}
PDOStatement::fetchAll
PDOStatement::fetchAll — Returns an array containing all of the result set rows
If you check the size of the array then you would actually know if something happened.
Using proper error handling can tell you if something's failing deep down:
try
{
...
}
catch (PDOException $e)
{
echo $e->getMessage();
}
You will need to enable PDO error-displaying:
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
If checking the size of the array doesn't do it and you get no errors then it's simply some logic error.
Most likely the logic error is that
$reprint = $result->fetchAll(PDO::FETCH_ASSOC);
doesn't get executed (wrapping the error handling around that should tell you why), so
$reprint = $result->fetchAll(PDO::FETCH_ASSOC);
isn't given a proper value, meaning you'll always hit the else statement.
Edit
Your original problem was that "So when I run it once. I get the else statement echoed. [...] In the database I have no record" but now you're saying "It adds the record, but doesn't limit it to me one".
Can you be more clear about the actual, current, problem?
I have a little login script.
function login($sql) {
try {
$fbhost = "localhost";
$fbname = "foodbank";
$fbusername = "root";
$fbpassword = "";
$DBH = new PDO("mysql:host=$fbhost;dbname=$fbname",$fbusername,$fbpassword);
$DBH->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$STH = $DBH->query($sql);
$STH->setFetchMode(PDO::FETCH_ASSOC);
session_start();
if ($row = $STH->fetch()) {
$_SESSION['username'] = "$row[username]";
header("Location:index.php");
}
} catch(PDOException $e) {
echo $e->getMessage();
}
}
EDITS:
index.php
$sql = "SELECT username from users where username = ". $_POST['username'] ." AND password = ". $_POST['password'] ."";
login($sql);
Changed above from insert to select query. Now I get new error:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'pvtpyro' in 'where clause'
Based on your latest edit: You can't fetch results with PDO after executing an INSERT query. See here: http://www.php.net/manual/en/pdostatement.fetch.php#105682
Edit: I suppose, since the function's called "login", you want to have something like this as $sql: "SELECT password FROM users WHERE username = :username", and then iterate over the results with the while loop, and then log in the user if the password matches?
Edit2: Based on your edit to provide a SELECT query: DO NOT USE THIS QUERY. What you are doing is NOT SQL injection proof. Never ever use variables from user input (i.e. $_POST, $_GET et al) and put them unfiltered into an SQL query. Please look up the term "prepared statements" here at SO or Google.
As you can see, since you forgot to put single ticks (apostrophes) before and after the double quotes, MySQL thinks that your input refers to another column ("pvtpyro") instead of comparing the value in the column against a string. ALWAYS use the ":username", ":password" syntax (the one with prepended colons) or your queries will be unsafe and enormously dangerous to your application.
The constructor of PDO uses 2 variables which are not defined in the code you supplied - $fbhost and $fbname.
EDIT:
You're calling session_start() inside the while loop, which can cause errors. Take it out of the loop.
EDIT 2:
You should really debug the code. Either via putting die in different parts of the code, outputting some helpful information just before (which is the less preferred way) OR by using xdebug and an IDE, which will allow you to run line by line, and see the exact state of each variable and such.
If I undestand correctly, $data $STH->execute($data); should be an array, even if value is one. So, you may try replacing that query with $STH->execute(array($data));
edited:
Change your lines to this:
$data = array($_POST["username"], $_POST["password"]);
$sql = "INSERT INTO users (username, password) value (?, ?)";
$STH = $DBH->prepare($sql);
$STH->execute($data);
Seems to me that you're not connected to your database properly... I had this error earlier today and it was for that reason. Either that or you have an incorrect string
I'm attempting to convert my script that I use for registering a user on my website from SQL to SQLi. I have some code and wondered if it was correct. Thanks.
$members = new mysqli("localhost", "root", "pass", "members");
$check = $members->prepare("select email from users where email = ?");
$check->bind_param('s', $_POST['r_email']);
$check->execute();
$check->store_result();
if ($check->num_rows > 0) {
echo "user already registered";
} else {
$user_id = mt_rand(100000000, 999999999);
$add_user = $members->prepare("insert into users(email, password, user_id) values(?, ?, ?)");
$add_user->bind_param('ssi', $r_email, $r_password, $user_id);
$r_email = $_POST['r_email'];
$r_password = md5($_POST['r_password']);
$add_user->execute();
$add_user->close();
}
$check->close();
$members->close();
Dealing with the error message you noted in your comment, 'All data must be fetched before a new statement prepare takes place'' ...
The error means exactly what it says: You're trying to prepare a new statement before you've fetched all the data from the previous statement. From the manual entry on mysqli::use_resultdocs ...
Used to initiate the retrieval of a result set from the last query
executed using the mysqli_real_query() function on the database
connection.
Either this or the mysqli_store_result() function must be called
before the results of a query can be retrieved, and one or the other
must be called to prevent the next query on that database connection
from failing.
Further, from the manual entry on mysqli_stmt::num_rowsdocs ...
Returns the number of rows in the result set. The use of
mysqli_stmt_num_rows() depends on whether or not you used
mysqli_stmt_store_result() to buffer the entire result set in the
statement handle.
You need to call mysqli_stmt::store_result before you check mysqli_stmt::num_rows (as described at mysqli_stmt::num-rows). After that, you need to close the statement using mysqli_stmt::close (mysqli_stmt::close).
Edit: Also, using md5 for password hashing (especially without a salt) is very insecure. Take a look at https://stackoverflow.com/a/1581919/140827 for suggestions on more secure solutions (bcrypt, salt, etc.)
I need to optimize a script for high performance so the question is how can I add MySQL multi-query to this code?
foreach($multiContent as $htmlContent) {
$email = urldecode($email['1']);
$user = $user['1'];
//store in db
$db->query("UPDATE eba_users
SET mail = '$email'
WHERE username = '$user'");
//echo "email is $email and user is $user\n";
}
//close if ($array_counter % 200
unset($array_counter);
unset($data);
}
If you're using mysqli or PDO already, you should be using prepared statements for your queries since they are supported. This will also have a slight increase in performance since the entire query doesn't need to be sent again to the DB server. However the biggest advantage is the increased security that prepared statements provide.
Other than this, try adding an index on username to speed this query up if you haven't already.
Edit:
If you want to do it all in one query, as you seem to suggest, you could also use ON DUPLICATE KEY UPDATE as mentioned as an answer to this question:
INSERT INTO eba_users (`username`, `mail`)
VALUES
('username1','$email1'),
('username2','$email2'),
('username3','$email3'),
('username4','$email4'),
....
('usernameN','$emailN'),
ON DUPLICATE KEY UPDATE `mail`=VALUES(mail);
However this may not be as fast as using prepared statements with a regular UPDATE.
Edit2: As requested, here is probably a close approximation of what you should be doing to bind the parameters in mysqli:
if ($stmt = $mysqli->prepare("UPDATE eba_users SET mail= ? WHERE username= ?")) {
/* loop through array of users */
foreach ($array as $username => $newemail) {
/* bind parameters for markers */
$stmt->bind_param("ss", $newemail, $username);
/* execute query */
$stmt->execute();
}
}
Of course this doesn't provide any sort of error messages in case this fails. For that, you can look into mysqli::error