I'm working on a school project creating a CMS for my portfolio site. I am having trouble getting my update function to work. I have a feeling it has something to do with the way I'm constructing my PDO Transaction. In my database I have a projects table, category table, and the associative content_category table. I'm able to insert my projects into those tables just fine. What I want to do is insert into my projects table then delete all records from the content_category table and finally insert the current category records into that associative table to complete the transaction. I do get my return statement "Project Updated" returned. But the tables aren't being updated. Any ideas anyone?
Here's the code:
This is a function in my Project class.
public function update(){
try {
$conn = getConnection();
$conn->beginTransaction();
$sql = "UPDATE project
SET project_title = :title,
project_description = :desc,
project_isFeatured = :feat,
project_mainImage = :image
WHERE project_id = :id";
$st = $conn->prepare($sql);
$st->bindValue(":id", $this->id, PDO::PARAM_INT);
$st->bindValue(":title", $this->title, PDO::PARAM_STR);
$st->bindValue(":desc", $this->description, PDO::PARAM_STR);
$st->bindValue(":feat", $this->isFeatured, PDO::PARAM_BOOL);
$st->bindValue(":image", $this->mainImage, PDO::PARAM_INT);
$st->execute();
$sql = "DELETE from content_category
WHERE content_id = :id";
$st = $conn->prepare($sql);
$st->bindValue("id", $this->id, PDO::PARAM_INT);
$st->execute();
$sql = "INSERT into content_category (content_id, cat_id)
VALUES (?,?)";
$st = $conn->prepare($sql);
foreach($this->categories as $key=>$value){
$st->execute(array(intval($projectID), intval($value)));
}
$conn->commit();
$conn = null;
return "Project updated";
}
catch(Exception $e) {
echo $e->getMessage();
$conn->rollBack();
return "Error... Unable to update!";
}
}
Your database engine for the tables needs to be INNODB. If you are using phpMyAdmin, it defaults to MyISAM. (I don't know if that will cause the updates not to go through or just the transaction line to be ignored. Edit: Pretty sure the documentation is saying that it will throw an error and not do anything if you beginTransaction on a myISAM)
In order to make sure you are not encountering a PDO error, you should set the PDO error reporting like this:
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
There are functions in PDO such as prepare() which will either return false or throw a PDOException depending on what error mode is set. This way, it will throw an exception and you'll definitely know if you are having a problem!
Also, if your database doesn't support transactions (like MyISAM), the beginTransaction() function will return false. So, maybe add a check in there like:
if($conn->beginTransaction()) {
// Do transaction here
} else {
echo("Unable to use transactions with this database.");
}
Oddly enough, according to PHP documentation, you would be getting an exception if your database doesn't support transactions...
Unfortunately, not every database supports transactions, so PDO needs to run in what is known as "auto-commit" mode when you first open the connection. Auto-commit mode means that every query that you run has its own implicit transaction, if the database supports it, or no transaction if the database doesn't support transactions. If you need a transaction, you must use the PDO::beginTransaction() method to initiate one. If the underlying driver does not support transactions, a PDOException will be thrown (regardless of your error handling settings: this is always a serious error condition).
Commit returns TRUE on success or FALSE on failure. You can check it. Also check for errorCode.
Related
More transaction questions!
What I have right now is a mess of strung-together queries, that are all manually reversed if any fail:
Code Block 1
$stmt1 = $db->prepare(...); // Update table1, set col=col+1
if($stmt1 = $db->execute(...)){
$stmt2 = $db->prepare(...); // Insert into table2, id=12345
if($stmt2 = $db->execute(...)){
$stmt3 = $db->prepare(...); // Select val from table3
if($stmt3 = $db->execute(...)){
$result = $stmt3->fetchAll();
if($result[0]['val'] == something){
$stmt4 = $db->prepare(...); // Update table4, set status=2
if($stmt4 = $db->execute(...)){
return true;
}else{
$stmt1 = $db->prepare(...); // Update table1, set col=col-1 (opposite of above)
$stmt1 = $db->execute(...);
$stmt2 = $db->prepare(...); // Delete from table2, where id=12345 (opposite of above)
$stmt2 = $db->execute(...);
return false;
}
}
return true;
}else{
$stmt1 = $db->prepare(...); // Update table1, set col=col-1 (opposite of above)
$stmt1 = $db->execute(...);
$stmt2 = $db->prepare(...); // Delete from table2, where id=12345 (opposite of above)
$stmt2 = $db->execute(...);
return false;
}
}else{
$stmt1 = $db->prepare(...); // Update table1, set col=col-1 (opposite of above)
$stmt1 = $db->execute(...);
return false;
}
}
It's a mess, difficult to debug, difficult to add on to, difficult to understand when the queries are large, and won't return all tables back to original state if the connection is lost mid-way through.
This same process is even worse when deleting a row, because everything in it needs to be stored - just in case the operation needs to be undone.
Now, I know most of this will still work when I port it over to a single transaction, but the one part I am unsure of is:
Code Block 2
$result = $stmt3->fetchAll();
if($result[0]['val'] == something){
... continue ...
}else{
... reverse operations ...
return false;
}
Because the results-gathering would take place before the commit() in the transaction. As follows:
Code Block 3
$db->beginTransaction();
try{
$stmt1 = $db->prepare(...);
$stmt1->execute();
$stmt2 = $db->prepare(...);
$stmt2->execute();
$stmt3 = $db->prepare(...);
$stmt3->execute();
$result = $stmt3->fetchAll();
if($result[0]['val'] == something){
$stmt4 = $db->prepare(...);
$stmt4->execute();
}else{
$db->rollBack();
return false;
}
$db->commit();
return true;
}catch(Exception $e){
$db->rollBack();
throw $e;
return false;
}
Will this work? Specifically, can I include the $result = $stmt3->fetchAll(); before the commit(), and then execute the conditional query?
Also, I'm not entirely sure on this, but do I require the $db->rollBack(); within the try block, if the code is exited (return false) before the commit()?
Your first question:
Specifically, can I include the $result = $stmt3->fetchAll(); before the commit(), and then execute the conditional query?
I see no reason why it should not work. A transaction behaves basically the same as operations without transactions - except that changes are only drafts. Any changes you make in the previous statements will be applied to a "working copy" valid for this single session only. For you it will appear completely transparent. However any changes will be rolled back if you do not commit them.
Also worth noting (emphasis mine):
In layman's terms, any work carried out in a transaction, even if it is carried out in stages, is guaranteed to be applied to the database safely, and without interference from other connections, when it is committed.
This can cause racing conditions.
Your second question:
Also, I'm not entirely sure on this, but do I require the $db->rollBack(); within the try block, if the code is exited (return false) before the commit()?
From the documentation it says:
When the script ends or when a connection is about to be closed, if you have an outstanding transaction, PDO will automatically roll it back.
Therefore you do not necessarily require to roll back manually as it will be done by the driver itself.
However note the following from the same source as well:
Warning PDO only checks for transaction capabilities on driver level. If certain runtime conditions mean that transactions are unavailable, PDO::beginTransaction() will still return TRUE without error if the database server accepts the request to start a transaction.
So be sure to check the compatibility beforehand!
A few notes
Do NOT begin a transaction in another transaction. This will commit the first transaction implicitely. See this comment.
Another note from the documentation:
Some databases, including MySQL, automatically issue an implicit COMMIT when a database definition language (DDL) statement such as DROP TABLE or CREATE TABLE is issued within a transaction. The implicit COMMIT will prevent you from rolling back any other changes within the transaction boundary.
Sometimes I need to know MySQL has found the record or no. I don't mean to know is there affected rows or not, just finding.
$db = database_system::connect();
$query = "UPDATE member SET username=?,password=? WHERE id=?";
$stmt = $db->prepare($query);
$stmt->bind_param("ssi",$this->username,$this->password,$this->id);
$stmt->execute();
$stmt->store_result();
if(!$stmt->affected_rows) throw new Exception("something about");
$stmt->close();
For example, php code above will throw exception if there is no affected record, but I wanna throw exception when there there is no such user (with that ID).
You need to check it in a separate query. The number of affected rows can return zero if the details haven't changed, too. (i.e. updating a valid user ID with the same username & password as it was before)
So best run a separate select for the user, first.
You can set an option to return the number of rows found, instead of the number of rows updated.
For PDO on PHP 5.3 and later:
$p = new PDO($dsn, $u, $p, array(PDO::MYSQL_ATTR_FOUND_ROWS => true));
For MySQLi:
mysqli_real_connect($dsn, $host, $u, $p, $dbname, $port, $socket, MYSQLI_CLIENT_FOUND_ROWS);
Does somebody know how i can check in PHP if an SQLite3 update query (prepared) was succesful or not?
Her my code...
$stmt = $project->prepare( 'UPDATE tasks set title=:title WHERE rowid=:rowid' );
$stmt->bindValue(':title', rtrim($_POST['title'],'<br>'), SQLITE3_TEXT);
$stmt->bindValue(':rowid', (int)$_POST['rel'], SQLITE3_INTEGER);
$result = $stmt->execute();
var_dump( $result );
This code is upodating my table. But "var_dump( $result )" return everytime an empty object. Even if i force an error by passing "rowid=non-existing-rowid".
Any ideas, how i can check my update query?
An UPDATE statements updates as many record as match the WHERE condition; this could be zero, one, or many records.
On the SQL level, all of this is considered a success.
If you want to find out how many records were affected, you can use the changes method of the database connection object.
In your case:
...
$result = $stmt->execute();
if ($result) {
echo 'Updated rows: ', $project->changes();
}
I'm trying to do a simple operation on a MySQL database: my contacts have their complete names on a column called first_name while the column last_name is empty.
So I want to take what's on the first_name column and split it on the first occurrence of a white space and put the first part on the first_name column and the second part on the last_name column.
I use the following code but it's not working:
$connection = new mysqli(DATABASE_SERVER, DATABASE_USERNAME, DATABASE_PASSWORD, DATABASE_NAME, DATABASE_PORT);
$statement = $connection->prepare("SELECT id, first_name FROM contacts");
$statement->execute();
$statement->bind_result($row->id, $row->firstName);
while ($statement->fetch()) {
$names = separateNames($row->firstName);
$connection->query('UPDATE contacts SET first_name="'.$names[0].'", last_name="'.$names[1].'" WHERE id='.$row->id);
}
$statement->free_result();
$statement->close();
$connection->close();
Can I use the $connection->query while having the statement open?
Best regards.
UPDATE
The $connection->query(...) returns FALSE and I get the following error:
PHP Fatal error: Uncaught exception 'Exception' with message 'MySQL Error - 2014 : Commands out of sync; you can't run this command now'
I changed the code to the following and worked:
$connection = new mysqli(DATABASE_SERVER, DATABASE_USERNAME, DATABASE_PASSWORD, DATABASE_NAME, DATABASE_PORT);
$result = $connection->query("SELECT id, first_name FROM contacts");
while ($row = $result->fetch_row()) {
$names = separateNames($row[1]);
$connection->query('UPDATE contacts SET first_name="'.$names[0].'", last_name="'.$names[1].'" WHERE id='.$row[0]);
}
$connection->close();
Can I use the $connection->query while having the statement open?
Yes. It will return a new result object or just a boolean depending on the SQL query, see http://php.net/mysqli_query - In your case of running an UPDATE query it will always return a boolean, FALSE if it failed, TRUE if it worked.
BTW, the Mysqli connection object is not the Mysqli statement object, so they normally do not interfere with each other (disconnecting might destroy/break some statements under circumstances, but I would consider this an edge-case for your question you can ignore for the moment).
I wonder why you ask actually. Maybe you should improve the way you do trouble-shooting?
I can only have one active statement at a given time, so I had to make one of the queries via the $connection->query() method.
As #hakre mentioned:
I still keep my suggestion that you should (must!) do prepared statements instead of query() to properly encode the update values
I opted to use the statement method for the update query, so the final working code is the following:
$connection = new mysqli(DATABASE_SERVER, DATABASE_USERNAME, DATABASE_PASSWORD, DATABASE_NAME, DATABASE_PORT);
$result = $connection->query("SELECT id, first_name FROM contacts");
$statement = $connection->prepare("UPDATE contacts SET first_name=?, last_name=? WHERE id=?");
while ($row = $result->fetch_row()) {
$names = separateNames($row[1]);
$statement->bind_param('ssi', $names[0], $names[1], $row[0]);
throwExceptionOnMySQLStatementError($statement, "Could not bind parameters", $logger);
$statement->execute();
throwExceptionOnMySQLStatementError($statement, "Could not execute", $logger);
}
$statement->free_result();
$statement->close();
$connection->close();
Thanks to all that gave their inputs, specially to #hakre that helped me to reach this final solution.
I have the following table:
ID: bigint autoinc
NAME: varchar(255)
DESCRIPTION: text
ENTRYDATE: date
I am trying to insert a row into the table. It executes without error but nothing gets inserted in database.
try {
$query = "INSERT INTO mytable (NAME, DESCRIPTION, ENTRYDATE) VALUES(?,?,?)";
$stmt = $conn->prepare($query);
$name= 'something';
$desc = 'something';
$curdate = "CURDATE()";
$stmt->bind_param("sss", $name, $desc, $curdate);
$stmt->execute();
$stmt->close();
$conn->close();
//redirect to success page
}
catch(Exception $e) {
print $e;
}
It runs fine and redirects to success page but nothing can be found inside the table. Why isn't it working?
What about replacing DESCTIPTION with DESCRIPTION inside the $query?
Edit
Just out of curiosity, I created a table called mytable and copy-pasted your code into a PHP script.
Here everything worked fine and rows got inserted, except that the binded parameter CURDATE() did not execute properly and the ENTRYDATE cell was assigned 0000-00-00.
Are you sure you are monitoring the same database and table your script is supposedly inserting to?
What happens when going with error_reporting(E_ALL); ?
Have you verified that the script actually completes the insertion?
The following appears to be working as expected:
error_reporting(E_ALL);
try {
$query = "INSERT INTO mytable (NAME, DESCRIPTION, ENTRYDATE) VALUES (?, ?, CURDATE())";
$stmt = $conn->prepare($query);
$name= 'something';
$desc = 'something';
$stmt->bind_param("ss", $name, $desc);
$stmt->execute();
if ($conn->affected_rows < 1) {
throw new Exception('Nothing was inserted!');
}
$stmt->close();
$conn->close();
//redirect to success page
}
catch(Exception $e) {
print $e->getMessage();
}
Are you sure there is no error? There seems to be a typo in your column name for example.
Note that PDO is extremely secretive about errors by default.
See How to squeeze error message out of PDO? on how to fix this.
Try preparing this query instead:
"INSERT INTO mytable (NAME, DESCRIPTION, ENTRYDATE) VALUES(?,?,CUR_DATE())"
And check the results of $stmt->execute(). It would have given you a warning that "CUR_DATE()" (sic) is not a valid DATE.
You can check if a statement was correctly executed by checking the return value of execute() and querying the errorInfo() method:
if (!$stmt->execute()) {
throw new Exception($stmt->errorInfo(), stmt->errorCode());
}
Be aware that upon failure, execute() does not throw an exception automagically. You'll have to check for successful operation and failure for yourself.
Is it possible that autocommit is OFF?
If so then you have to commit your insert like so
/* commit transaction */
$conn->commit();
Regards