Parametrized PDO query for LIMIT clause - php

I have seen similar questions answered already but I can't seem to apply the same solutions to my code.
$a=1;
$results = DB::query('SELECT posts.`postbody`, posts.`filepost`, posts.`likes`, posts.`posted_at`, users.`id`, posts.`id_of_post` FROM posts, users WHERE posts.`post_id` = users.`id` ORDER BY id_of_post DESC LIMIT :a', array(':a'=>$a));
class DB {
private static function connect() {
$pdo = new PDO('mysql:host=127.0.0.1;dbname=SocialNetwork;charset=utf8', 'root', '');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $pdo;
}
public static function query($query, $params = array()) {
$statement = self::connect()->prepare($query);
$statement->execute($params);
if (explode(' ', $query)[0] == 'SELECT') {
$data = $statement->fetchAll();
return $data;
}
}
}
For the record the following code works fine.
$results = DB::query('SELECT posts.`postbody`, posts.`filepost`, posts.`likes`, posts.`posted_at`, users.`id`, posts.`id_of_post` FROM posts, users WHERE posts.`post_id` = users.`id` ORDER BY id_of_post DESC LIMIT 1');

Not ideal, but you could do away with the PDO parameters.
$a = 1;
$sql = "SELECT stuff FROM table LIMIT {$a};";
Then run your query from the $sql string.

As stated in the previous answers if you do not define:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
You have to define the parameter to be binded as integer:
foreach($params as $key => $value){
if(is_numeric($value))
$statement->bindParam($key,$value,PDO::PARAM_INT);
else
$statement->bindParam($key,$value,PDO::PARAM_STR);
}
$statement->execute();
This is still not a perfect solution, but if you trust the key value pairs(ie they are from code, not user input) it's good enough.

In MySQL's LIMIT clause, it's an error to do this:
LIMIT '1'
Because LIMIT must take an integer, not a string.
If PDO is configured to emulate prepare() (by interpolating values into your SQL string), it's likely to make the interpolated value a quoted string, which will cause an error.
To avoid this, you must use a native integer as your bound variable and you just specify PDO::PARAM_INT.
$statement = self::connect()->prepare($query);
$statement->bindParam('a', $a, PDO::PARAM_INT);
$statement->execute();
That will let the driver know to avoid putting quotes around the interpolated value.
You can also avoid the error if you set the PDO attribute to disable emulated prepares. I always do this, because I don't trust "emulated prepare."
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
See also my tests I wrote up here: Parametrized PDO query and `LIMIT` clause - not working

Related

Converting regular mysql into prepared statements

Im new to database and i have written a LOT of PHP code that accesses a database using MySQL.
I didnt take into account SQL injection attacks so i have to re-write all that PHP code to use mysql prepared statements.
After looking at videos on how to used prepared SQL statements, to perform just ONE SQL command requires a whole lot of "prepared" statements. My existing code has lots of different SQL statements all over the place, it would be a nightmare to change all that code to pack and unpack all the required preparation for each "prepared" statement command.
Is there some kind of wrapper i can use to prevent turning one line of regular SQL into 6 or 7 lines of prepared statements?
For example use to do this line line of SQL
SELECT * from users where userid=10
needs many more lines of prepared SQL statements, especially if there are lots of other SQL statements too it now becomes very complex.
Is there was some sort of one line wrapper that i can call that accepts the template SQL string, plus the parameters, which also executes the command and returns the result in just one line of wrapper for different types of MYSQL statements it would be great and the code would be much less confusing looking and error prone.
For example
$users=WrapAndExecute($db,"SELECT * from users where userid=?","s",$userid);
$data=WrapAndExecute($db,"UPDATE table SET username=?,city=?","ss",$name,$city);
$result=WrapAndExecute($db,"DELETE from table where id=?","s",$userid);
$result=WrapAndExecute($db,"INSERT into ? (name,address) VALUES(?,?)","ss","users",$name,$address);
Each of those lines above would create a prepared statement template, do the bind, execute it and return the result that a regular MYSQL statement would. This would create minimal impact on existing code.
Anybody knows how to do this or if some easy php library or class already exists to do this, that i can just import and start using it?
Thanks
You don't need to change a query to a prepared statement if it has no PHP variables in it. If it has just constant expressions, it's safe from SQL injection.
$sql = "SELECT * from users where userid=10"; // Safe!
$stmt = $pdo->query($sql);
$data = $stmt->fetchAll();
You don't need to change a query that contains PHP variables, as long as the value of that variable is a constant specified in your code. If it doesn't take its value from any external source, it's safe.
$uid = 10;
$sql = "SELECT * from users where userid=$uid"; // Safe!
$stmt = $pdo->query($sql);
$data = $stmt->fetchAll();
You don't need to change a query that contains PHP variables, as long as you can filter the value to guarantee that it won't risk an SQL injection. A quick and easy way to do this is to cast it to an integer (if it's supposed to be an integer).
$uid = (int) $_GET['uid'];
$sql = "SELECT * from users where userid=$uid"; // Safe!
$stmt = $pdo->query($sql);
$data = $stmt->fetchAll();
That leaves cases where you are using "untrusted" values, which may have originated from user input, or reading a file, or even reading from the database. In those cases, parameters are the most reliable way to protect yourself. It's pretty easy:
$sql = "SELECT * from users where userid=?"; // Safe!
// two lines instead of the one line query()
$stmt = $pdo->prepare($sql);
$stmt->execute([$_GET['uid']]);
$data = $stmt->fetchAll();
In a subset of cases, you need one additional line of code than you would normally use.
So quit your whining! ;-)
Re your comment about doing prepared statements in mysqli.
The way they bind variables is harder to use than PDO. I don't like the examples given in http://php.net/manual/en/mysqli.prepare.php
Here's an easier way with mysqli:
$sql = "SELECT * from users where userid=?"; // Safe!
$stmt = $mysqli->prepare($sql);
$stmt->bind_param('i', $_GET['uid']);
$stmt->execute();
$result = $stmt->get_result();
$data = $result->fetch_all();
I don't like the stuff they do in their examples with bind_result(), that's confusing and unnecessary. Just use get_result(). So with mysqli, you need two more lines of code than you would with PDO.
I've written query wrappers for mysqli that emulate the convenience of PDO's execute() function. It's a PITA to get an array mapped to the variable-arguments style of bind_param().
See the solution in my answers to https://stackoverflow.com/a/15933696/20860 or https://stackoverflow.com/a/7383439/20860
I were in the same boat, and I wrote such a wrapper that works exactly the way you want, save for it's being a class, not a function.
$user = $sdb->getRow("SELECT * from users where userid=?s", $userid);
$sdb->query("UPDATE table SET username=?s, city=?s", $name, $city);
$sdb->query("DELETE from table where id=?s", $userid);
$sdb->query("INSERT into ?n (name,address) VALUES(?s,?s)","users", $name, $address);
The above is a working code, as long as you have somewhere in your bootstrap file
$db = mysqli_connect(...);
...
require 'safemysql.class.php';
$sdb = new SafeMySQL('mysqli' => $db);
Note that none of the other suggestions could do anything like that.
Also note that if I were writing it today, I would have used PDO, as this class is duplicating a lot of functionality already exists in PDO.
Take a look at the PDO extension in PHP - http://php.net/manual/en/intro.pdo.php: it it secure against injections thanks to prepared statements; also, it allows you to connect to many different databases (e.g. MySQL, MSSQL, etc.).
You can then build your own wrapper as you wish to keep it clean; for example your own wrapper could be as follows:
(following example will return user rows as objects)
// connect to DB
$GLOBALS['default_db'] = new DB('localhost','db_name','username','password') ;
// Get users and output results
$query = new DBQuery('SELECT * FROM users WHERE userid = ?',array(10)) ;
var_dump($query -> results()) ;
var_dump($query -> num_rows()) ;
// DB connection
class DB {
public $connection;
public function __construct($host , $dbname , $username , $password) {
$this->connection = new \PDO('mysql:host=' . $host . ';dbname=' . $dbname , $username , $password);
}
}
// Wrapper
class DBQuery {
private $num_rows = 0;
private $results = array();
public function __construct($query , $params = null , $class_name = null , DB $db = null) {
if ( is_null($db) ) {
$db = $GLOBALS['default_db'];
}
$statement = $db->connection->prepare($query);
$statement->execute($params);
$errors = $statement->errorInfo();
if ( $errors[2] ) {
throw new \Exception($errors[2]);
}
$fetch_style = ($class_name ? \PDO::FETCH_CLASS : \PDO::FETCH_OBJ);
$this->results = $class_name ? $statement->fetchAll($fetch_style , $class_name) : $statement->fetchAll($fetch_style);
$this->num_rows += $statement->rowCount();
while ( $statement->nextrowset() ) {
$this->results = array_merge($this->results,$class_name ? $statement->fetchAll($fetch_style , $class_name) : $statement->fetchAll($fetch_style));
$this->num_rows += $statement->rowCount();
}
}
public function num_rows() {
return $this->num_rows;
}
public function results() {
return $this->results;
}
}
Since a key requirement seems to be that you can implement this with minimal impact on your current codebase, it would have been helpful if you had told us what interface you currently use for running your queries.
While you could use PDO:
that means an awful lot of work if you are not already using PDO
PDO exceptions are horrible
Assuming you are using procedural mysqli (and have a good reason not to use mysqli_prepare()) its not that hard to write something (not tested!):
function wrapAndExecute()
{
$args=func_get_args();
$db=array_shift($args);
$stmt=array_shift($args);
$stmt_parts=explode('?', $stmt);
if (count($args)+1!=count($stmt_parts)) {
trigger_error("Argument count does not match placeholder count");
return false;
}
$real_statement=array_shift($stmt_parts);
foreach ($args as $k=>$val) {
if (isnull($val)) {
$val='NULL';
} else if (!is_numeric($val)) {
$val="'" . mysqli_real_escape_string($db, $val) . "'";
}
$real_statement.=$val . array_shift($stmt_parts);
}
return mysqli_query($db, $real_statement);
}
Note that this does not handle IS [NOT] NULL nicely nor a literal '?' in the statement nor booleans (but these are trivial to fix).

Variable binding in aura/sqlquery when using mysqli_* connection

I've got a legacy app that uses mysqli_*() functions (actually, it uses mysql_*() functions. Gah!). I am using aura/sqlquery as a SQL query generator. For example:
$queryFactory = new Aura\SqlQuery\QueryFactory('mysql');
$select = $queryFactory->newSelect();
$select->from('sometable AS t')
->where('t.field1 = 0')
->where("t.field2 <> ''");
Then we get the raw SQL by casting to string:
$sql = (string) $select;
Now I want to do do some variable binding in a where():
$select->where('t.somefield = ?', $somevalue);
When I cast to string, the escaping/binding never seems to be occur. It appears that the binding only takes place when one uses PDO and prepared statements.
Any ideas how to get variable binding in aura/sqlquery when using a mysqli connection?
If your PHP version is >= 5.6, here is a function that you can use to run a query from aura/sqlquery against mysqli
function mysqli_query_params($mysqli, $query, $params, $types = NULL)
{
$statement = $mysqli->prepare($select);
$types = $types ?: str_repeat('s', count($params));
$statement->bind_param($types, ...$params);
$statement->execute();
return $statement;
}
used like this
mysqli_query_params($mysqli, $select->getStatement(), $select->getBindValues())
You can use $select->getBindValues() to get the bind values.
I will say make use of Aura.Sql than pdo for it helps you in certain other cases like IN () query.
Taking an example from readme.
// a PDO connection
$pdo = new PDO(...);
// prepare the statment
$sth = $pdo->prepare($select->getStatement());
// bind the values and execute
$sth->execute($select->getBindValues());
Let me know in case you need more clarification for the same.
Thank you.

MySQLi dynamic prepared function with array binding

I am attempting to create a database query function which can take multiple parameters and be reused elsewhere, however tried a number of methods online similar to my approach and they are not working as expected.
function query($query, $bindings, $type)
I want to be able to run queries on the go with this single function, this type of function is a lot easier with the PDO driver as you could simply enter the binding inside ->execute($binding); however in this case I am forced to use MySQLi as the application currrently relies on it but wanting to upgrade it to use prepared statements.
An example of how I need to be able to use the function to ensure it is reusable and flexible:
$engine->query("SELECT * FROM accounts WHERE email = :mail AND id = :id",array(':mail' => $_POST['mail'], ':id' => 2))->fetch_assoc();
Lets split each of them up. First is the statement, then the array which is used to bind the parameters used, then the types of the parameters, and finally the type of fetch_ to utilize on the query (ASSOC, OBJECT, ARRAY) etc.
"SELECT * FROM accounts WHERE email = :mail AND id = :id",
array(':mail' => $_POST['mail'], ':id' => 2),
"si"
->fetch_assoc();
though to implement named parameters would be quite a task, the rest is pretty doable.
A PHP >= 5.6 variant, implementing splat operator:
function query($query, $params = NULL, $types = NULL)
{
if (!$params)
{
return $mysqli->query($query);
}
$statement = $this->mysqli->prepare($select);
$types = $types ?: str_repeat('s', count($params));
$statement->bind_param($types, ...$params);
$statement->execute();
return $statement->get_result();
}
used like this
$sql = "SELECT * FROM accounts WHERE email = ? AND id = ?";
$row = $engine->query($sql, [$_POST['mail'], 2])->fetch_assoc();
or, if you want to set types explicitly
$row = $engine->query($sql, [$_POST['mail'], 2], "si")->fetch_assoc();

Converting this function to PDO

I'm working on convert this on PDO
function SystemConfig($str)
{
$tmp = mysql_query("SELECT ".$str." FROM server_status LIMIT 1") or die(mysql_error());
$tmp = mysql_fetch_assoc($tmp);
return $tmp[$str];
}
I tried this :
function SystemConfig($str)
{
global $bdd;
$tmp = $bdd->prepare("SELECT ? FROM server_status LIMIT 1");
$tmp->bindValue(1, $str, PDO::PARAM_INT);
$tmp->execute();
$tmp_res = $tmp->fetch(PDO::FETCH_ASSOC);
return $tmp_res[$str];
}
?>
But it's return 'users_online' and not the value (10000 on the database) (PS : SystemConfig('users_online');)
Someone can help me ?
Sincerly,
function SystemConfig($str)
{
static $row;
global $bdd;
if (!$row)
{
$stm = $bdd->query("SELECT * FROM server_status");
$row = $stm->fetch();
}
return $row[$str];
}
but better reorganize your table, because table with sole row is a nonsense
function SystemConfig($str)
{
global $bdd;
$stm = $bdd->prepare("SELECT value FROM server_status WHERE param = ?");
$stm->execute(array($str))
return $stm->fetchColumn();
}
You can only bind value parameters with PDO. You cannot bind column names, table names, or anything else.
You'd still have to manually construct this query by concatenating strings. To make cracker's life harder use a whitelisting approach:
if(!in_array($str, array('users_online', 'users_offline', 'free_memory')) die('...')
// rest of the function.
(But please, stop using the deprecated mysql_* functions. Upgrade to mysqli instead. Or use PDO, but without parameter binding for this one single query)

Error while using PDO prepared statements and LIMIT in query [duplicate]

This question already has answers here:
How to apply bindValue method in LIMIT clause?
(11 answers)
Closed 7 years ago.
I'm using PDO in my application. But I have a problem while I'm working with prepared statements in a query that contains LIMIT. What's the problem?
Codes:
$start = 0;
$rows = 20;
$sql = "SELECT * FROM tbl_news ORDER BY date DESC LIMIT ?, ?";
$q = $db->prepare($sql);
$q->execute(array($start , $rows));
Error:
check the manual that corresponds to your MySQL server version for the right syntax to use near ''0', '20''
You can do like this:
$sql = SELECT * FROM tbl_news ORDER BY date DESC LIMIT :start, :rows";
$q = $db->prepare($sql);
$q->bindParam(':start', $start, PDO::PARAM_INT);
$q->bindParam(':rows',$rows, PDO::PARAM_INT);
Regarding to post LIMIT keyword on MySQL with prepared statement , the code below could solve my problem.
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, FALSE);
Thanks Álvaro G. Vicario and Maerlyn
It is a known bug which was fixed in 5.5.6 from memory.
From the article:
LIMIT doesn't allow variables in any context.
Its arguments must be integer constants.
Further Edit: (There is contention on the matter)
User variables are accepted arguments of LIMIT clause in prepared statements, and SQL syntax for prepared statements can be used in stored procedures.
Third Edit:
This link explains that these should work with prepared statements.
I just stumbled upon the same problem. For me, using my own statement class (extending PDOStatement) with my own execute() method fixed it.
This is the class:
class MyPDOStatement extends PDOStatement {
public function execute($input_parameters = null) {
if (is_array($input_parameters)) {
$i = 1;
foreach ($input_parameters as $p) {
// default to string datatype
$parameterType = PDO::PARAM_STR;
// now let's see if there is something more appropriate
if (is_bool($p)) {
$parameterType = PDO::PARAM_BOOL;
} elseif (is_null($p)) {
$parameterType = PDO::PARAM_NULL;
} elseif (is_int($p)) {
$parameterType = PDO::PARAM_INT;
}
// parameters passed to execute() are input-only parameters, so use
// bindValue()
$this->bindValue($i, $p, $parameterType);
$i++;
}
}
return parent::execute();
}
}
To tell PDO to use this statement class instead of the default one, do this:
$db = new PDO(...);
$db->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('MyPDOStatement'));
Now the code in the question will work:
$start = 0;
$rows = 20;
$sql = "SELECT * FROM tbl_news ORDER BY date DESC LIMIT ?, ?";
$q = $db->prepare($sql);
$q->execute(array($start , $rows));
The only thing you have to make shure is that the variables bound to the statement have the correct type, integer. If you have a numeric string, e.g. from the $_GET array, you can do something like this:
if (isset($_GET['start']) && is_numeric($_GET['start'])
&& is_int($_GET['start'] + 0) {
$start = (int) $_GET['start'];
}
I'm not shure if there is an easier way for the last thing, but at least it works fine for me.
date is a reserved word you have to wrap it with back-ticks

Categories