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.
Before moving to PDO, I created SQL queries in PHP by concatenating strings. If I got database syntax error, I could just echo the final SQL query string, try it myself on the database, and tweak it until I fixed the error, then put that back into the code.
Prepared PDO statements are faster and better and safer, but one thing bothers me: I never see the final query as it's sent to the database. When I get errors about the syntax in my Apache log or my custom log file (I log errors inside a catch block), I can't see the query that caused them.
Is there a way capture the complete SQL query sent by PDO to the database and log it to a file?
You say this :
I never see the final query as it's
sent to the database
Well, actually, when using prepared statements, there is no such thing as a "final query" :
First, a statement is sent to the DB, and prepared there
The database parses the query, and builds an internal representation of it
And, when you bind variables and execute the statement, only the variables are sent to the database
And the database "injects" the values into its internal representation of the statement
So, to answer your question :
Is there a way capture the complete
SQL query sent by PDO to the database
and log it to a file?
No : as there is no "complete SQL query" anywhere, there is no way to capture it.
The best thing you can do, for debugging purposes, is "re-construct" an "real" SQL query, by injecting the values into the SQL string of the statement.
What I usually do, in this kind of situations, is :
echo the SQL code that corresponds to the statement, with placeholders
and use var_dump (or an equivalent) just after, to display the values of the parameters
This is generally enough to see a possible error, even if you don't have any "real" query that you can execute.
This is not great, when it comes to debugging -- but that's the price of prepared statements and the advantages they bring.
Looking in the database log
Although Pascal MARTIN is correct that PDO doesn't send the complete query to the database all at once, ryeguy's suggestion to use the DB's logging function actually allowed me to see the complete query as assembled and executed by the database.
Here's how:
(These instructions are for MySQL on a Windows machine - your mileage may vary)
In my.ini, under the [mysqld] section, add a log command, like log="C:\Program Files\MySQL\MySQL Server 5.1\data\mysql.log"
Restart MySQL.
It will start logging every query in that file.
That file will grow quickly, so be sure to delete it and turn off logging when you're done testing.
Sure you can debug using this mode {{ PDO::ATTR_ERRMODE }}
Just add new line before your query then you will show the debug lines.
$db->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING );
$db->query('SELECT *******');
Probably what you want to do is use debugDumpParams() on the statement handle. You can run that any time after binding values to the prepared query (no need to execute() the statement).
It doesn't build the prepared statement for you, but it will show your parameters.
An old post but perhaps someone will find this useful;
function pdo_sql_debug($sql,$placeholders){
foreach($placeholders as $k => $v){
$sql = preg_replace('/:'.$k.'/',"'".$v."'",$sql);
}
return $sql;
}
Here's a function to see what the effective SQL will be, adpated from a comment by "Mark" at php.net:
function sql_debug($sql_string, array $params = null) {
if (!empty($params)) {
$indexed = $params == array_values($params);
foreach($params as $k=>$v) {
if (is_object($v)) {
if ($v instanceof \DateTime) $v = $v->format('Y-m-d H:i:s');
else continue;
}
elseif (is_string($v)) $v="'$v'";
elseif ($v === null) $v='NULL';
elseif (is_array($v)) $v = implode(',', $v);
if ($indexed) {
$sql_string = preg_replace('/\?/', $v, $sql_string, 1);
}
else {
if ($k[0] != ':') $k = ':'.$k; //add leading colon if it was left out
$sql_string = str_replace($k,$v,$sql_string);
}
}
}
return $sql_string;
}
No. PDO queries are not prepared on the client side. PDO simply sends the SQL query and the parameters to the database server. The database is what does the substitution (of the ?'s). You have two options:
Use your DB's logging function (but even then it's normally shown as two separate statements (ie, "not final") at least with Postgres)
Output the SQL query and the
paramaters and piece it together
yourself
almost nothing was said about error displaying except check error logs,
but there's a rather helpful functionality:
<?php
/* Provoke an error -- bogus SQL syntax */
$stmt = $dbh->prepare('bogus sql');
if (!$stmt) {
echo "\PDO::errorInfo():\n";
print_r($dbh->errorInfo());
}
?>
(source link)
it is clear that this code can be modified to be used as exception message
or any other kind of error handling
for example you have this pdo statement :
$query="insert into tblTest (field1, field2, field3)
values (:val1, :val2, :val3)";
$res=$db->prepare($query);
$res->execute(array(
':val1'=>$val1,
':val2'=>$val2,
':val3'=>$val3,
));
now you can get the executed query by defining an array like this :
$assoc=array(
':val1'=>$val1,
':val2'=>$val2,
':val3'=>$val3,
);
$exQuery=str_replace(array_keys($assoc), array_values($assoc), $query);
echo $exQuery;
Searching internet I found this as an acceptable solution. A different class is used instead of PDO and PDO functions are called through magic function calls. I am not sure this creates serious performance problems. But it can be used until a sensible logging feature is added to PDO.
So as per this thread, you can write a wrapper for your PDO connection which can log and throws an exception when you get a error.
Here is simple example:
class LoggedPDOSTatement extends PDOStatement {
function execute ($array) {
parent::execute ($array);
$errors = parent::errorInfo();
if ($errors[0] != '00000'):
throw new Exception ($errors[2]);
endif;
}
}
so you can use that class instead of PDOStatement:
$this->db->setAttribute (PDO::ATTR_STATEMENT_CLASS, array ('LoggedPDOStatement', array()));
Here a mentioned PDO decorator implementation:
class LoggedPDOStatement {
function __construct ($stmt) {
$this->stmt = $stmt;
}
function execute ($params = null) {
$result = $this->stmt->execute ($params);
if ($this->stmt->errorCode() != PDO::ERR_NONE):
$errors = $this->stmt->errorInfo();
$this->paint ($errors[2]);
endif;
return $result;
}
function bindValue ($key, $value) {
$this->values[$key] = $value;
return $this->stmt->bindValue ($key, $value);
}
function paint ($message = false) {
echo '<pre>';
echo '<table cellpadding="5px">';
echo '<tr><td colspan="2">Message: ' . $message . '</td></tr>';
echo '<tr><td colspan="2">Query: ' . $this->stmt->queryString . '</td></tr>';
if (count ($this->values) > 0):
foreach ($this->values as $key => $value):
echo '<tr><th align="left" style="background-color: #ccc;">' . $key . '</th><td>' . $value . '</td></tr>';
endforeach;
endif;
echo '</table>';
echo '</pre>';
}
function __call ($method, $params) {
return call_user_func_array (array ($this->stmt, $method), $params);
}
}
To log MySQL in WAMP, you will need to edit the my.ini (e.g. under wamp\bin\mysql\mysql5.6.17\my.ini)
and add to [mysqld]:
general_log = 1
general_log_file="c:\\tmp\\mysql.log"
Here is a function I made to return a SQL query with "resolved" parameters.
function paramToString($query, $parameters) {
if(!empty($parameters)) {
foreach($parameters as $key => $value) {
preg_match('/(\?(?!=))/i', $query, $match, PREG_OFFSET_CAPTURE);
$query = substr_replace($query, $value, $match[0][1], 1);
}
}
return $query;
$query = "SELECT email FROM table WHERE id = ? AND username = ?";
$values = [1, 'Super'];
echo paramToString($query, $values);
Assuming you execute like this
$values = array(1, 'SomeUsername');
$smth->execute($values);
This function DOES NOT add quotes to queries but does the job for me.
I've created a modern Composer-loaded project / repository for exactly this here:
pdo-debug
Find the project's GitHub home here, see a blog post explaining it here. One line to add in your composer.json, and then you can use it like this:
echo debugPDO($sql, $parameters);
$sql is the raw SQL statement, $parameters is an array of your parameters: The key is the placeholder name (":user_id") or the number of the unnamed parameter ("?"), the value is .. well, the value.
The logic behind: This script will simply grad the parameters and replace them into the SQL string provided. Super-simple, but super-effective for 99% of your use-cases. Note: This is just a basic emulation, not a real PDO debugging (as this is not possible as PHP sends raw SQL and parameters to the MySQL server seperated).
A big thanks to bigwebguy and Mike from the StackOverflow thread Getting raw SQL query string from PDO for writing basically the entire main function behind this script. Big up!
How to debug PDO mysql database queries in Ubuntu
TL;DR Log all your queries and tail the mysql log.
These directions are for my install of Ubuntu 14.04. Issue command lsb_release -a to get your version. Your install might be different.
Turn on logging in mysql
Go to your dev server cmd line
Change directories cd /etc/mysql. You should see a file called my.cnf. That’s the file we’re gonna change.
Verify you’re in the right place by typing cat my.cnf | grep general_log. This filters the my.cnf file for you. You should see two entries: #general_log_file = /var/log/mysql/mysql.log && #general_log = 1.
Uncomment those two lines and save via your editor of choice.
Restart mysql: sudo service mysql restart.
You might need to restart your webserver too. (I can’t recall the sequence I used). For my install, that’s nginx: sudo service nginx restart.
Nice work! You’re all set. Now all you have to do is tail the log file so you can see the PDO queries your app makes in real time.
Tail the log to see your queries
Enter this cmd tail -f /var/log/mysql/mysql.log.
Your output will look something like this:
73 Connect xyz#localhost on your_db
73 Query SET NAMES utf8mb4
74 Connect xyz#localhost on your_db
75 Connect xyz#localhost on your_db
74 Quit
75 Prepare SELECT email FROM customer WHERE email=? LIMIT ?
75 Execute SELECT email FROM customer WHERE email='a#b.co' LIMIT 5
75 Close stmt
75 Quit
73 Quit
Any new queries your app makes will automatically pop into view, as long as you continue tailing the log. To exit the tail, hit cmd/ctrl c.
Notes
Careful: this log file can get huge. I’m only running this on my dev server.
Log file getting too big? Truncate it. That means the file stays, but the contents are deleted. truncate --size 0 mysql.log.
Cool that the log file lists the mysql connections. I know one of those is from my legacy mysqli code from which I'm transitioning. The third is from my new PDO connection. However, not sure where the second is coming from. If you know a quick way to find it, let me know.
Credit & thanks
Huge shout out to Nathan Long’s answer above for the inspo to figure this out on Ubuntu. Also to dikirill for his comment on Nathan’s post which lead me to this solution.
Love you stackoverflow!
The problem I had with the solution to catch PDO exemptions for debuging purposes is that it only caught PDO exemptions (duh), but didn't catch syntax errors which were registered as php errors (I'm not sure why this is, but "why" is irrelevant to the solution). All my PDO calls come from a single table model class that I extended for all my interactions with all tables... this complicated things when I was trying to debug code, because the error would register the line of php code where my execute call was called, but didn't tell me where the call was, actually, being made from. I used the following code to solve this problem:
/**
* Executes a line of sql with PDO.
*
* #param string $sql
* #param array $params
*/
class TableModel{
var $_db; //PDO connection
var $_query; //PDO query
function execute($sql, $params) {
//we're saving this as a global, so it's available to the error handler
global $_tm;
//setting these so they're available to the error handler as well
$this->_sql = $sql;
$this->_paramArray = $params;
$this->_db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->_query = $this->_db->prepare($sql);
try {
//set a custom error handler for pdo to catch any php errors
set_error_handler('pdoErrorHandler');
//save the table model object to make it available to the pdoErrorHandler
$_tm = $this;
$this->_query->execute($params);
//now we restore the normal error handler
restore_error_handler();
} catch (Exception $ex) {
pdoErrorHandler();
return false;
}
}
}
So, the above code catches BOTH PDO exceptions AND php syntax errors and treats them the same way. My error handler looks something like this:
function pdoErrorHandler() {
//get all the stuff that we set in the table model
global $_tm;
$sql = $_tm->_sql;
$params = $_tm->_params;
$query = $tm->_query;
$message = 'PDO error: ' . $sql . ' (' . implode(', ', $params) . ") \n";
//get trace info, so we can know where the sql call originated from
ob_start();
debug_backtrace(); //I have a custom method here that parses debug backtrace, but this will work as well
$trace = ob_get_clean();
//log the error in a civilized manner
error_log($message);
if(admin(){
//print error to screen based on your environment, logged in credentials, etc.
print_r($message);
}
}
If anyone has any better ideas on how to get relevant info to my error handler than setting the table model as a global variable, I would be happy to hear it and edit my code.
this code works great for me :
echo str_replace(array_keys($data), array_values($data), $query->queryString);
Don't forget to replace $data and $query by your names
i use this class to debug PDO (with Log4PHP)
<?php
/**
* Extends PDO and logs all queries that are executed and how long
* they take, including queries issued via prepared statements
*/
class LoggedPDO extends PDO
{
public static $log = array();
public function __construct($dsn, $username = null, $password = null, $options = null)
{
parent::__construct($dsn, $username, $password, $options);
}
public function query($query)
{
$result = parent::query($query);
return $result;
}
/**
* #return LoggedPDOStatement
*/
public function prepare($statement, $options = NULL)
{
if (!$options) {
$options = array();
}
return new \LoggedPDOStatement(parent::prepare($statement, $options));
}
}
/**
* PDOStatement decorator that logs when a PDOStatement is
* executed, and the time it took to run
* #see LoggedPDO
*/
class LoggedPDOStatement
{
/**
* The PDOStatement we decorate
*/
private $statement;
protected $_debugValues = null;
public function __construct(PDOStatement $statement)
{
$this->statement = $statement;
}
public function getLogger()
{
return \Logger::getLogger('PDO sql');
}
/**
* When execute is called record the time it takes and
* then log the query
* #return PDO result set
*/
public function execute(array $params = array())
{
$start = microtime(true);
if (empty($params)) {
$result = $this->statement->execute();
} else {
foreach ($params as $key => $value) {
$this->_debugValues[$key] = $value;
}
$result = $this->statement->execute($params);
}
$this->getLogger()->debug($this->_debugQuery());
$time = microtime(true) - $start;
$ar = (int) $this->statement->rowCount();
$this->getLogger()->debug('Affected rows: ' . $ar . ' Query took: ' . round($time * 1000, 3) . ' ms');
return $result;
}
public function bindValue($parameter, $value, $data_type = false)
{
$this->_debugValues[$parameter] = $value;
return $this->statement->bindValue($parameter, $value, $data_type);
}
public function _debugQuery($replaced = true)
{
$q = $this->statement->queryString;
if (!$replaced) {
return $q;
}
return preg_replace_callback('/:([0-9a-z_]+)/i', array($this, '_debugReplace'), $q);
}
protected function _debugReplace($m)
{
$v = $this->_debugValues[$m[0]];
if ($v === null) {
return "NULL";
}
if (!is_numeric($v)) {
$v = str_replace("'", "''", $v);
}
return "'" . $v . "'";
}
/**
* Other than execute pass all other calls to the PDOStatement object
* #param string $function_name
* #param array $parameters arguments
*/
public function __call($function_name, $parameters)
{
return call_user_func_array(array($this->statement, $function_name), $parameters);
}
}
In Debian NGINX environment i did the following.
Goto /etc/mysql/mysql.conf.d edit mysqld.cnf if you find log-error = /var/log/mysql/error.log add the following 2 lines bellow it.
general_log_file = /var/log/mysql/mysql.log
general_log = 1
To see the logs goto /var/log/mysql and tail -f mysql.log
Remember to comment these lines out once you are done with debugging if you are in production environment delete mysql.log as this log file will grow quickly and can be huge.
Because I find PDO executions extremely hard to remember and find myself looking back at previous projects or other websites just to remember how to select rows from a database, I decided that I would try and create my own functions that contain the PDO executions and just plug in the data I need. It seemed a lot simpler than it actually is though...
So far I have already created a connect function successfully, but now when it comes to create a select function I'm stumped for multiple reasons.
For starters there could be a variating amount of args that can be passed into the function and secondly I can't figure out what I should pass to the function and in which order.
So far the function looks like this. To keep me sane, I've added the "id" part to it so I can see what exactly I need to accomplish in the final outcome, and will be replaced by variables accordingly when I work out how to do it.
function sql_select($conn, **what to put here**) {
try {
$stmt = $conn->prepare('SELECT * FROM myTable WHERE id = :id');
$stmt->execute(array('id' => $id));
$result = $stmt->fetchAll();
if ( count($result) ) {
foreach($result as $row) {
print_r($row);
}
} else {
return "No rows returned.";
}
} catch(PDOException $e) {
echo 'ERROR: ' . $e->getMessage();
}
}
So far what I've established that the function will need to do is
Connect to the database (using another function to generate the $conn variable, already done)
Select the table
Specify the column
Supply the input to match
Allow for possible args such as ORDER by 'id' DESC
Lastly from this I would need to create a function to insert, update and delete rows from the database.
Or, is there a better way to do this rather than functions?
If anyone could help me accomplish my ambitions to simply simplify PDO executions it would be greatly appreciated. Thanks in advance!
First of all, I have no idea where did you get 10 lines
$stmt = $conn->prepare('SELECT * FROM myTable WHERE id = ?');
$stmt->execute(array($id));
$result = $stmt->fetchAll();
is ALL the code you need, and it's actually three lines, which results with a regular PHP array that you can use wherever you wish. Without the need of any PDO code. Without the need of old mysql code.
Lastly from this I would need to create a function to insert, update and delete rows from the database.
DON'T ever do it.
Please read my explanations here and here based on perfect examples of what you'll end up if continue this way.
accomplish my ambitions to simply simplify PDO executions
That's indeed a great ambition. However, only few succeeded in a real real simplification, but most resulted with actually more complex code. For starter you can try code from the first linked answer. Having a class consists of several such functions will indeed improve your experience with PDO.
. . . and find myself looking back at previous projects or other
websites just to remember how to select rows from a database . . .
FYI, we all do that.
You had a problem with the PDO API and now you have two problems. My best and strongest suggestion is this: If you want a simpler/different database API, do not roll your own. Search http://packagist.org for an ORM or a DBAL that looks good and use it instead of PDO.
Other people have already done this work for you. Use their work and focus instead on whatever awesome thing is unique to your app. Work smart, not hard and all that.
Writting a wrapper, should start form connecting the DB, and all the possible method could be wrapped. Passing connection to the query method, doesn't look good.
A very rough example would be the code bellow, I strongly do not suggest this mixture, but it will give you the direction.
You connection should be made either from the constructor, or from another method called in the constructor, You can use something like this:
public function __construct($driver = NULL, $dbname = NULL, $host = NULL, $user = NULL, $pass = NULL, $port = NULL) {
$driver = $driver ?: $this->_driver;
$dbname = $dbname ?: $this->_dbname;
$host = $host ?: $this->_host;
$user = $user ?: $this->_user;
$pass = $pass ?: $this->_password;
$port = $port ?: $this->_port;
try {
$this->_dbh = new PDO("$driver:host=$host;port=$port;dbname=$dbname", $user, $pass);
$this->_dbh->exec("set names utf8");
} catch(PDOException $e) {
echo $e->getMessage();
}
}
So you can either pass connection credentials when you instantiate your wrapper or use default ones.
Now, you can make a method that just recieves the query. It's more OK to write the whole query, than just pass tables and columns. It will not make a whole ORM, but will just make the code harder to read.
In my first times dealing with PDO, I wanted everything to be dynamically, so what I achieved, later I realized is immature style of coding, but let's show it
public function query($sql, $unset = null) {
$sth = $this->_dbh->prepare($sql);
if($unset != null) {
if(is_array($unset)) {
foreach ($unset as $val) {
unset($_REQUEST[$val]);
}
}
unset($_REQUEST[$unset]);
}
foreach ($_REQUEST as $key => $value) {
if(is_int($value)) {
$param = PDO::PARAM_INT;
} elseif(is_bool($value)) {
$param = PDO::PARAM_BOOL;
} elseif(is_null($value)) {
$param = PDO::PARAM_NULL;
} elseif(is_string($value)) {
$param = PDO::PARAM_STR;
} else {
$param = FALSE;
}
$sth->bindValue(":$key", $value, $param);
}
$sth->execute();
$result = $sth->fetchAll();
return $result;
}
So what all of these spaghetti does?
First I though I would want all of my post values to be send as params, so if I have
input name='user'
input name='password'
I can do $res = $db->query("SELECT id FROM users WHERE username = :user AND password = :password");
And tada! I have fetched result of this query, $res is now an array containing the result.
Later I found, that if I have
input name='user'
input name='password'
input name='age'
In the same form, but the query remains with :user and :password and I submit the form, the called query will give mismatch in bound params, because the foreach against the $_REQUEST array will bind 3 params, but in the query I use 2.
So, I set the code in the beginning of the method, where I can provide what to exclude. Calling the method like $res = $db->query("SELECT id FROM users WHERE username = :user AND password = :password", 'age'); gave me the possibility to do it.
It works, but still is no good.
Better have a query() method that recieves 2 things:
The SQL string with the param names
The params as array.
So you can use the foreach() logic with bindValue, but not on the superglobal array, but on the passed on.
Then, you can wrap the fetch methods
public function fetch($res, $mode = null)
You should not directly return the fetch from the query, as it might be UPDATE, INSERT or DELETE.
Just pass the $res variable to the fetch() method, and a mode like PDO::FETCH_ASSOC. You can use default value where it would be fetch assoc, and if you pass something else, to use it.
Don't try to be so abstract, as I started to be. It will make you fill cracks lately.
Hum... IMHO I don't think you should try to wrap PDO in functions, because they're already "wrapped" in methods. In fact, going from OOP to procedural seems a step back (or at least a step in the wrong direction). PDO is a good library and has a lot of methods and features that you will surely lose if you wrap them in simple reusable functions.
One of those features is the BeginTransaction/Rollback (see more here)
Regardless, In a OOP point of view you can decorate the PDO object itself, adding some simple methods.
Here's an example based on your function
Note: THIS CODE IS UNTESTED!!!!
class MyPdo
{
public function __construct($conn)
{
$this->conn = $conn;
}
public function pdo()
{
return $this->conn;
}
public function selectAllById($table, $id = null)
{
$query = 'SELECT * FROM :table';
$params = array('table'=>$table);
if (!is_null($id)) {
$query .= ' WHERE id = :id';
$params['id'] = $id;
}
$r = $this->conn->prepare($query)
->execute($params)
->fetchAll();
//More stuff here to manipulate $r (results)
return $r;
}
public function __call($name, $params)
{
call_user_func_array(array($this->conn, $name), $params);
}
}
Note: THIS CODE IS UNTESTED!!!!
ORM
Another option is using an ORM, which would let you interact with your models/entities directly without bothering with creating/destroying connections, inserting/deleting, etc... Doctrine2 or Propel are good bets for PHP.
Howeveran ORM is a lot more complex than using PDO directly.
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 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).