I am trying PDO transactions for the first time. The below code doesnt work. the email address we are trying to insert has a duplicate so it should fail. It does give me an error. but the first insert get inserted into the DB and it doesnt roll back. I know rollback work cuase if i move PDO::rollBack into the Try{ before commit, it does roll back. I think the problem is its not catching the error, therefore not calling PDO::rollBack. Any ideas?
try {
PDO::beginTransaction();
$sql = "INSERT INTO .`tblUsersIDvsAgencyID` (`id`, `agency_id`) VALUES (NULL, :agencyID)";
$STH = $this->prepare($sql);
$STH->bindParam(':agencyID', $AgencyUser['agency_id']);
$STH->execute();
$userID = parent::lastInsertId();
$sql = "INSERT INTO `tblUsersEmailAddress` (`id`, `user_id`, `email_address`, `primary`, `created_ts`, `email_verified`) VALUES (NULL , :userID , :EmailAddress , '1', CURRENT_TIMESTAMP , '0' )";
$STH = $this->prepare($sql);
$STH->bindParam(':userID', $userID);
$STH->bindParam(':EmailAddress', $email_address);
$STH->execute();
PDO::commit();
echo 'Data entered successfully<br />';
}
catch(PDOException $e)
{
/*** roll back the transaction if we fail ***/
PDO::rollBack();
echo "failed";
}
PDO::beginTransaction() is not a static method. From your question, it looks like you're extending the PDO class. I wouldn't do that as I doubt you're adding anything significant to the base class. Instead, you should the set the PDO connection as a class property.
For example
class ParentClass
{
/**
* #var PDO
*/
protected $dbh;
public function __construct(PDO $dbh)
{
$this->dbh = $dbh;
// Make sure PDO is set to throw exceptions
$this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
}
class ChildClass extends ParentClass
{
public function insertStuff()
{
$this->dbh->beginTransaction();
try {
// do stuff
$this->dbh->commit();
} catch (PDOException $e) {
$this->dbh->rollBack();
throw $e;
}
}
}
I'm just going to start by quoting the docs:
Beware: Some MySQL table types (storage engines) do not support transactions. When writing transactional database code using a table type that does not support transactions, MySQL will pretend that a transaction was initiated successfully. In addition, any DDL queries issued will implicitly commit any pending transactions.
Your problem may be expected behavior. Additionally:
beginTransaction is not static. (I repeat Phil's statement that you should not be extending PDO).
Call $pdo->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
You never call closeCursor on your statement (this will often cause problems). (Phil points out that it is not explicitly necessary in this case. It is still best practice though).
Using bindParam does not yield any benefit for the userID variable as you are using it (a locally defined variable which is not re-used).
Related
This is the class I have created which I am using for the queries:
<?php
mysqli_report(MYSQLI_REPORT_INDEX | MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
class DBConnect {
private $dbcon;
private $paramquery;
private $result;
public function __construct() {
try {
$this->dbcon = mysqli_init();
mysqli_real_connect($this->dbcon, '127.0.0.1', '', '', '', 3306, '', MYSQLI_CLIENT_COMPRESS);
$this->paramquery = $this->dbcon->stmt_init();
} catch (mysqli_sql_exception $e) {
exit('Database Connection Failed');
}
}
public function dbquery($querysql, $querydata) {
try {
mysqli_ping($this->dbcon);
$this->paramquery->prepare($querysql);
array_walk($querydata, function(&$escval){$escval = mysqli_real_escape_string($this->dbcon, $escval);}); //Problem
call_user_func_array(array($this->paramquery, 'bind_param'), $querydata); //Problem
$this->paramquery->execute();
} catch (mysqli_sql_exception $e) {
exit('Database Query Failed');
}
$this->result = $this->paramquery->get_result(); // problem
if ($this->result) {
$drs = $this->result->fetch_array();
$this->result->free_result();
return $drs;
}
}
public function __destruct() {
if (($this->dbcon !== null) && ($this->paramquery !== null) && ($this->result !== null)) {
$this->paramquery->close();
$this->dbcon->close();
}
unset($this->result);
unset($this->paramquery);
unset($this->dbcon);
}
}
?>
The index.php file code is this:
<?php
require_once('connection.php');
$DBX = new DBConnect();
$DBX->dbquery('INSERT INTO `xathx_key` (`license`, `client`, `server`, `uniquex`) VALUES (?, ?, ?, ?)', array('ssss', '1', '3', '5', '7'));
var_dump($DBX);
unset($DBX)
?>
I am trying to do an INSERT query in this instance. And I want to get a success result or flag when the query is executed successfully. But in the var_dump of the object I get some irrelevant data and if I use echo I get an error that the object cannot be converted to a string. I just want to get a 0 for query execution failure, corruption or problem and a 1 for completion, success, ok status. When am I going wrong in the code?
EDIT: Can you guys just tell me what are the things that are wrong with this simple script? The main goal of this script is to connect to mysql server and execute all possible queries as fast as possible, as securely as possible.
Full Project Source: https://github.com/FSMySQL/PHP-FSMySQL
The main goal of this script is to connect to mysql server and execute all possible queries as fast as possible, as securely as possible.
The goal is a good one but the implementation could benefit from many improvements.
Disclaimer: there will be a lot of links to my own site because I am helping people with PHP for 20+ years and got an obsession with writing articles about most common issues.
The concept of error reporting
First of all, you need to change the concept of error reporting. Your exit() approach would be a nightmare for a programmer, as error messages are a vital source of information when something goes wrong. A programmer should go at any lengths trying to get the error message in the full detail. In my article, PHP error reporting, I do explain how to make error reporting both programmer- and user-friendly. In short, you shouldn't catch errors on the spot, but have a single dedicated place to report errors and exceptions, and then it could be easily configured depends on the current server's role.
Although, as suggested in the other answer, you could use a global try-catch block in your index.php file to act as such a global error handler, I would prefer a dedicated error handler script, as explained in the article above. It will make your code better organized and make index.php less bloated.
Besides, your idea of having "a true result return in insert query" contradicts with your intention to use exceptions. When one is using exceptions, there is no point to verify the immediate function's result. In case of error it will just bubble up to the error handler or a catch block, so, it will never reach the condition. A quick example:
function test() {
throw new Exception("Test");
return false;
}
$result = test();
if ($result === false) {
echo "false";
}
The code execution in this example will never reach the condition, therefore making your functions return false on error useless. Which, in turn, makes returning true on success superfluous. Just return a meaningful result but don't use it as flag: simply write your code without any conditions, as though everything is fine. Remember that you have your error handling code elsewhere that will be magically invoked in case of error.
Connection
As explained in my other article, How to connect properly using mysqli, there is a slight chance to reveal connection credentials in case of a connection error. To avoid even a possibility but keep the programmer informed we have to throw a brand new exception, however keeping the error information - so the stack trace will begin from the throw line, and thus contain no sensitive information.
Also, the connection code lacks an essential part - setting the correct charset. Although in MySQL 8 the correct charset is set by default, it's better to make it explicit.
Also, making a mysqli statement a class variable is a grave mistake that will lead to race condition errors. The only state that your class should keep is that related to the connection but not a single class variable should be used for a statement.
So let's rewrite your constructor based on the code from the article above:
public function __construct()
{
mysqli_report(MYSQLI_REPORT_INDEX | MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
try {
$this->dbcon = mysqli_init();
$this->dbcon->real_connect('127.0.0.1', '', '', '', 3306, '', MYSQLI_CLIENT_COMPRESS);
$this->dbcon->set_charset('utf8mb4');
} catch (\mysqli_sql_exception $e) {
throw new \mysqli_sql_exception($e->getMessage(), $e->getCode());
}
}
The dbquery function
The function is, frankly, weird. It's a strange mix between prepared statements and escaping. Let's rewrite it based on my mysqli helper function that actually utilizes mysqli prepared statements
public function dbquery($sql, $data = [], $types = "")
{
$this->dbcon->ping(); // not sure if it's necessary
$stmt = $this->dbcon->prepare($sql);
if ($data) {
$types = $types ?: str_repeat("s", count($data));
$stmt->bind_param($types, ...$data);
}
$stmt->execute();
return $stmt->get_result();
}
Now this function fulfills your desire for secure SQL queries
So finally we can rewrite your index.php
<?php
require_once('connection.php');
$DBX = new DBConnect();
$sql = 'INSERT INTO `xathx_key` (`license`, `client`, `server`, `uniquex`) VALUES (?, ?, ?, ?)';
$DBX->dbquery($sql, ['1', '3', '5', '7']);
Just as you learned above, there is no need for a "flag when the query is executed successfully". Just act as though there is always a success. In case of error it will appear without any conditions (an on a live site will be handled properly if you include an error handler script in your index).
In your DBConnect Class, you have try catch blocks. But your catch blocks are simply terminating the request using exit statement. Your Class should not be doing that.
Imagine you deploy this on production and for some reason the DB Connection Fails. In that case User will simply see a white screen with Message "Database Connection Failed" which would not look professional at all.
Instead your class should pass this information back to the index.php which called the method of this Class and let index.php handle the Error Message or Exception.
So I would make following changes to your code:
DBConnect Class should throw an Exception rather than terminating the execution of the program completely. Below is how the __contruct() should look.
public function __construct() {
try {
$this->dbcon = mysqli_init();
mysqli_real_connect($this->dbcon, '127.0.0.1', '', '', '', 3306, '', MYSQLI_CLIENT_COMPRESS);
$this->paramquery = $this->dbcon->stmt_init();
} catch (mysqli_sql_exception $e) {
//exit('Database Connection Failed'); Commented this out.
//Throw the Exception Here. This will then be passed to the calling code.
throw $e;
}
}
You will need to change the other methods accordingly.
In your index.php File, you should be looking to catch the above exception. So you should move your code in a Try Catch Block to catch that exception.
require_once('connection.php');
try {
$DBX = new DBConnect();
$DBX->dbquery('INSERT INTO `xathx_key` (`license`, `client`, `server`, `uniquex`) VALUES (?, ?, ?, ?)', array('ssss', '1', '3', '5', '7'));
} catch (Exception $e) {
$message = 'Caught exception: ', $e->getMessage() . "\n";
//Display this Message to User in an appropriate way.
//Write to Error Log
}
//var_dump($DBX);
//unset($DBX)
So this will catch the Exception in case the DB Connection Fails as well as when the Insert Query Fails. You can write the exception to the logs so that you can check them later and you can display any appropriate error message to user based on the exception caused.
You could read more on Exceptions in PHP Manual
You have a problem with
$this->result = $this->paramquery->get_result();
because mysqli_stmt::get_result returns a resultset for successful SELECT queries, or FALSE for other DML queries or on failure.
Other DML-queries are INSERT, UPDATE, DELETE. And that's exactly what you have in the example.
To resolve your problem you can modify the class by adding some extra-checks to $mysqli->errno:
$this->result = $this->paramquery->get_result();
if ($this->result) {
...
}
if ($this->paramquery->errno !== 0) { // we have some real error
exit('Database Query Failed');
}
// we have DML-query (INSERT, UPDATE, DELETE)
// and we can return number of affected rows (if it's necessary)
return $this->paramquery->affected_rows;
P.S. I agree with this comment and I think that your class should be used for educational purposes only because it has multiple serious flaws.
<?php
$sql = "insert into user (firstname, lastname) values (:firstname, :lastname)";
$arg = array("John", "Doe");
$stmt = pdo($sql, $arg);
$id = $pdo->lastInsertId();
print $id;
function pdo($sql, $args = NULL){
$dsn = "mysql:host=localhost;dbname=db;charset=utf8mb4";
try {
$pdo = new \PDO($dsn, "user", "123");
} catch (\PDOException $e) {
throw new \PDOException($e->getMessage(), (int)$e->getCode());
}
if (!$args){
return $pdo->query($sql);
}
$stmt = $pdo->prepare($sql);
$stmt->execute($args);
return $stmt;
}
?>
I use a wrapper to call database queries, this has always worked well.
Except for today when I need to get the last insert ID. The error is "Undefined variable pdo"
I see why there is an error message, but not sure what is the solution while at the same time keeping the wrapper function?
You might just want to have a function create the DB connection and let the application code use the PDO API directly. That would be simpler than trying to wrap all the functionality of the PDO API. It would get complicated to do that.
If you really need a wrapper around your PDO connection, then you may want to create a class with different methods operating on the same connection. The class could have a special insert method that returns the inserted ID as well as a fetch method to retrieve results.
I want to fetch some information from my mysql database in a class, so I'm passing in the PDO object into a __construct function, and working from there. However, what's an elegant way of checking to see if the PDO object was correctly created, and that the connection is open when the Table class is instantiated?
class Table{
public function __construct(PDO $db, $week){
try{
$query = $db -> query ("SELECT * FROM `table1` where `day` = 'monday'");
}
catch(PDOExeption $e){
echo 'error: '. $e->getMessage();
//die();
}
}
}
I don't think this code does what I want.
there is no need for such a verification.
If PDO object was created incorrectly and no connection were opened, an exception would be thrown, and thus script will be halted before calling any class' method.
In the past I would just create a class to connect to a database, and then run a bunch of methods to run queries.. like so:
class connectDB
{
public $db_host = "asdf.db.asdf.hostedresource.com";
public $db_name = "asdf";
public $db_user = "asdf";
public $db_pass = "asdf!1";
public $result;
function setDB_Host($value){
$this->db_host=$value;
}
function setDB_name($value){
$this->db_name=$value;
}
function setDB_user($value){
$this->db_user=$value;
}
function setDB_pass($value){
$this->db_pass=$value;
}
function makeConnection()
{
$connection = mysql_connect($this->db_host, $this->db_user, $this->db_pass) or die
("Unable to connect!");
mysql_select_db($this->db_name) or die(mysql_error());
}
function setQuery($query)
{
$this->result = mysql_query($query) or die(mysql_error());
}
class video
{
public $sequence;
public $fileName;
public $vidTitle;
public $vidCat;
public $thumbName;
function addVideo($sequence, $fileName, $vidTitle, $vidCat, $thumbName)
{
$this->connect-> setQuery("SELECT COUNT(id) AS numrows FROM vids WHERE vidCat = '$vidCat'");
$row = mysql_fetch_array($this->connect->result);
$sequence = $row['numrows'] + 1;
$this->connect->setQuery("INSERT INTO vids (sequence, fileName, vidTitle, vidCat, thumbName) VALUES ('$sequence', '$fileName', '$vidTitle', '$vidCat', '$thumbName') ");
}
function addKeypoints($keypoints, $mins, $secs)
{
$v_id = mysql_insert_id();
for ($i=0; $i<sizeof($keypoints); $i++)
{
$totalsecs = ($mins[$i]*60) + $secs[$i];
$this->connect->setQuery("INSERT INTO keypoints (v_id, totalsecs, keypoints, mins, secs) VALUES ('$v_id', '$totalsecs', '$keypoints[$i]', '$mins[$i]', '$secs[$i]') ");
}
}
without wanting to read all that. Basically I just run a bunch of methods that access my first class, and run queries. I don't understand how this would work in a PDO context. PDO should be making this sort of thing easier since it's OOP based.. correct?
$hostname = "aaa.db.7149468.aaa.com";
$username = "coolcaaaaodez";
$password = "aaaa";
try
{
$dbh = new PDO("mysql:host=$hostname;dbname=coolcodez", $username, $password);
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $dbh->prepare("INSERT INTO links (link, cool, difficulty) values (:link, :cool, :difficulty)");
$stmt->bindParam(':link', $_POST['link']);
$stmt->bindParam(':cool', $_POST['cool']);
$stmt->bindParam(':difficulty', $_POST['difficulty']);
if(isset($_POST['submit-links']))
{
$stmt->execute();
echo "row added <br />";
}
}
catch(PDOException $e)
{
echo $e->getMessage();
}
basically what i'm asking is.. the try / catch thing really trips me up. Does just the connection itself need to be put into a try / catch block, and then I could prepare queries within each of my methods belonging to a class? Or does each sequence of events (database connection, query, results, etc) need to be stuck within a try / catch block. PDO is a little fancy for my needs, but I'm trying to learn, and I'd like to know the best general way to write it when a large number of queries are involved.
PDO should be making this sort of thing easier since it's OOP based.. correct?
Yes... in a way.
Being fine instrument, OOP requires some skill in using - only then it will make things easier indeed. Otherwise, it will make things quite harder.
All the code you need, actually, is
if(isset($_POST['submit-links']))
{
require 'pdo.php';
$sql = "INSERT INTO links (link, cool, difficulty) values (?, ?, ?)";
$data = array($_POST['link'], $_POST['cool'], $_POST['difficulty']);
$dbh->prepare($sql)->execute($data);
}
The rest of your code is superfluous for two reasons:
surely DB connection code should be stored in a separate file and included in all other files, instead of being duplicated each time. A good example for the connection code can be found in the PDO tag wiki
This try-catch stuff, which indeed confuse many inexperienced developers. In fact, you don't need it here at all - or, at least, not in this form. Just because PHP can handle the error itself (and do it better than average PHP user, to be honest). So, you can just omit this try/catch stuff. Further reading: The (im)proper use of try..catch.
Think about it this way - for any error that PDO might encounter, it will throw an exception. WHen using the mysql_ functions, they either just return FALSE/NULL or give you a notice, a warning or even a fatal error. With PDO, you do not get errors, you get exceptions. So you can catch something that mysql_ would have caused to stop the script all together.
So in the end, you probably want to have a try/catch block around every call that could result in some kind of error, and you know how to handle it. If that disturbs your eye, you can write a short class extending PDO that would do the try/catch logic inside and return you either the results or FALSE/NULL as would mysql_ functions do.
I'm preparing my sql statements and binding parameters to it and if something goes wrong i catch them in a catch block. But, i want to see which query i processed. So i extended the PDO class (found it on the internet , not exactly sure what i'm doing or where the ATTR_STATEMENT_CLASS is really for. So this is the code of the extention:
class PDOTester extends PDO {
public function __construct($dsn, $username = null, $password = null, $driver_options = array()) {
parent::__construct($dsn, $username, $password, $driver_options);
$this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('PDOStatementTester', array($this)));
}
}
class PDOStatementTester extends PDOStatement {
protected $connection;
protected function __construct(PDO $connection)
{
$this->connection = $connection;
}
public function execute() {
try {
parent::execute();
} catch (PDOException $e) {
$e->errorInfo[3] = $this->queryString;
echo '<pre>';
var_dump($this);
var_dump($this->connection);
echo '</pre>';
throw $e;
}
}
}
But, here comes the problem, i used named parameters to bind values to it, but when i see the SQl query then i see the named parameters and not the values!
For example, i use it like:
try {
$sql = "INSERT INTO table (column1, column2) VALUES (:column1, :column2)";
$stmt = $db->preapre($sql);
$stmt->bindParam(':column1', $column1, PDO::PARAM_STR);
$stmt->bindParam(':column2', $column2, PDO::PARAM_INT);
$stmt->execute();
} catch (PDOException $e) {
echo $e->errorInfo[3];
}
But then i see as values :column1, :column2, instead of the actual variables!
Can please someone help me out (for example provide a better modification of the extension i use)?
PHP Version 5.2.17, MySQL version 5.0.92
As far as I know, there is no way to see the actual query when using prepared statements
Your best option would be to turn MySQL's general log option.
Run the following in MySQL terminal (or any other preferred program / app you use to control MySQL)
SET GLOBAL general_log = ON;
SET GLOBAL general_log_file = 'path_to_file.log';
Run your query, inspect the file.
They really didn't need to extend the PDO class to do what they did. Simply create a new instance then add (or remove) that attribute after creation rather than during.
That being said, there's a built-in way of outputting the debug information regarding a statement, and that's using PDOStatement->debugDumpParams. This won't let you see the compiled version (that's done database-side for databases that support it) but it will let you see both the query and that bound parameters easily.
A note of caution, don't enable this on a live system. It could give attackers of your website a big clue into how your system is set up and what they can do it take over (and destroy or otherwise ruin).