I discovered PDO and transactions few days ago and they're great. Today I wrote my first transaction but it didn't end as expected. To test it I put a wrong inexistent table in one of the two queries and, independently of which one, the other (correct) one is committed anyway.
I read the syntax structure on some websites and it seems to be correct, but maybe the error is right there. The tables are InnoDB. Or better, phpMyAdmin, into the db overview's table, reports MyISAM on the last summary row but the column "type" of each table's row reports InnoDB; I think this could be 'cause MyISAM is the default type on the server, is that right?
Here's the code:
$conn = new PDO($db->getDsn(), $db->getUsername(), $db->getPassword());
try {
$conn->beginTransaction();
$stmt = $conn->prepare('INSERT INTO trips (country_code, year, img, showX) VALUES (:country_code, :year, :img, :showX)');
$stmt->execute(array(':country_code' => strtolower($country_code), ':year' => $year, ':img' => $rename_response, ':showX' => $show_hide));
$tripID = $conn->lastInsertId();
$stmt = $conn->prepare('INSERT INTO trips_multilang (tripID, lang, country_name) VALUES (:tripID, :lang, :country_name)');
$stmt->execute(array(':tripID' => $tripID, ':lang' => $trip_lang, ':country_name' => strtolower($country_name)));
$conn->commit();
} catch (PDOException $e) {
$conn->rollBack();
die($e->getMessage());
}
Add to your $db class another method, $db->getOptions(), that returns an array
return [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION];
and then call PDO as
$conn = new PDO($db->getDsn(), $db->getUsername(), $db->getPassword(), $db->getoptions());
After that your code will start to catch up.
For now you are catching exceptions for nought, as none actually thrown.
On a side note, you must catch Exception, not PDOException and also you must re-throw an exception in case of error instead of dying out.
Related
I'm working on a custom CMS using PHP OOP. Basically I have made a class that can add a new row to db.
My Class:
<?php
class Navigation
{
private $db;
public function __construct()
{
$this->db = new Connection();
$this->db = $this->db->dbConnect();
}
public function NewMenu($menu_name,$menu_numbers)
{
if (!empty($menu_name) && !empty($menu_numbers)) {
$sql = "INSERT INTO menu_nav "
. "(menu_name, menu_items) VALUES (?, ?)";
$ins = $this->db->prepare($sql);
$ins->bindParam(1,$menu_name);
$ins->bindParam(2,$menu_numbers);
$ins->execute();
} else {
header("Location: maint/php/includes/errors/009.php");
exit();
}
}
}
This class works fine but the problem is that I don't know how to check if the menu_name exists already in the table or not. And if yes ,it should receive the error message that "Data can not be inserted to db" for example. So if you know how to do this feature in PHP OOP ,please let me know cause I really need it.
There are two ways for this to work. For the first method you'll need to have a primary/unique key, which would cause the query to fail. Set PDO up with throwing exceptions on failures, and you can check the exceptions message for "duplicate key".
To do this you'd need to change the connection options, like this[1]:
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$opt = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
$pdo = new PDO($dsn, $user, $pass, $opt);
Then pdo::exec() will throw an exception instead of just returning false.
For the second method you need to query the DB first, and then insert it if 0 rows are returned. The problem with this approach is that it can trigger race conditions. In which a duplicate row is inserted between the check and the INSERT query, by another, simultaneously running, script.
That's why I recommend doing it in the first manner. As you'd need to do it anyway, to catch the race conditions.
PS: As an alternative way of doing method 1, you could use ON DUPLICATE KEY in the SQL query. If you wanted to insert or update the data.
[1]: Copied from here https://phpdelusions.net/pdo
so you should check if row exists in table, and insert the data only if row doesn't exist(if i correctly understand your question). at first select the row with given menu name $ins = $this->db->prepare("SELECT FROM menu_nav (menu_name, menu_items) VALUES (?, ?)"); and then check if you got any data. go here , you will get the clue)
My understanding is the InnoDB is now the default engine for MySQL. With that knowledge, I am beginning to delve into transactions.
Here is what I have so far...
try{
$pdo->beginTransaction();
$stmnt = $pdo->prepare ("delete from playing where uniq = :uniq");
$stmnt->bindParam (':uniq',$uniq);
$stmnt->execute();
$stmnt = $pdo->prepare ("insert into removals (playdate, time, vid) values (:playdate, :time, :vid");
$stmnt->bindParam (":playdate",$playdate);
$stmnt->bindParam (":time", $time);
$stmnt->bindParam (":vid", $vid);
$stmnt->execute();
$pdo->commit();
echo "1"; // success
return;
}
catch (PDOException $e){
$pdo->rollback();
echo $e->getMessage();
}
This is called by jQuery with a result of "1" indicating a success.
If I understand this correctly, if bot statements execute successfully, they will both be "committed" however it either fails, no database activity will take place and an error message will be generated detailing the first statement execution that fails.
My real question is whether the begin transaction and commit should reside within or outside the try...catch block.
Thanks,
-dmd-
For readability and cleanliness, yes it should be inside the try block. But it really does not matter. It just declares what to commit or rollback if you call roll back.
try {
$query = 'UPDATE keywords SET value = :keyvalue WHERE keyword = :keyname AND document_id = :docId';
$pdo = _openConnection();
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->beginTransaction();
$pdoStatement = $pdo->prepare($query);
foreach ($keywords as $keyname => $keyval) {
$pdoStatement->bindParam(':docId', $id, PDO::PARAM_STR);
$pdoStatement->bindParam(':keyname', $keyname, PDO::PARAM_STR);
$pdoStatement->bindParam(':keyvalue', $keyval, PDO::PARAM_STR);
$pdoStatement->execute();
}
$res = $pdo->commit();
var_dump('retornando true', $res);
return true;
} catch (PDOException $e) {
$pdo->rollBack();
echo $e->getMessage();
return false;
}
The sentence updates a given row identified by KEYWORDNAME and DOCUMENT_ID.
I am sending a wrong keyword name (non-existent) but existent document id.
Shouldn't it throw an exception for record not found and rollback the operation?
It is always succeeding and returning true (also I see the var_dump)
PS: this is the last portion of the code.
As far as the database (and thus PDO) is concerned - this is not an error. You performed an update statement, and it successfully updated 0 rows.
If you want to handle this as an error, you'd have to do it manually:
$res = $pdo->commit();
if ($pdo->rowCount() == 0) {
# some exception treatment
}
No, you are simply updating 0 rows when the WHERE condition is not matched. Updating, selecting, etc. 0 rows is not an error, these are normal database operations.
Check the rowCount() to see how many rows are updated and handle that accordingly.
A query which doesn't match any records is NOT an error. It's just an empty result set, which is a perfectly valid result.
The only time you'd get an exception from the query is if there was an actual problem with the query itself, the connection to the db, etc... e.g. A syntax error in the DB, connection failure, permission denied on whatever table(s) you're accessing, etc...
I am trying to create a simple PHP page that describes the tables of my DB so I can always find a table/column name or data type that I am looking for (a sort of poor man's schema if you will).
I have done this in the past with a Sybase DB but our new DB will be SQL Server so I am trying to reproduce it using that DB.
try {
$conn = new PDO("odbc:Driver={SQL Native Client};Server=servername,1433;Database=dbname;Uid=user;Pwd=password;");
$conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
} catch(PDOException $e) {
echo 'Connection failed: ' . $e->getMessage();
}
try {
$query = $conn->query("execute sp_help tablename");
} catch(PDOException $e) {
echo "Error: ".$e->getMessage();
}
while($result = $query->fetch(PDO::FETCH_ASSOC)) {
print_r($result);
}
But it's as if I only get the first result (it doesn't loop). I was expecting something like the example given here SQL Server FAQ but all I get is the first row (name, owner, type, date_created) the result set does not go on to give the column names etc (which is what I'm really after).
Any ideas?
Update: According to TechNet if the argument given is a user table, it should return the columns, but in this case it does not. All I get is...
Array ( [Name] => TableName [Owner] => dbo [Type] => user table [Created_datetime] => 2013-07-15 23:26:43.377 )
The only way I can think of is create procedure on the sybase side that could call sp_help and would return require data. That procedure could be used as you need.
I have a set of data stored in variables, and I want a page to write that data to a MySQL database, I'd like to include the time of insertion, here's my method:
$username="username"; $password="password";
try {
$pdo = new PDO('mysql:host=localhost; dbname=db01', $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo->prepare('INSERT INTO table01
(
Time,
variable1,
variable2,
)
VALUES
(
:Time,
:variable1,
:variable2,
)');
$stmt->execute(array(
':Time' => NOW(),
':variable1' => $var1,
':variable2' => $var2,
));
echo $stmt->rowCount(); // 1
} catch(PDOException $e) {
echo 'Error: ' . $e->getMessage();
}
On loading this page, the css is stripped from the page, I get a sort of garbled output of what the page should look like (white screen, plain text some images), additionally on checking the database nothing has been written to it.
Without the Time variable in there, all works perfectly.
Any advice? Thanks in advance
Sorry, took me moment to re-read that. You are using the nysql function now() to do the work, and as such you don't need to set it as a param and therefore don't need to bind it at all. Just write it into your query.
$stmt = $pdo->prepare('INSERT INTO table01
(
Time,
variable1,
variable2,
)
VALUES
(
now(),
:variable1,
:variable2,
)');
$stmt->execute(array(
':variable1' => $var1,
':variable2' => $var2,
));
Edit Re Comment
The : in a query denotes it as a parameter in a prepared query. That means you must then bind it via the bind() command. However if you are inserting the data from within the database (such as using a built in function, or pulling the data in from another row) you don't need to declare it in the initial query with : and therefore you can't pass it to the query in the bound params array.