I am trying to write a very small abstraction layer for my mysqli connection and have run into a problem.
Since I am maintaining older code I need to get an associative array from my query as this is the way the code has been set up and therefor less work for me once this works...
This function does work with all kinds of queries(not just select)...
my function I wrote is this:
function connectDB($query,$v=array()) {
$mysqli = new mysqli(HOST,USER,PW,DATABASE);
if($res=$mysqli->prepare($query)) {
//dynamically bind all $v
if($v) {
$values=array($v[0]);
for($i=1; $i<count($v); $i++) {
${'bind'.$i}=$v[$i];
$values[]=&${'bind'.$i};
}
call_user_func_array(array($res,'bind_param'),$values);
}
$res->execute();
//bind all table rows to result
if(strtolower(substr($query,0,6))=="select") {
$fields=array();
$meta=$res->result_metadata();
while($field=$meta->fetch_field()) {
${$field->name}=null;
$fields[$field->name]=&${$field->name};
}
call_user_func_array(array($res,"bind_result"),$fields);
//return associative array
$results = array();
$i=0;
while($res->fetch()) {
$results[$i]=array();
foreach($fields as $k => $v) $results[$i][$k] = $v;
$i++;
}
}
else {
$results=$mysqli->affected_rows;
if($mysqli->affected_rows<1) $results=$mysqli->info;
}
$res->close();
}
$mysqli->close();
return $results;
}
so if I call:
$MySqlres=connectDB("select * from `modx_events` events limit 1");
var_dump($MySqlres);
I get a nice associative array with the content of my select.
Now unfortunately the following mysql query will return NULL as a value to all of it's array keys:
$MySqlres=connectDB("select *, events.`id` as `ID`,venues.`name` as `venueName`,
venues.`suburb` as `venueSuburb`,venues.`advertiser` as `venueAdvertiser`
from `modx_events` events left join `modx_venues` venues on events.`venue`=venues.`id`
where events.`id`!='e' order by events.`start_date` asc, venues.`name` limit 1");
(the same query runs as pure SQL and will return the correct values)
Any idea what this could be? Does my associative array function fail? Is there something wrong with the way I implemented the query?
ps: PDO is not an option and mysqlnd is not installed... :(
ADDED QUESTION
is this too much of overhead just to preserve the associative array return? Should I go with $res->fetch_object() instead?
Mysqli is very poor with dynamic prepared statements, which makes small abstraction layer creation a nightmare.
I strongly suggest you to switch to PDO or get rid of prepared statements and create a regular query based on the manually handled placeholders (preferred).
As a palliative patch you can try to use get_result() function which will return a regular result variable which you can traverse usual way with fetch_assoc()
But it works only with mysqlnd builds.
Also note that creating mysqli object for the every query is a big no-no.
Create it once and then assign it in your query function using global $mysqli;
is this too much of overhead
I don't understand what overhead you're talking about
I just fixed the function.
Perhaps this is interesting for someone else:
function connectDB($mysqli,$query,$v=array()) {
if($mysqli->connect_errno) {
return array('error'=>'Connect failed: '.$mysqli->connect_error); //error handling here
exit();
}
if(substr_count($query,"?")!=strlen($v[0])) {
return array('error'=>'placeholders and replacements are not equal! placeholders:'.substr_count($query,"?").' replacements:'.strlen($v[0]).' ('.$v[0].')'); //error handling here...
exit();
}
if($res=$mysqli->prepare($query)) {
//dynamically bind all $v
if($v) {
$values=array($v[0]);
for($i=1; $i<count($v); $i++) {
${'bind'.$i}=$v[$i];
$values[]=&${'bind'.$i};
}
call_user_func_array(array($res,'bind_param'),$values);
}
$res->execute();
//bind all table rows to result
if(strtolower(substr($query,0,6))=="select") {
$field=$fields=$tempRow=array();
$meta=$res->result_metadata();
while($field=$meta->fetch_field()) {
$fieldName=$field->name;
$fields[]=&$tempRow[$fieldName];
}
$meta->free_result();
call_user_func_array(array($res,"bind_result"),$fields);
//return associative array
$results=array();
$i=0;
while($res->fetch()) {
$results[$i]=array();
foreach($tempRow as $k=>$v) $results[$i][$k] = $v;
$i++;
}
$res->free_result();
}
else { //return infos about the query
$results["affectedRows"]=$mysqli->affected_rows;
$results["info"]=$mysqli->info;
$results["insertID"]=$mysqli->insert_id;
}
$res->close();
}
return $results;
}
cheers
Related
I recently started using MySQLi prepared statements. I did not liked how many rows of code that was needed for just a simple select statement. So I created a wrapper function, see the code below the questions below.
Note: get_results() or PDO is not an option for me.
My questions are:
Will it slow down the performance noticeably?
Will it be more memory intensive because of the use of the result array?
Will the $stmt->close() before the return cause any problems? For example maybe the result array data is also are freed from memory?
Do I need to close or free anything else (except from closing the db connection)?
Do you see any other problems with the function or could it be improved?
Code:
class DatabaseHelper{
static function select($con, $query, $formats, $params){
$a_params = array();
$param_type = '';
$n = count($formats);
for($i = 0; $i < $n; $i++) {
$param_type .= $formats[$i];
}
$a_params[] = & $param_type;
for($i = 0; $i < $n; $i++) {
$a_params[] = & $params[$i];
}
$stmt = $con->prepare($query);
call_user_func_array(array($stmt, 'bind_param'), $a_params);
$stmt->execute();
$meta = $stmt->result_metadata();
while ($field = $meta->fetch_field()) {
$columns[] = &$row[$field->name];
}
call_user_func_array(array($stmt, 'bind_result'), $columns);
while ($stmt->fetch()) {
foreach($row as $key => $val) {
$x[$key] = $val;
}
$results[] = $x;
}
$stmt->close();
return $results;
}
}
Used like this for example:
$users = DatabaseHelper::select($conn, "SELECT name,username FROM users WHERE id > ?", "i", array(30));
foreach ($users as $row){
echo $row['username'] . " ". $row['name'] . "<br />";
}
Will it slow down the performance noticeably?
No.
Will it be more memory intensive because of the use of the result array?
No, as long as you are selecting sensible amounts of data. In a modern application you have to select all the data first anyway, as the business logic should be separated from display logic.
Will the $stmt->close() before the return cause any problems? For example maybe the result array data is also are freed from memory?
Why not to try and see?
Do I need to close or free anything else (except from closing the db connection)?
You rather don't have to close a statement either.
Do you see any other problems with the function or could it be improved?
First and foremost. As it's a class you are writing, and not a function, there is absolutely no point in passing the connection through a parameter. Make it a static property.
Also, I would suggest to make types the last parameter with default value. In most cases you don't have to nitpick with types - a default string will do.
Besides, as your PHP version is 5.6 you can use the splat operator just to reduce the amount of code. You can check this answer of mine for the details
I would also suggest to split your function into several methods - one to execute the query and others to get the results. It will let you to re-use the same code for all kinds of queries
make sure you are watching for mysqli errors as explained here
So, ideally you'd call your query this way
$users = DatabaseHelper::getAll("SELECT name,username FROM users WHERE id > ?", [30]);
foreach ($users as $row){
echo $row['username'] . " ". $row['name'] . "<br />";
}
where getAll() method is using query() method internally to perform a query and then to fetch all the results. Similarly you may want to write getRow() and getOne() methods
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.
This is similar to this question - Are Dynamic Prepared Statements Bad? (with php + mysqli), however since it is 4 years old I wanted to get a more upto date answer.
I've written a class which, although I haven't tested it on more copmlex sql queries, it has worked without fail on simple sql queries, however I'm not sure if doing so has bypassed one of the main reasons for prepared statements - security.
I have made use of the call_user_func_array which was easy enough with the bind_param statements however with the bind_result was a little trickier. I originally used get_result however the host I've gone with doesn't have mysqlnd available, but I managed to get around using the metadata. This is the full class I have written.
Do you think this is secure?
The passed in values are:
$sql is the passed in sql statement:
SELECT * FROM users WHERE id = ? AND created_timestamp > ?
$mysqli is the mysqli connection
$para is the placeholder in the prepared statement:
array ($types = 'ii', 23, 1235376000)
The class:
class crudModel {
function ps($sql, $mysqli, $para) {
//this function should work for just about any simple mysql statement
//for more complicated stuff like joins, unions etc,. we will see
if ($prep = $mysqli->prepare($sql)) {
call_user_func_array(array($prep, 'bind_param'), $this->makeValuesRef($para, $mysqli));
$prep->execute();
$meta = $prep->result_metadata();
while ($field = $meta->fetch_field()) {
$parameters[] = &$row[$field->name];
}
call_user_func_array(array($prep, 'bind_result'), $parameters);
while ($prep->fetch()) {
foreach ($row as $key=>$val) {
$x[$key] = $val;
}
$data[] = $x;
}
return $data;
}
}
function makeValuesRef($array, $mysqli) {
$refs = array();
foreach($array as $key => $value) {
$array[$key] = $mysqli->real_escape_string($value); //i don't think escaping is necessary, but it can't hurt (??)
$refs[$key] = &$array[$key];
}
return $refs;
}
}
What you're doing here isn't a dynamic prepared statement. You're just putting some syntatic sugar on top of the MySQLi API (which sucks).
In short, there aren't really any security concerns present from the code you've shown here. In fact, this sort of practice is quite good, because it makes it easier to verify that you're doing it correctly later (since the MySQLi API sucks).
So you're fine. I would worry about the areas you're generating the queries, and ensuring that you're not accidentally putting user-data into them without white-listing...
So, I've been learning PDO. So far, I am not at all impressed, honestly, due to the large amount of code needed to do small tasks. However, I am willing to convert nonetheless if I can get my code to be efficient and reusable.
My question is this: can I make this code any more efficient? By efficient, I mean both A) take up less lines, and B) run faster. I am worried that I am going about this all wrong. However, due to the lack of a num_rows() function, I can't think of a better way.
try
{
$sth = $dbh->prepare("SELECT * FROM table_name");
$sth->execute();
if (count($result = $sth->fetchAll()))
{
foreach ($result as $value)
{
// Rows returned! Loop through them.
}
}
else
{
// No rows returned!
}
}
catch (PDOException $e)
{
// Exception!
}
Is this written properly?
As far as my research has shown, no. There is no way to rewrite this code more concisely or logically--the way it stands is entirely optimized. :) It's easy to use, so that's definitely not a bad thing!
Use PDO::query() to issue a SELECT COUNT(*) statement with the same predicates as your intended SELECT statement
Then, Use PDOStatement::fetchColumn() to retrieve the number of rows that will be returned
$sql = "SELECT COUNT(*) FROM table_name";
if ($res = $conn->query($sql))
{
/* Check the number of rows that match the SELECT statement */
$res->fetchColumn(); //This will give you the number of rows selected//
}
Make a general function that does that, and all you need to do is send a select count based on your needs. You can make in more general by dividing the $select to more variables.
function countRows($select)
{
if ($res = $conn->query($select))
{
/* Check the number of rows that match the SELECT statement */
return $res->fetchColumn(); //This will give you the number of rows selected//
}
}
No, you need to use PDO's rowCount method.
try
{
$sth = $dbh->prepare("SELECT * FROM table_name");
$sth->execute();
if ($sth->rowCount() > 0)
{
while ($result = $sth->fetch())
{
// Rows returned! Loop through them.
}
}
else
{
// No rows returned!
}
}
catch (PDOException $e)
{
// Exception!
}
I'm currently learning PHP and MySQL. I'm just wrote a class that handles all the MySQL traffic, but I'm encountering some errors.
function table_exists($tablename){
// check if table exists
$stmt = $this->db->prepare("SHOW TABLES LIKE '?'");
$stmt->bind_param("s", $tablename); //This is line 24.
$stmt->execute();
$ar = $stmt->affected_rows;
$stmt->close();
if ($ar > 0){
return true;
} else {
return false;
}
}
This is the code with the problem, and the error i'm getting is
Generates Warning:
mysqli_stmt::bind_param()
[mysqli-stmt.bind-param]: Number of
variables doesn't match number of
parameters in prepared statement in
C:\xampp\htdocs\mail\datahandler.php
on line 24
Ideas?
Thanks
No need to use quotes when working with prepared statements.
$stmt = $this->db->prepare("SHOW TABLES LIKE ?");
Also, instead of SHOW TABLES, you might want to use information_schema views, which give you a bit more flexibility.
You also have to use a number as first parameter for bind_param()
$stmt->bind_param(1, $tablename);
See here: http://php.net/manual/pdostatement.bindparam.php
For strings you can also just pass an array into execute().
private function table_exists($tablename){
// check if table exists
$stmt = $this->db->query("SHOW TABLES");
while($row = $stmt->fetch(PDO::FETCH_ASSOC)){
$arr[]=$row;
}
$ar=0;
foreach($arr as $val){
foreach($val as $value){
if($value==$tablename) $ar=1;
}
}
unset($stmt);
if ($ar == 1){
return true;
} else {
return false;
}
}