PDO library with better parameter placement - php

Don't get me wrong PDO is great but what I don't like about it, is how variables are placed far away from the place they belong inside the SQL statement. Meaning I have a query like:
$stmt = $dbh->prepare("SELECT * FROM users WHERE email = ? AND pass = ?");
The variables that are replacing the ? are always far away some lines below:
$stmt->bindParam(1, $email);
$stmt->bindParam(2, $pass);
If you have a lot of parameters this can get quite ugly. Using :email instead of ? does not make it much better. Sometimes you see the parameters as array in the same methode like:
$db->query("SELECT * FROM users WHERE email = ? AND pass = ?",
array($email, $pass));
A little bit better but with 10 variables it is still ugly. You practically create a variable ? you only use once. Also code highlighting is not supported for this pseudo variable.
I think it would be nicer to have it like this
$db->prepare("SELECT * FROM user WHERE email = ", $email, " AND pass = ", $pass);
You could even include the parameters of binParam() like this:
$db->prepare_new(
"SELECT * FROM user WHERE email = ", array($email, PDO::PARAM_STR),
" AND pass = ", $pass);
I wounder if there is a library that supports this type of style. Do you know one?

If always every even parameter will be parameter you can do it like this:
class MyPDO extends PDO {
public function prepareQuery() {
$query_str = "";
$params = array();
foreach(func_get_args() as $key => $param) {
if( $key % 2 == 0 ) {
$query_str .= $param;
}
else {
$params[] = $param;
$query_str .= ' ? ';
}
}
$prepared = $this->prepare($query_str);
foreach( $params as $key => $param ) {
$prepared->bindParam( $key+1, $param );
}
return $prepared;
}
}
and then you can use it as you wanted:
$db = new MyPDO( .. );
$db->prepareQuery("SELECT * FROM user WHERE email = ", $email, " AND pass = ", $pass);
PS: not tested - just a concept

A lot of the point of having ? and :email is that you can reuse the query multiple times. For example:
$stmt = $pdo->prepare("SELECT true FROM user WHERE email = :email");
$stmt->execute(array($email1));
$stmt->execute(array($email2));
// etc.
Having specific variables in the query removes this functionality entirely.
If you wanted, you could always extend or comprise PDO, though:
class DB {
private $pdo;
public function executeQuery($query, $args) {
$stmt = $this->pdo->prepare($query);
$stmt->execute($args);
}
}
$db->executeQuery("SELECT true FROM user WHERE email = :email", array($email1));
This hides the functionality of PDO that you dislike.
UPDATE:
This is an unusual way of doing things, but it seems to be what you're after:
$pdo->query("SELECT true FROM user WHERE email = " . $pdo->quote($email));
http://us2.php.net/manual/en/pdo.quote.php

You could put something similar to this into a function:
$stmt = $dbh->prepare("SELECT * FROM users WHERE email = :email AND pass = :pass");
$arr = array(
'email' => 'test#test.com',
'pass' => 'secret'
);
foreach($arr as $key => $val){
$stmt->bindParam(':'.$key, $val);
}
Example:
function myBindParams($stmt, $bindings){
foreach($bindings as $key => $val){
$stmt->bindParam(':'.$key, $val);
}
return $stmt;
}
$stmt = $dbh->prepare("SELECT * FROM users WHERE email = :email AND pass = :pass");
$arr = array(
'email' => 'test#test.com',
'pass' => 'secret'
);
$stmt = myBindParams($stmt, $arr);

Related

How to organize SQL select functions?

I want to get information by user id, so lets add this to the model:
public function getById ($id)
{
$sql = 'SELECT * FROM users';
return ActualDbHander::run($sql);
}
later, I want to get only some fields:
public function getById ($id, $fields = '*')
{
$sql = 'SELECT '.$fields.' FROM users';
return ActualDbHander::run($sql);
}
another idea, lets add ordering:
public function getById ($id, $fields = '*', $orderBy = '')
{
$sql = 'SELECT '.$fields.' FROM users';
if ($orderBy != '')
{
$sql.= ' ORDER BY '.$orderBy;
}
return ActualDbHander::run($sql);
}
and I see this becaming messy and messy. What if I want to add JOIN-s? What if I want to add detailed WHERE-s? This is when "too generalic" methods born.
I completely agree with mch and Mjh comments, but, only in the case you actually want to have a "BD driver" (and build it yourself) I'd use different names for each query, very specific names, because you need to know exactly what a function will return to you.
So if I were you I would use names like getAllUsers, getUserById, getAllUsersOnlyPersonalData, getUserByIdOnlyPersonalData, getAllUsersOnlyContactData and so on (with fixed fields and filters for each method).
Note that in your examples you are not using at all the $id variable, so you are always receiving a list of users.
Regarding the method to make the queries, there are lots of ways to do it. Personally, I prefer MySQLi Object-Oriented prepared statements, because it's safe, easy and currently very extended, so I will use it just to ilustrate the examples.
Your functions would be something like this:
<?php
class DBDriver{
function openConnection(){
// If you don't always use same credentials, pass them by params
$servername = "localhost";
$username = "username";
$password = "password";
$database = "database";
// Create connection
$conn = new mysqli($servername, $username, $password, $database);
// Check connection
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
// Return conection object
return $conn;
}
function closeConnection($conn){
$conn->close();
}
function getAllUsers (){ // We don't need ids here
$conn = $this->openConnection();
// Array of arrays to store the results
// You can use any other method you want to return them
$resultsArray = [];
$sqlQuery = "SELECT * FROM users";
// In this case it's not neccesary to use prepared statements because we aren't binding any param but we'll use it to unify the method
if ($stmt = $conn->prepare($sqlQuery)) {
// Execute query
$stmt->execute();
// Bind result variables (I don't know your actuall column names)
$stmt->bind_result($id, $name, $email, $phone, $birthdate);
// Fetch values
while ($stmt->fetch()) {
$resultsArray[] = [$id, $name, $email, $phone, $birthdate];
}
// Close statement
$stmt->close();
}
$this->closeConnection($conn);
// If no results, it returns an empty array
return $resultsArray;
}
function getUserByIdOnlyContactData ($userId){
$conn = $this->openConnection();
// Array to store the results (only one row in this case)
$resultsArray = [];
$sqlQuery = "SELECT name, email, phone FROM users WHERE id = ?";
if ($stmt = $conn->prepare($sqlQuery)) {
// Bind parameter $userId to "?" marker in $sqlQuery
$stmt->bind_param("i", $userId);
$stmt->execute();
$stmt->bind_result($name, $email, $phone);
// If id found
if ($stmt->fetch()) {
$resultsArray = [$name, $email, $phone];
}
// Close statement
$stmt->close();
}
$this->closeConnection($conn);
return $resultsArray;
}
function getAllUserOnlyBirthdayDataOrderByBirthday (){
$conn = $this->openConnection();
$resultsArray = [];
$sqlQuery = "SELECT id, name, birthdate FROM users ORDER BY birthdate";
if ($stmt = $conn->prepare($sqlQuery)) {
$stmt->execute();
$stmt->bind_result($id, $name, $birthdate);
while ($stmt->fetch()) {
$resultsArray[] = [$id, $name, $birthdate];
}
// Close statement
$stmt->close();
}
$this->closeConnection($conn);
return $resultsArray;
}
} // Class end
This way it's true you will have lots of functions depending on your requirements but as you can see it's extremely easy to add new ones or modify them (and you won't get mad with many different options in the same function).
Hope this helps you to organize your database driver!

PHP - for statement in an array

For a PDO execution statement I am trying to make any static information such as column names and array strings to a dynamic array which contains every column from the MySQL table.
The original code was:
$stmt = $conn->prepare("INSERT into data (`username,` `password`, `email`) VALUES username = :username , password = :password , email = :email ");
$stmt->execute(array(
':username' => $entry_username,
':password' => $entry_password,
':email' => $entry_email
));
So far I have been able to change the sql statement to
$sql = "INSERT into DATA (`" . implode('`,`', $columns) . "`) values (:" . implode(',:', $columns) . ")";
$stmt = $conn->prepare($sql);
but have been unable to do a similar thing to the execution array to make it dynamically variating like the statement.
I have tried adding a for statement in the array
for ($i = 0; $i < count($columns); $i++) {
':'.$columns[$i] => ${'entry_'.$columns[$i]};
}
but this hasn't worked.
Any help would be much appreciated.
Thanks in advance!
This is a perfect situation to make good use of a prepared statement.
Try this:
I am kind of assuming what the varuables will be called in the $columns array here.
$stmt = $conn->prepare("INSERT into data
(username, password, email) VALUES( :username , :password, :email )");
$stmt->bindParam(':username', $username, PDO::PARAM_STR);
$stmt->bindParam(':password', $password, PDO::PARAM_STR);
$stmt->bindParam(':email', $email, PDO::PARAM_STR);
foreach ( $columns as $column ) {
$username = $column['username'];
$password = $column['password'];
$email = $column['email'];
$result = $stmt->execute();
if ( ! $result ) {
// add some error checking code here
}
}
Basically, your code would look like this.
$entry = array(
'username' => $_POST['username'], //assuming it's comming from the post data or for instance $row['username'] if from previous select statement
'password' => $_POST['password'],
'email' => $_POST['email']
);
$sth = $dbh->prepare('INSERT into data (`username,` `password`, `email`) VALUES (:username, :password, :email)');
$sth->bindValue(':username', $entry['username'], PDO::PARAM_INT);
$sth->bindValue(':password', $entry['password'], PDO::PARAM_STR);
$sth->bindValue(':email', $entry['email'], PDO::PARAM_STR);
$sth->execute();
If you want the bound variables to be dynamically created, then you need to create with a loop the bindValue rows:
$entry = array(
'username' => $_POST['username'], //assuming it's comming from the post data or for instance $row['username'] if from previous select statement
'password' => $_POST['password'],
'email' => $_POST['email']
);
$sth = $dbh->prepare('INSERT into data (`username,` `password`, `email`) VALUES (:username, :password, :email)');
foreach($entry as $key => $value) {
$sth->bindValue(':'.$key, $entry[$key], PDO::PARAM_STR);
}
$sth->execute();
or inside the foreach
$sth->bindValue(':'.$key, $value, PDO::PARAM_STR);
Since your keys are (username, password, email) their keynames will be initiated to $key variable, and their values to the $value variable. in the first case it will produce:
$sth->bindValue(':username', $entry['username'], PDO::PARAM_INT);
$sth->bindValue(':password', $entry['password'], PDO::PARAM_STR);
$sth->bindValue(':email', $entry['email'], PDO::PARAM_STR);
Which will be evaluated to:
$sth->bindValue(':username', $_POST['username'], PDO::PARAM_INT);
$sth->bindValue(':password', $_POST['password'], PDO::PARAM_STR);
$sth->bindValue(':email', $_POST['email'], PDO::PARAM_STR);
In the second case it will be directly evaluated.
Have in mind it's completely unacceptable to dynamically create the column names in the query. And you have to reason to do it. However, not a full query is also hard to be read from the other developers. It's enough for you to dynamically create the bound values. You can make a method do it for you. For instance, if you column names in the query are the same way aliased, as the names of the input fields, you will have nothing more to do, but to execute the query.
Let's say you have that helper method:
Class DBConnect {
private $_driver = "mysql";
private $_dbname = "xxxx";
private $_host = "xxxx";
private $_user = "xxxx";
private $_password = "xxxx";
private $_port = 3306;
private $_dbh;
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();
}
}
public function query($sql) {
$sth = $this->_dbh->prepare($sql);
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, lets say in another class you have a lot of queries, separated by methods:
public function getFirstQuery() {
$sql = "SELECT
col1, col2
FROM table1
WHERE col3 = :col3;";
$query = $this->_db->query($sql);
return $query;
}
public function inserSecondquery() {
$sql = "INSERT INTO
`table1`
(col1, col2)
VALUES
((SELECT
id
FROM table2
WHERE col8 = :col8), :post_field_5);";
$query = $this->_db->query($sql);
return $query;
}
Assuming you have called these queries the query() method which also fetches the data, the select one you can foreach to retrieve the data, and the insert one you can just call, to insert data. The only rule here is the post fields should be named same way, for example <input name="post_field_5" />
You can also take a look here: PDO Dynamic Query Building
OK, it seems you need to find library for active record like the ones CodeIgniter uses, or... use CodeIgniter.
From the official documentation:
http://ellislab.com/codeigniter/user-guide/database/helpers.html
$this->db->insert_string();
This function simplifies the process of writing database inserts. It
returns a correctly formatted SQL insert string. Example: $data =
array('name' => $name, 'email' => $email, 'url' => $url);
$str = $this->db->insert_string('table_name', $data);
The first parameter is the table name, the second is an associative
array with the data to be inserted. The above example produces: INSERT
INTO table_name (name, email, url) VALUES ('Rick', 'rick#example.com',
'example.com')
So, in your case, you can have something like this:
<form action="" method="post">
<input type="text" name="username" value="testUser123" />
<input type="password" name="password" value="yourPass666" />
<input type="text" name="email" value="email#example.com" />
<input type="submit" value="submit" />
</form>
<?php
//... extending CI
//... opening a method
$table = 'data';
//comming from somewhere, let's dynamically populated array but for testing purpose I will hardcode:
$columns('username', 'password', 'email');
foreach($columns as $column) {
$data[$column] = $_POST[$column]; // this will produce $data=array('username'=>$_POST['username'],password=....);
}
$str = $this->db->insert_string($table, $data);
?>
If you submit the form in the beginning, you will have:
INSERT INTO data (username, password, email) VALUES ('testUser123', 'yourPass666', 'email#example.com');
The whole active record class doc (insert chosen here)
http://ellislab.com/codeigniter/user-guide/database/active_record.html#insert
If you don't have to stick to the for loop, I would suggest a foreach, which should be easier (I know the little problems with for too).
foreach ($element in $array)
{
code execution here
}
Your array element is then stored in the $element (or as you like to name it) and you can execute the command found there.
Is this what you're looking for or did I get you wrong?

Simple PDO update is not working

The problem is on :soundid if I type manually soundid='soundidfromPOST' received from POST, the row is updated, but with soundid=:soundid ... nothing. Why?
PDO::ATTR_ERRMODE to PDO::ERRMODE_EXCEPTION and error_reporting enabled.
public function save($args) {
$userid = Controller::getUserConnection();
if ($userid) {
$soundid = $_POST['soundid'];
$track_title = $_POST['track_title'];
$track_artist = $_POST['track_artist'];
$track_album = $_POST['track_album'];
$track_genre = $_POST['track_genre'];
$track_description = $_POST['track_description'];
$played = 1;
$statement = $this->_db->prepare("UPDATE sounds SET title=:track_title, artist=:track_artist, album=:track_album, genre_id=:track_genre, description=:track_description, played=:played WHERE soundid=:soundid AND userid=:userid AND ip=:ip");
$statement->bindParam(':soundid',$soundid,PDO::PARAM_STR);
$statement->bindParam(':userid',$userid,PDO::PARAM_INT);
$statement->bindParam(':track_title',$track_title,PDO::PARAM_STR);
$statement->bindParam(':track_artist',$track_artist,PDO::PARAM_STR);
$statement->bindParam(':track_album',$track_album,PDO::PARAM_STR);
$statement->bindParam(':track_genre',$track_genre,PDO::PARAM_INT);
$statement->bindParam(':track_description',$track_description,PDO::PARAM_STR);
$statement->bindParam(':ip',$_SERVER['REMOTE_ADDR'],PDO::PARAM_STR);
$statement->bindParam(':played',$played,PDO::PARAM_INT);
$statement->execute();
echo 'saved!';
}
}
I would do the following to make it cleaner and because you don't need to bind everything explicitly (please note I didn't use all your variables):
Assign all your post data that you want to use in the query to an array:
$data = array(
'userid' => $userid,
'sounddid' => $_POST['soundid'],
'track_title' => $_POST['track_title'],
'track_artist' => $_POST['track_title'],
'ip' => $_SERVER['REMOTE_ADDR'],
);
Write you query:
$sth = $this->_db->prepare("
UPDATE sounds SET
title = :track_title,
artist = :track_artist
WHERE soundid = :soundid
AND userid = :userid
AND ip = :ip
");
Pass in your data array to be executed:
$result = $sth->execute($data);

Convert mysqli to pdo bind_all

here is my current code
function getUserDetails($username=NULL, $id=NULL) {
if($username!=NULL) {
$column = "user_name";
$data = $username;
}
elseif($id!=NULL) {
$column = "id";
$data = $id;
}
global $db;
$query = $db->prepare("SELECT id, username, permissions, forename, surname, password, email, courseid, choiceid, lastlogin, active FROM users WHERE $column = :column");
$query->bindParam(":column", $data);
$query->execute();
$query->bind_result ($id, $username, $permissions, $forename, $surname, $password, $email, $courseid, $choiceid, $lastlogin, $active);
while ($query->fetch()){
$row = array('id' => $id, 'userlevel' => $permissions, 'username' => $username, 'forename' => $forename, 'surname' => $surname, 'password' => $password, 'email' => $email, 'courseId' => $courseid, 'choiceId' => $choiceId, 'lastlogin' => $lastlogin, 'active'=> $active);
}
return ($row);
}
I have been trying to convert this to pdo, as I've found out bind_result doesn't work with pdo - could anyone help me as to what I should be doing?
I've read arround that I should be using fetch? But i'm getting really confused.
[edit]
ive tried this:
function getUserDetails($username=NULL,$id=NULL) {
if($username!=NULL) {
$column = "user_name";
$data = $username;
}
elseif($id!=NULL) {
$column = "id";
$data = $id;
}
global $db;
$query = $db->prepare("SELECT id, username, permissions, forename, surname, password, email, courseid, choiceid, lastlogin, active FROM users WHERE $column = :column");
$query->bindParam(":column", $data);
$query->execute();
$results = array();
while ($row = $query->fetch(PDO::FETCH_ASSOC)) {
$results[] = $row;
}
return ($results);
}
is this a step in the right direction ?
[edit2]
updated my code to this:
function getUserDetails($username) {
global $db;
$query = $db->prepare("SELECT * FROM users WHERE username = :username");
$query->bindParam(":username", $username);
return $query->fetch(PDO::FETCH_ASSOC);
}
$username = 'uname';
$result = getUserDetails($username);
print_r($result);
however it prints nothing. the username definitely exists.
ive tried a test database with some dummy data
$data = '2';
$sth = $db->prepare("SELECT * FROM test WHERE id = :id");
$sth->bindParam(":id", $data);
$sth->execute();
$result = $sth->fetch(PDO::FETCH_ASSOC);
print_r($result);
im trying to figure out how i access what is in the printed array:
the array comes out as
Array ( [Id] => 2 [Name] => tom )
how do i (for example) do
$name = $result['name']; //line 67
when i try that code i get
Notice: Undefined index: name in <directory>\test.php on line 67
Figured it out!
function getUserDetails($username) {
global $db;
$sth = $db->prepare("SELECT id, username, permissions, forename, surname, password, email, courseid, choiceid, lastlogin, active FROM users WHERE username = :username");
$sth->bindParam(":username", $username);
$sth->execute();
$result = $sth->fetch(PDO::FETCH_ASSOC);
return $result;
}
$username = 'un';
$userdetails = getUserDetails($username);
echo $userdetails['forename'];
and it gives me the correct answer!
thanks for your help
YES!
It's great step in the right direction.
As you can see, mysqli is absolutely unusable with prepared statements, both in binding placeholders and returning results.
while PDO can solve your problem using dramatically less code.
You don't need useless bind with PDO at all - just get all results with fetchAll():
function getUserDetails($username=NULL,$id=NULL) {
if ($username) {
$column = "user_name";
$data = $username;
} elseif($id) {
$column = "id";
$data = $id;
} else {
return;
}
global $db;
$query = $db->prepare("SELECT * FROM users WHERE $column = ?");
$query->execute(array($data));
return $query->fetchAll();
}
But wait. Why do you want to return an array if it's users details?
It will add just useless dimension to the returned array.
For this very case make it
return $query->fetch();
instead of fetchAll().
But then you need many rows - use this latter method.
On other methods and useful connect options refer to the tag wiki

No data supplied for parameters in MySQLi prepared statement

I've been reworking my website from unprotected MySQL queries to mysqli prepared statements and it all went well until I got this: No data supplied for parameters in prepared statement.
if(empty($err)) {
$pSETQuery = NULL;
if(!empty($_POST['password'])) {
$pSETQuery .= ", password = ?";
}
if($session->isSuperuser()) {
$pSETQuery .= ", usertype = ?";
}
if(!($stmt = $database->prepare("UPDATE user SET username = ?, email = ? $pSETQuery WHERE UserId = ?"))) {
$err[] = "PREPARE FAILED.";
}
$stmt->bind_param("s", $_POST['username']);
$stmt->bind_param("s", $_POST['email']);
if(!empty($_POST['password'])) {
$stmt->bind_param("s", $_POST['password']);
}
if($session->isSuperuser()) {
$stmt->bind_param("s", $_POST['usertype']);
}
$stmt->bind_param("i", $_POST['userid']);
if(!$stmt->execute()){
$err[] = "Execute failed. ERROR: " . $stmt->error;
}
}
The error you are getting is becauses of these lines:
$stmt->bind_param("s", $_POST['username']);
$stmt->bind_param("s", $_POST['email']);
You should only call bind_param() once and you need to provide the same number of variadic variables as you have placeholders in the SQL. This function is not well designed, which is one of the main reasons people prefer PDO.
To solve the problem you need to dynamically prepare 3 things: placeholders, types and variables to bind. Here is how you could dynamically build such query:
if(empty($err)) {
$pSETQuery = '';
$types = 'sss'; // for the three constant placeholders
$data = [$_POST['username'], $_POST['email']];
if(!empty($_POST['password'])) {
$pSETQuery .= ", password = ?";
$types .= 's'; //concat one more
$data[] = $_POST['password'];
}
if($session->isSuperuser()) {
$pSETQuery .= ", usertype = ?";
$types .= 's'; //concat one more
$data[] = $_POST['usertype'];
}
$data[] = $_POST['userid']; // for UserId
$stmt = $database->prepare("UPDATE user SET username = ?, email = ? $pSETQuery WHERE UserId = ?");
$stmt->bind_param($types, ...$data);
$stmt->execute();
}
Do you use Zend Framework ?
It could be a version problem between Php and Zend.
I got the problem with PHP 5.3 + who got the same error on insert or update with Zend framework 1.8.3.
If you are in that case, one of the solutions is to change the connector to the database. Try this, it works for me :
$db = new Zend_Db_Adapter_Pdo_Mysql(array(
'host' => '127.0.0.1',
'username' => 'webuser',
'password' => 'xxxxxxxx',
'dbname' => 'test'
));
"No data supplied for parameters in prepared statement" means statement is ok but at least one of the vars you're providing to bind_param is not there as expected! i would print out $_POST and see what's going on and eventually set $pSETQuery = ''; and not to null!
$_POST['username']
$_POST['email']
$_POST['password']
$_POST['usertype']
$_POST['userid'] // this one is the one I would really watch after, how do you tell the userid if the user is not logged ( i assume that from email, passwrod and might be wrong)
I've just found a way to fix the same problem.
It was a value passed to MySQL, which was NULL. Whereas this column can't be NULL in table definition...

Categories