PDO Named parameters inside JSON - php

I have data that should be escaped inside a JSON formatted string, so I'm using PDO's named parameters and PDO::Prepare to bind them.
Because JSON with it's apostrophes causes errors in the MySQL query, i have to use single quotes around it - although this causes the PDO::Prepare to ignore the named parameters inside the JSON, so it fails with SQLSTATE[HY093]: Invalid parameter number: parameter was not defined.
Any ideas how to work around this?
function send($_data) {
global $_SESSION;
global $dbApi;
#These are temporary debug variables:
$_SESSION['room_id'] = 1;
$id = 124;
$json = '"' . $id . '": {"user_id": ":email","data": ":data"}';
$query = "UPDATE `room_index` " .
"SET `data` = JSON_ARRAY_INSERT(`data`, '$[0]', '" . $json . "') " .
"WHERE `id` = :room_id";
$dbApi->query($query, array(':email' => $_SESSION['email'],
':data' => $_data,
':room_id' => $_SESSION['room_id']));
}
To explain the code a bit, :email ($_SESSION['email']) doesn't have to be a parameter, but it's cleaner this way. The main issue is :data ($_data) - that is user input straight from a textarea via JS.
$dbApi is a class with a proper query function, that looks like this:
function query($_query, $_params = array()) {
global $_DB; # <- Database connection object
$query = $_DB->prepare($_query);
if (! $query)
echo $_DB->errorInfo();
try {
$query->execute($_params);
} catch (PDOException $e) {
die( $e->getMessage() );
}
return $query;
}

There are 2 issues with the code.
1. JSON_INSERT is more appropriate.
As I'm inserting a named object into another object (the top level document), the JSON_INSERT offers such a syntax straight away
2. Using JSON_OBJECT instead of manually writing the JSON syntax
As my main issue was, that PDO doesn't replace single, or double quoted parameters, the solution was using JSON_OBJECT, which doesn't require double quotes as they are automatically generated later (my assumption) - but after PDO replaces the variables and also places single quotes around them.
New, tested code outputting valid JSON:
#Temporary, to avoid other unrelated issues
$_SESSION['room_id'] = 1;
$_SESSION['email'] = 'email#email.com';
$id = 123;
$json = 'JSON_OBJECT("user_id", :email, "data", :data)';
$query = "UPDATE `room_index` " .
"SET `data` = JSON_INSERT(`data`, :id, $json) " .
"WHERE `id` = :room_id";
$dbApi->query($query, [':id' => "$." . $id,
':email' => $_SESSION['email'],
':data' => $_data,
':room_id' => $_SESSION['room_id']]);

Related

SQL INSERT function with PHP only

edit I changed the code to the suggestion answer, all snippets now updated
currently I am playing around with PHP. Therefore I am trying to build a programm which can execute SQL commands. so, what I am trying is to write some functions which will execute the query. But I came to a point where I coundn't help myself out. My trouble is, for the INSERT INTO command, I want to give an array, containing the Data that shall be inserted but I simply can't figure out how to do this.
Here is what I got and what I think is relevant for this operation
First, the function I want to create
public function actionInsert($data_values = array())
{
$db = $this->openDB();
if ($db) {
$fields = '';
$fields_value = '';
foreach ($data_values as $columnName => $columnValue) {
if ($fields != '') {
$fields .= ',';
$fields_value .= ',';
}
$fields .= $columnName;
$fields_value .= $columnValue;
}
$sqlInsert = 'INSERT INTO ' . $this->tabelle . ' (' . $fields . ') VALUES (' . $fields_value . ')';
$result = $db->query($sqlInsert);
echo $sqlInsert;
if ($result) {
echo "success";
} else {
echo "failed";
}
}
}
and this is how I fil the values
<?php
require_once 'funktionen.php';
$adresse = new \DB\Adressen();
$adresse->actionInsert(array('nachname'=>'hallo', 'vorname'=>'du'));
My result
INSERT INTO adressen (nachname,vorname) VALUES (hallo,du)failed
What I wish to see
success
and of course the freshly insertet values in the database
There are a few things to consider when you are working with relational databases without using PDO:
What is the database that you are using.
It's your decision to choose from MySQL, postgreSQL, SQLite and etc., but different DBs generally have different syntax for inserting and selecting data, as well as other operations. Also, you may need different classes and functions to interact with them.
That being said, did you checkout the official manual of PHP? For example, An overview of a PHP application that needs to interact with a MySQL database.
What is the GOAL you are trying to accomplish?
It's helpful to construct your SQL first before you are messing around with actual codes. Check if your SQL syntax is correct. If you can run your SQL in your database, then you can try to implement your code next.
What's the right way to form an SQL query in your code?
It's okay to mess around in your local development environment, but you should definitely learn how to use prepared statements to prevent possible SQL injection attacks.
Also learn more about arrays in PHP: Arrays in PHP. You can use key-value pairs in a foreach loop:
foreach ($keyed_array as $key => $value) {
//use your key and value here
}
You don't need to construct your query in the loop itself. You are only using the loop to construct the query fields string and VALUES string. Be very careful when you are constructing the VALUES list because your fields can have different types, and you should add double quotes around string field values. And YES, you will go through all these troubles when you are doing things "manually". If you are using query parameters or PDO or any other advanced driver, it could be much easier.
After that, you can just concatenate the values to form your SQL query.
Once you get more familiar with the language itself and the database you are playing with, you'll definitely feel more comfortable. Good luck!
Is this inside of a class? I assume the tabelle property is set correctly.
That said, you should correct the foreach loop, that's not used correctly:
public function actionInsert($data_values) //$data_values should be an array
{
$db = $this->openDB();
if ($db) {
foreach ($data_values as $data){
// $data_values could be a bidimensional array, like
// [
// [field1=> value1, field2 => value2, field3 => value3],
// [field1=> value4, field2 => value5, field3 => value6],
// [field1=> value7, field2 => value8, field3 => value9],
// ]
$fields = Array();
$values = Array();
foreach($data as $key => $value){
array_push($fields,$key);
array_push($values,"'$value'");
}
$sqlInsert = 'INSERT INTO ' . $this->tabelle . ' (' . join(',',$fields) . ') VALUES (' . join(',',$values) . ')';
$result = $db->query($sqlInsert);
echo $sqlInsert;
if ($result) {
echo "success";
} else {
echo "failed";
}
}
}
This is a rather basic approach, in which you cycle through you data and do a query for every row, but it isn't very performant if you have big datasets.
Another approach would be to do everything at once, by mounting the query in the loop and sending it later (note that the starting array is different):
public function actionInsert($data_values) //$data_values should be an array
{
$db = $this->openDB();
if ($db) {
$vals = Array();
foreach ($data_values['values'] as $data){
// $data_values could be an associative array, like
// [
// fields => ['field1','field2','field3'],
// values => [
// [value1,value2,value3],
// [value4,value5,value6],
// [value7,value8,value9]
// ]
// ]
array_push('('.join(',',"'$data'").')',$vals);
}
$sqlInsert = 'INSERT INTO ' . $this->tabelle . ' (' . join(',',$data_values['fields']) . ') VALUES '.join(' , ',$vals);
$result = $db->query($sqlInsert);
echo $sqlInsert;
if ($result) {
echo "success";
} else {
echo "failed";
}
}
By the way dragonthought is right, you should do some kind of sanitizing for good practice even if you don't make it public.
Thanks to #Eagle L's answer, I figured a way that finally works. It is diffrent from what I tryed first, but if anyone having similar troubles, I hope this helps him out.
//get the Values you need to insert as required parameters
public function actionInsert($nachname, $vorname, $plz, $wohnort, $strasse)
{
//database connection
$db = $this->openDB();
if ($db) {
//use a prepared statement
$insert = $db->prepare("INSERT INTO adressen (nachname, vorname, plz, wohnort, strasse) VALUES(?,?,?,?,?)");
//fill the Values
$insert->bind_param('ssiss', $nachname, $vorname, $plz, $wohnort, $strasse);
//but only if every Value is defined to avoid NULL fields in the Database
if ($vorname && $nachname && $plz && $wohnort && $strasse) {
edited
$inserted = $insert->execute(); //added $inserted
//this is still clumsy and user unfriendly but serves my needs
if ($inserted) {//changed $insert->execute() to $inserted
echo 'success';
} else {
echo 'failed' . $inserted->error;
}
}
}
}
and the Function call
<?php
require_once 'funktionen.php';
$adresse = new \DB\Adressen();
$adresse->actionInsert('valueWillBe$nachname', 'valueWillBe$vorname', 'valueWillBe$plz', 'valueWillBe$wohnort', '$valueWillBe$strasse');

Why does PDO 'execute' work irrespective of whether colon is included on a bound parameter variable?

I'm working on an application which uses PDO.
I've noticed that when binding parameters the query still works irrespective of whether I use or omit a colon on the bound parameter variable.
Example:
$sql = "SELECT * FROM `" . $this->table . "` WHERE id = :id LIMIT 1";
$stmt = $this->ci->db->prepare($sql);
$result = $stmt->execute([
"id" => $id,
]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
Gives exactly the same result as:
$sql = "SELECT * FROM `" . $this->table . "` WHERE id = :id LIMIT 1";
$stmt = $this->ci->db->prepare($sql);
$result = $stmt->execute([
":id" => $id,
]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
The difference (which might be hard to spot) is "id" => $id vs ":id" => $id
So, which should be used? And is it the expected behaviour for them to give the same output?
A quick glance at the PHP source code turns up this code in pdo_stmt.c (this is the current master branch, so PHP 7.1, but I imagine it's fundamentally the same in all versions):
if (param->name) {
if (is_param && ZSTR_VAL(param->name)[0] != ':') {
zend_string *temp = zend_string_alloc(ZSTR_LEN(param->name) + 1, 0);
ZSTR_VAL(temp)[0] = ':';
memmove(ZSTR_VAL(temp) + 1, ZSTR_VAL(param->name), ZSTR_LEN(param->name) + 1);
param->name = temp;
} else {
param->name = zend_string_init(ZSTR_VAL(param->name), ZSTR_LEN(param->name), 0);
}
}
Roughly, the key part can be read as:
if ( char 0 of param->name is not ':' ) {
set param->name to ':' concatenated with param->name
}
So, the form with the : on the front is the "correct" form, but if you pass any string without a : prefix, the PDO code will add one on internally, just to be user-friendly.
Note that this has nothing to do with how arrays work: the PHP array is just a way of getting a bunch of name-value pairs into the PDO code. It's the same as if an API accepted either the US spelling 'color' or the UK spelling 'colour' for a parameter; they're not "the same key", but the API can decide they have the same meaning.
In practice, the answer remains "use whichever you prefer".
Why does PDO query work irrespective
Because it's handy.
So, which should be used?
One that suits you best.
And is it the expected behaviour for them to give the same output?
Yes.

What defines the PHP error: Call to a member function fetch_object() on a non-object

I have been wondering on this question for a while. I have two PHP programs that are almost exactly identical except for the fact that on of them is a function and the other isn't. Furthermore one works and the other send back the error:
Call to a member function fetch_object() on a non-object
I fixed the one that wasn't working by omitting the variables and inserting definitive strings and adding $con->errno instead of mysqli_errno. However, when I replaced the strings with the variables again the problem returned.
So, my question is: what causes this error and how would I fix it. Also, why is the error coming up in the second code and not the first?
The First Code (works)
<?php
$con = new mysqli("database info");
if (mysqli_connect_errno())
{
echo "Failed to connect to MySQL: " . mysqli_connect_error();
}
$stmt = $con->query("SELECT coordinator, announcements, description, comments, picture FROM Class_data WHERE class_year = '" .$class. "';");
$result_row = $stmt->fetch_object();
$coordinator = $result_row->coordinator;
$announcement = $result_row->announcements;
$description = $result_row->description;
$comments = $result_row->comments;
$picturepath = $result_row->picture;
mysqli_error($con);
mysqli_close($con);
if ($picturepath == "")
{
$picturepath = "../images/AlumnLogo.png";
}
?>
Second Code (doesn't work)
<?php
function fetch($page, $content1, $content2, $content3)
{
$con = new mysqli("database info");
if ($con->errno)
{
echo "Failed to connect to MySQL: " . mysqli_connect_error();
}
$stmt = $con->query("SELECT '" .$content1. "' , '" .$content2. "' , '" .$content3. "' FROM '" .$page. "';");
$result_row = $stmt->fetch_object();
$content = array();
$content[0] = $result_row->body;
$content[1] = $result_row->calendar;
$content[2] = $result_row->announcements;
$output = implode("--",$content);
mysqli_error($con);
mysqli_close($con);
return $output;
}
?>
Thanks alot!
You are quoting your column names using single quotes. You cannot do that, you need to quote table- and column names using backticks (in case of reserved words, spaces, etc.) and only (non-integer...) values need to be quoted using single or double quotes.
Change your code to:
$stmt = $con->query("SELECT `" .$content1. "` , `" .$content2. "` , `" .$content3. "` FROM `" .$page. "`;");
^ All these
By the way, I assume you are using a white-list for your table- and column names. If not, you should to avoid sql injection.
It is also a good idea to add error handling to your database calls. An easy way using mysqli, is to put mysqli_report(MYSQLI_REPORT_STRICT); at the start of your script. This will cause mysqli to throw exceptions so that you do not have to check for individual errors on each database call.
The error message simply means that you try to call a method on something that is not an object. The problem is that $stmt is not an object!
This happens because there is an error in your SQL Statemetn. $con->query() retuns false in such a case. Therefore, what you try to execute is something like false->fetch_object();. And false isn't an object...
Try to print your generated SQL Statement. It will probabely contain some syntax errors. Fix those, and it will work

How do I prevent Zend from trying to bind my MySQL values?

I have a query like:
INSERT IGNORE INTO my_table SET `data` = '{\"m\":50}'
Granted the JSON data is much larger in my real query, I always get the error:
Zend_Db_Statement_Exception: Invalid bind-variable name ':50'
This is when I do $connection->query( $sql );
In the past I've solved this by using single quotes rather than double quotes around my values, but for some reason it's not working now. What am I missing?
EDIT
On top of the accepted answer, here is the code I used to make sure I can still pass new Zend_Db_Expr("NOW()") to my function, but have something like the JSON data prepared properly.
foreach ( $params as $key => $value ) {
// Can't use ? for anything that requires an expression, such as NOW()
if ( $value instanceof Zend_Db_Expr ) {
$db_keys[] = $connection->quoteInto( "`{$key}` = ?", $value );
}
else {
$db_values[] = $value;
$db_keys[] = "`{$key}` = ?";
}
} // foreach params
$sql = "INSERT IGNORE INTO {$table} SET " . implode( ', ', $db_keys );
$result = $connection->query( $sql, $db_values );
Don't embed data like that in a query string. As you're finding out, it's subject to mis-interpretation as an invalid placeholder. Use a prepared statement with placeholders instead:
$stmt = $db->query("INSERT IGNORE ... `data` = ?", array('{"m":50}'));
^----placeholder

Surround string with quotes

Is there a function in PHP that adds quotes to a string?
like "'".str."'"
This is for a sql query with varchars. I searched a little, without result...
I do the following:
$id = "NULL";
$company_name = $_POST['company_name'];
$country = $_POST['country'];
$chat_language = $_POST['chat_language'];
$contact_firstname = $_POST['contact_firstname'];
$contact_lastname = $_POST['contact_lastname'];
$email = $_POST['email'];
$tel_fix = $_POST['tel_fix'];
$tel_mob = $_POST['tel_mob'];
$address = $_POST['address'];
$rating = $_POST['rating'];
$company_name = "'".mysql_real_escape_string(stripslashes($company_name))."'";
$country = "'".mysql_real_escape_string(stripslashes($country))."'";
$chat_language = "'".mysql_real_escape_string(stripslashes($chat_language))."'";
$contact_firstname = "'".mysql_real_escape_string(stripslashes($contact_firstname))."'";
$contact_lastname = "'".mysql_real_escape_string(stripslashes($contact_lastname))."'";
$email = "'".mysql_real_escape_string(stripslashes($email))."'";
$tel_fix = "'".mysql_real_escape_string(stripslashes($tel_fix))."'";
$tel_mob = "'".mysql_real_escape_string(stripslashes($tel_mob))."'";
$address = "'".mysql_real_escape_string(stripslashes($address))."'";
$rating = mysql_real_escape_string(stripslashes($rating));
$array = array($id, $company_name, $country, $chat_language, $contact_firstname,
$contact_lastname, $email, $tel_fix, $tel_mob, $address, $rating);
$values = implode(", ", $array);
$query = "insert into COMPANIES values(".$values.");";
Rather than inserting the value directly into the query, use prepared statements and parameters, which aren't vulnerable to SQL injection.
$query = $db->prepare('SELECT name,location FROM events WHERE date >= ?');
$query->execute(array($startDate));
$insertContact = $db->prepare('INSERT INTO companies (company_name, country, ...) VALUES (?, ?, ...)');
$insertContact->execute(array('SMERSH', 'USSR', ...));
Creating a PDO object (which also connects to the DB and is thus a counterpart to mysql_connect) is simple:
$db = new PDO('mysql:host=localhost;dbname=db', 'user', 'passwd');
You shouldn't scatter this in every script where you want a DB connection. For one thing, it's more of a security risk. For another, your code will be more susceptible to typos. The solution addresses both issues: create a function or method that sets up the DB connection. For example:
function localDBconnect($dbName='...') {
static $db = array();
if (is_null($db[$dbName])) {
$db[$dbName] = new PDO("mysql:host=localhost;dbname=$dbName", 'user', 'passwd');
$db[$dbName]->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
return $db[$dbName];
}
If you're working with an array of more than two or three elements, you should use loops or array functions rather than a long sequence of similar statements, as is done in the sample code. For example, most of your sample can be replaced with:
$array = array();
foreach ($_POST as $key => $val) {
$array[$key] = "'" . mysql_real_escape_string(stripslashes($val)) . "'";
}
Here's a more comprehensive example of creating an insert query. It's far from production ready, but it illustrates the basics.
$db = localDBconnect();
// map input fields to table fields
$fields = array(
'company' => 'company_name',
'country' => 'country',
'lang' => 'chat_language',
'fname' => 'contact_firstname',
'lname' => 'contact_lastname',
'email' => 'email',
'land' => 'tel_fix',
'mobile' => 'tel_mob',
'addr' => 'address',
'rating' => 'rating',
);
if ($missing = array_diff_key($fields, $_POST)) {
// Form is missing some fields, or request doesn't come from the form.
...
} else {
$registration = array_intersect_key($_POST, $fields);
$stmt = 'INSERT INTO `dbname`.`Companies` (`'
. implode('`, `', $fields) . '`) VALUES ('
. implode(', ', array_fill(0, count($registration), '?')) . ')';
try {
$query = $db->prepare($stmt);
$query->execute(array_values($registration));
} catch (PDOException $exc) {
// log an
error_log($exc);
echo "An error occurred. It's been logged, and we'll look into it.";
}
}
To make it production ready, the code should be refactored into functions or classes that hide everything database related from the rest of the code; this is called a "data access layer". The use of $fields shows one way of writing code that will work for arbitrary table structures. Look up "Model-View-Controller" architectures for more information. Also, validation should be performed.
Firstly, I see you're using stripslashes(). That implies you have magic quotes on. I would suggest turning that off.
What you might want to do is put some of this in a function:
function post($name, $string = true) {
$ret = mysql_real_escape_string(stripslashes($_POST[$name]));
return $string ? "'" . $ret . "'" : $ret;
}
and then:
$company_name = post('company_name');
All this does however is reduce the amount of boilerplate you have slightly.
Some have suggested using PDO or mysqli for this just so you can use prepared statements. While they can be useful it's certainly not necessary. You're escaping the fields so claims of vulnerability to SQL injection (at least in the case of this code) are misguided.
Lastly, I wouldn't construct a query this way. For one thing it's relying on columns in the companies table being of a particular type and order. It's far better to be explicit about this. I usually do this:
$name = mysql_real_escape_string($_POST['name']);
// etc
$sql = <<<END
INSERT INTO companies
(name, country, chat_language)
VALUES
($name, $country, $language)
END;
That will sufficient for the task. You can of course investigate using either mysqli or PDO but it's not necessary.
Thought I'd contribute an option that answers the question of "Is there a function in PHP that adds quotes to a string?" - yes, you can use str_pad(), although it's probably easier to do it manually.
Benefits of doing it with this function are that you could also pass a character to wrap around the variable natively within PHP:
function str_wrap($string = '', $char = '"')
{
return str_pad($string, strlen($string) + 2, $char, STR_PAD_BOTH);
}
echo str_wrap('hello world'); // "hello world"
echo str_wrap('hello world', '#'); // #hello world#
Create your own.
function addQuotes($str){
return "'$str'";
}
Don't do this. Instead use parametrized queries, such as those with PDO.
This isn't a function - but it's the first post that comes up on google when you type "php wrap string in quotes". If someone just wants to wrap an existing string in quotes, without running it through a function first, here is the correct syntax:
echo $var // hello
$var = '"'.$var.'"';
echo $var // "hello"

Categories