Currently Im trying to create a PDO class where I will have a method to run a query like INSERT, UPDATE, or DELETE.
For examaple this is my method for a SELECT
public function getPreparedQuery($sql){
$stmt = $this->dbc->prepare($sql);
$stmt->execute([5]);
$arr = $stmt->fetchAll(PDO::FETCH_ASSOC);
if(!$arr) exit('No rows');
$stmt = null;
return $arr;
}
And i Simply call it like this:
$stmt = $database->getPreparedQuery($sql2);
var_export($stmt);
And so far I know that a runQuery should work something similar to this:
Insert Example without using a method:
$idRol = "6";
$nomRol = "test6";
$stmt = $database->dbc->prepare("insert into roles (idRol, NomRol) values (?, ?)");
$stmt->execute(array($idRol,$nomRol));
$stmt = null;
But I want to make it into an universal method where i simply can pass the sql sentence, something like this:
$database->runQuery($query);
but the query can happen to be
$query = "INSERT INTO roles (idRol, NomRol) VALUES ('4','test')";
or
$query = "INSERT INTO movies (movName, movLength, movArg) VALUES ('name1','15','movie about...')";
So how do I slice the arg $query so I can get all the variables that are being used in order to make an universal runQuery?
Because I can imagine that my method should be something like this
runQuery([$var1 , $var2, $var3....(there can be more)] , [$var1value, $var2value, $var3value...(there can be more)]){
$stmt = $database->dbc->prepare("insert into movies($var1 , $var2, $var3....(there can be more)) values (?, ? , ? (those are the values and there ca be more than 3)"); //how do i get those "?" value and amount of them, and the name of the table fields [movName, movLength, movArg can be variable depending of the sentence]?
$stmt->execute(array($var1,$var2, $var3, ...)); //this is wrong , i dont know how to execute it
$stmt = null;
}
You need to add a second parameter to your function. Simply an array where all those variables would go. An array by definition can have an arbitrary number of elements, which solves your problem exactly:
public function runQuery($sql, $parameters = []) {
$stmt = $this->dbc->prepare($sql);
$stmt->execute($parameters);
return $stmt;
}
this simple function will run ANY query. You can see the usage example in my article dedicated to PDO helper functions:
// getting the number of rows in the table
$count = $db->runQuery("SELECT count(*) FROM users")->fetchColumn();
// the user data based on email
$user = $db->runQuery("SELECT * FROM users WHERE email=?", [$email])->fetch();
// getting many rows from the table
$data = $db->runQuery("SELECT * FROM users WHERE salary > ?", [$salary])->fetchAll();
// getting the number of affected rows from DELETE/UPDATE/INSERT
$deleted = $db->runQuery("DELETE FROM users WHERE id=?", [$id])->rowCount();
// insert
$db->runQuery("INSERT INTO users VALUES (null, ?,?,?)", [$name, $email, $password]);
// named placeholders are also welcome though I find them a bit too verbose
$db->runQuery("UPDATE users SET name=:name WHERE id=:id", ['id'=>$id, 'name'=>$name]);
// using a sophisticated fetch mode, indexing the returned array by id
$indexed = $db->runQuery("SELECT id, name FROM users")->fetchAll(PDO::FETCH_KEY_PAIR);
As you can see, now your function can be used with any query with any number of parameters
Related
I am new to php and trying hard to learn its why you guys and gals need to Forgive me for asking a lot!
Here is my question;
I am trying to call a function with where clause multiple times, I have read allmost all posts and examples still didn't understand how to do it.
I tought that An example will be more useful than any blurb I can write.
Here is the function I am trying to create and use it multiple times :
function getTable($tableName, $clause) {
$stmt = $pdo->prepare("SELECT * FROM ".$tableName." WHERE ".$clause." = :".$clause);
$stmt->bindParam(":$clause", $clause, PDO::PARAM_STR);
$stmt->execute();
if($stmt->rowCount() > 0){
return true;
}else{
return false;
}
return $stmt;
}
I am not sure if my fucntion is safe or its rigth.
AND this is how I am trying to call function, which I dont know how to call table name and where clause and how to turn while loop.
getTable('posts');
If you give an example of creating and caling function, I would be grateful, Thanks
Nope, your function is not safe. Moreover it is just useless. There is no use case where you would use it like this getTable('posts');. And for the everything else it is much better to allow the full SQL syntax, not some limited subset.
The simplest yet most powerful PDO function I can think of is a function that accepts a PDO object, an SQL query, and array with input variables. A PDO statement is returned. I wrote about such function in my article about PDO helper functions. So here is the code:
function pdo($pdo, $sql, $args = NULL)
{
if (!$args)
{
return $pdo->query($sql);
}
$stmt = $pdo->prepare($sql);
$stmt->execute($args);
return $stmt;
}
With this function you will be able to run any query, with any number of WHERE conditions, and get results in many different formats. Here are some examples from the article mentioned above:
// getting the number of rows in the table
$count = pdo($pdo, "SELECT count(*) FROM users")->fetchColumn();
// the user data based on email
$user = pdo($pdo, "SELECT * FROM users WHERE email=?", [$email])->fetch();
// getting many rows from the table
$data = pdo($pdo, "SELECT * FROM users WHERE salary > ?", [$salary])->fetchAll();
// getting the number of affected rows from DELETE/UPDATE/INSERT
$deleted = pdo($pdo, "DELETE FROM users WHERE id=?", [$id])->rowCount();
// insert
pdo($pdo, "INSERT INTO users VALUES (null, ?,?,?)", [$name, $email, $password]);
// named placeholders are also welcome though I find them a bit too verbose
pdo($pdo, "UPDATE users SET name=:name WHERE id=:id", ['id'=>$id, 'name'=>$name]);
// using a sophisticated fetch mode, indexing the returned array by id
$indexed = pdo($pdo, "SELECT id, name FROM users")->fetchAll(PDO::FETCH_KEY_PAIR);
Special for you, here is the while example, though this method is considered clumsy and outdated:
$stmt = pdo($pdo,"SELECT * FROM tableName WHERE field = ?",[$value]);
while ($row = $stmt->fetch()) {
echo $row['name'];
}
Currently Im trying to create a PDO class where I will have a method to run a query like INSERT, UPDATE, or DELETE.
For examaple this is my method for a SELECT
public function getPreparedQuery($sql){
$stmt = $this->dbc->prepare($sql);
$stmt->execute([5]);
$arr = $stmt->fetchAll(PDO::FETCH_ASSOC);
if(!$arr) exit('No rows');
$stmt = null;
return $arr;
}
And i Simply call it like this:
$stmt = $database->getPreparedQuery($sql2);
var_export($stmt);
And so far I know that a runQuery should work something similar to this:
Insert Example without using a method:
$idRol = "6";
$nomRol = "test6";
$stmt = $database->dbc->prepare("insert into roles (idRol, NomRol) values (?, ?)");
$stmt->execute(array($idRol,$nomRol));
$stmt = null;
But I want to make it into an universal method where i simply can pass the sql sentence, something like this:
$database->runQuery($query);
but the query can happen to be
$query = "INSERT INTO roles (idRol, NomRol) VALUES ('4','test')";
or
$query = "INSERT INTO movies (movName, movLength, movArg) VALUES ('name1','15','movie about...')";
So how do I slice the arg $query so I can get all the variables that are being used in order to make an universal runQuery?
Because I can imagine that my method should be something like this
runQuery([$var1 , $var2, $var3....(there can be more)] , [$var1value, $var2value, $var3value...(there can be more)]){
$stmt = $database->dbc->prepare("insert into movies($var1 , $var2, $var3....(there can be more)) values (?, ? , ? (those are the values and there ca be more than 3)"); //how do i get those "?" value and amount of them, and the name of the table fields [movName, movLength, movArg can be variable depending of the sentence]?
$stmt->execute(array($var1,$var2, $var3, ...)); //this is wrong , i dont know how to execute it
$stmt = null;
}
You need to add a second parameter to your function. Simply an array where all those variables would go. An array by definition can have an arbitrary number of elements, which solves your problem exactly:
public function runQuery($sql, $parameters = []) {
$stmt = $this->dbc->prepare($sql);
$stmt->execute($parameters);
return $stmt;
}
this simple function will run ANY query. You can see the usage example in my article dedicated to PDO helper functions:
// getting the number of rows in the table
$count = $db->runQuery("SELECT count(*) FROM users")->fetchColumn();
// the user data based on email
$user = $db->runQuery("SELECT * FROM users WHERE email=?", [$email])->fetch();
// getting many rows from the table
$data = $db->runQuery("SELECT * FROM users WHERE salary > ?", [$salary])->fetchAll();
// getting the number of affected rows from DELETE/UPDATE/INSERT
$deleted = $db->runQuery("DELETE FROM users WHERE id=?", [$id])->rowCount();
// insert
$db->runQuery("INSERT INTO users VALUES (null, ?,?,?)", [$name, $email, $password]);
// named placeholders are also welcome though I find them a bit too verbose
$db->runQuery("UPDATE users SET name=:name WHERE id=:id", ['id'=>$id, 'name'=>$name]);
// using a sophisticated fetch mode, indexing the returned array by id
$indexed = $db->runQuery("SELECT id, name FROM users")->fetchAll(PDO::FETCH_KEY_PAIR);
As you can see, now your function can be used with any query with any number of parameters
So I have three tables in a MySQL database:
order(id, etc..),
product(id, title, etc..)
orderproduct(productFK, orderFK)
Now I want to be able to insert an order with one order-id and (in some cases) multiple product-ids for orders containing more than one product:
order 1: orderid = 1, productids = 1
order 2: orderid = 2, productids = 2, 3
this while using prepared statements, like:
$stmt = $mysqli->prepare("INSERT INTO orderproduct (orderFK, productFK)
VALUES (?, ?)");
$result = $stmt->bind_param('ss', $orderid, $productid);
if($stmt->execute() == false) {
$flag = false;
}
$stmt->close();
One obvious solution is to loop the insert query but is there another way to do this without having to call the database multiple times?
This is working (hardcoded) but still, I can't figure out how to fill the bind_param dynamically..
$strings = "";
$values = "";
foreach ($params['products'] as $product) {
$strings .= 'ss';
$values .= "(?, ?),";
}
$values = substr($values, 0, -1);
$productid = array(1, 2);
$stmt = $mysqli->prepare("INSERT INTO orderproduct (orderFK, productFK)
VALUES " . $values);
$result = $stmt->bind_param($strings, $orderid, $productid[0], $orderid, $productid[1]);
if($stmt->execute() == false) {
$flag = false;
}
$stmt->close();
If you are on php 5.6+ you can use argument unpacking ... to bind your variables:
$args = [
$arg1,
$arg2,
$arg3,
$arg4,
];
$result = $stmt->bind_param($strings, ...$args);
An alternative would be to use PDO where you can send an array of arguments to bind to the execute() method.
Mysql will happily process something like:
INSERT INTO mytable (col1, col2)
VALUES (c1a, c2a)
,(C1b, c2b)
,(C1c, c2c)
...
(Up to max_allowed_packet) however expressing the bind_param() call cleanly (in a way which is easy to debug) will be rather difficult. Invoking with call_user_func_array() means you can simply pass a single array as the argument, but you still need to map your 2 dimensional data set to a one dimensional array.
Previously I've used the procedural mysql api to do multiple inserts resulting in a significant speed up of inserts and better balanced indexes.
My SQL looks something like this:
$sql = "select * from user where id in (:userId) and status = :status";
$em = $this->getEntityManager();
$stmt = $em->getConnection()->prepare($sql);
$stmt->bindValue(':userId', $accounts, \Doctrine\DBAL\Connection::PARAM_INT_ARRAY);
$stmt->bindValue(':status', 'declined');
$stmt->execute();
$result = $stmt->fetchAll();
But it returns:
An exception occurred while executing (...)
with params
[[1,2,3,4,5,6,7,8,11,12,13,14], "declined"]
Notice: Array to string conversion
I cannot user queryBuilder because my real SQL is more complicated (ex. contains joined select, unions and so on)
You can't use prepared statements with arrays simply because sql itself does not support arrays. Which is a real shame. Somewhere along the line you actually need to determine if your data contains say three items and emit a IN (?,?,?). The Doctrine ORM entity manager does this for you automatically.
Fortunately, the DBAL has you covered. You just don't use bind or prepare. The manual has an example: https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/data-retrieval-and-manipulation.html#list-of-parameters-conversion
In your case it would look something like:
$sql = "select * from user where id in (?) and status = ?";
$values = [$accounts,'declined'];
$types = [Connection::PARAM_INT_ARRAY, \PDO::PARAM_STR];
$stmt = $conn->executeQuery($sql,$values,$types);
$result = $stmt->fetchAll();
The above code is untested but you should get the idea. (Make sure you use Doctrine\DBAL\Connection; for Connection::PARAM_INT_ARRAY)
Note for people using named parameters:
If you are using named parameters (:param instead of ?), you should respect the parameter names when providing types. For example:
$sql = "select * from user where id in (:accounts) and status = :status";
$values = ['accounts' => $accounts, 'status' => 'declined'];
$types = ['accounts' => Connection::PARAM_INT_ARRAY, 'status' => \PDO::PARAM_STR];
If you want to stick to the :param syntax where order does not matter, you have to do a bit of extra work, but I'll show you an easier way to bind the parameters:
// store all your parameters in one array
$params = array(
':status' => 'declined'
);
// then, using your arbitrary array of id's ...
$array_of_ids = array(5, 6, 12, 14);
// ... we're going to build an array of corresponding parameter names
$id_params = array();
foreach ($array_of_ids as $i => $id) {
// generate a unique name for this parameter
$name = ":id_$i"; // ":id_0", ":id_1", etc.
// set the value
$params[$name] = $id;
// and keep track of the name
$id_params[] = $name;
}
// next prepare the parameter names for placement in the query string
$id_params = implode(',', $id_params); // ":id_0,:id_1,..."
$sql = "select * from user where id in ($id_params) and status = :status";
In this case we end up with:
"select * from user where id in (:id_0,:id_1,:id_2,:id_3) and status = :status"
// now prepare your statement like before...
$stmt = $em->getConnection()->prepare($sql);
// ...bind all the params in one go...
$stmt->execute($params);
// ...and get your results!
$result = $stmt->fetchAll();
This approach will also work with an array of strings.
You need to wrap them in an array
$stmt->bindValue(':userId', array($accounts), array(\Doctrine\DBAL\Connection::PARAM_INT_ARRAY));
http://doctrine-dbal.readthedocs.io/en/latest/reference/data-retrieval-and-manipulation.html#list-of-parameters-conversion
edit
I should have elaborated more. You cannot bind an array like that, dont prepare the sql execute directly as the example in the docs.
$stmt = $conn->executeQuery('SELECT * FROM articles WHERE id IN (?)',
array(array(1, 2, 3, 4, 5, 6)),
array(\Doctrine\DBAL\Connection::PARAM_INT_ARRAY));
You cannot bind an array of values into a single prepared statement parameter
I have the following function. I expect it to print the number of rows in the table provided in its argument.
private function getTotalCount($tbl){
$sql = "SELECT count(*) FROM :tbl ;";
$sth = $this->db->prepare($sql);
$sth->execute(array(
':tbl' => $tbl
));
$data = $sth->fetch(PDO::FETCH_ASSOC);
print_r($data);
}
But the function is not printing anything...
When I replace the function to something like this:
private function getTotalCount($tbl){
$sql = "SELECT count(*) FROM $tbl ;";
$sth = $this->db->prepare($sql);
$sth->execute();
$data = $sth->fetch(PDO::FETCH_ASSOC);
print_r($data);
}
Then it works fine and print the number of rows.
QUESTION: Why the execute() function not binding the :tbl parameter to $tbl ??
Sadly MySQL PDO doesn't accept parameters for SQL keywords, table names, view names and field names. This doesn't really come up in the main manual, but is mentioned a couple of times in comments.
The solution you have in the second piece of code is the workaround, although you may wish to sanitise the table name first (checking against a white list of table names would be ideal). More info: Can PHP PDO Statements accept the table or column name as parameter?