Uncaught exception 'PDOException' with message 'SQLSTATE[HY093]: Invalid parameter number' - php

I'm receiving an error whenever I attempt to insert into my database using PDO.
public function save($primaryKey = "") {
$validate = $this->rules();
if ($validate === true) {
$properties = '';
$values = '';
$bindings = array();
$update = '';
foreach ($this as $property => $value){
if ($property === "conn") {
continue;
}
$properties .= $property . ',';
$values .= ':' . $property . ',';
$update .= $property . ' = :' . $property . ',';
$bindings[':'.$property] = $value;
}
$sql_string = 'INSERT INTO ' . get_class($this) . ' (' . rtrim($properties, ',') . ') ';
$sql_string .= 'VALUES (' . rtrim($values, ',') . ') ON DUPLICATE KEY UPDATE ' . rtrim($update, ',') . ';';
$result = $this->executeQuery(NULL, $sql_string, $bindings);
$this->buildObject($result);
if (!empty($primaryKey)) {
$this->$primaryKey = $this->conn->lastInsertId();
}
return $result;
} else {
return $validate;
}
}
public function executeQuery($object, $sql_string, $bindings = null) {
$stmt = $this->conn->prepare($sql_string);
if (!empty($bindings)) {
if (!$stmt->execute($bindings)) {return false;}
} else {
if (!$stmt->execute()) {return false;}
}
$result = (!empty($object) ? $stmt->fetchAll(PDO::FETCH_CLASS, $object) : $stmt->fetchAll());
return (($stmt->rowCount() > 0) ? $result : false);
}
The save function generates both the query string and the bindings which both seem correct.
query = INSERT INTO am_administrator (firstName,lastName,username,password,email,isSuperUser,dateCreated,dateLastModified) VALUES (:firstName,:lastName,:username,:password,:email,:isSuperUser,:dateCreated,:dateLastModified) ON DUPLICATE KEY UPDATE firstName = :firstName,lastName = :lastName,username = :username,password = :password,email = :email,isSuperUser = :isSuperUser,dateCreated = :dateCreated,dateLastModified = :dateLastModified;
bindings = array(8) {
[":firstName"]=> string(5) "First"
[":lastName"]=> string(4) "Last"
[":username"]=> string(7) "cova-fl"
[":password"]=> string(8) "password"
[":email"]=> string(16) "test#testing.com"
[":isSuperUser"]=> int(1) "1"
[":dateCreated"]=> string(19) "2016-05-11 02:40:15"
[":dateLastModified"]=> string(19) "2016-05-11 02:40:15"
}
Whenever I put the query into workbench I have no problems, but when trying to run it in code I get Fatal Error: Uncaught exception 'PDOException' with message 'SQLSTATE[HY093]: Invalid parameter number' which confuses me since the number of bindings params matches the bindings keys and nummbers. Can anyone enlighten me on this issue?

I think this might be because you have decared each binding twice in the statement e.g. :firstname appears in the VALUES clause as well as the ON DUPLICATE KEY UPDATE clause.
You only pass 8 bindings to the $stmt->execute but PDO is looking for 16.
You could try naming them slightly different in the ON DUPLICATE KEY UPDATE clause giving you a query such as e.g.
INSERT INTO am_administrator (firstName,lastName,username,password,email,isSuperUser,dateCreated,dateLastModified) VALUES (:firstName,:lastName,:username,:password,:email,:isSuperUser,:dateCreated,:dateLastModified) ON DUPLICATE KEY UPDATE firstName = :update_firstName,lastName = :update_lastName,username = :update_username,password = :update_password,email = :update_email,isSuperUser = :update_isSuperUser,dateCreated = :update_dateCreated,dateLastModified = :update_dateLastModified;

Related

PHP reproduce exact working sql query but no result

I've got a stored procedure which looks like this:
-- GET INVITES
DROP PROCEDURE IF EXISTS pda.get_invite;
DELIMITER //
CREATE PROCEDURE pda.get_invite ( code varchar(100), invitator_id bigint )
BEGIN
SELECT * FROM pda_invites
WHERE (pda_invites.invitator_id = invitator_id || invitator_id IS NULL)
AND (pda_invites.code = code || code IS NULL);
END //
CALL get_invite ( 'cec95191-23db-11e9-86d7-26374278e97f', NULL );
That works in phpmyadmin and deliver the desired result.
Now on php side, I've got some kind of query builder in a procedure class with registered arguments, which looks like this:
public function toSQL ( ) {
// Make sure params are set
if ( $this->ready == false ) {
throw new Exception ( 'Called unready procedure ' . $this->PROCEDURE_NAME . '.' );
return false;
}
// Build query
$sql= 'CALL ' . $this->PROCEDURE_NAME . '( ';
$i = 0;
foreach ( $this->args as $arg ) {
$param = $this->params[ $i ][ $arg['NAME'] ];
// Apply valid null values
if ( $param == null || $param == 'null' ) {
$sql = $sql . 'NULL';
// Apply varchar strings
} else if ( $arg['TYPE'] == 'varchar' ) {
$sql = $sql . '\'' . $param . '\'';
// Apply numeric values
} else {
$sql = $sql . $param;
}
if ( ($i+1) < $this->args_count ) {
$sql = $sql . ', ';
}
$i++;
}
$sql = $sql . ' );';
return $sql;
}
In my DB Service I trigger that by calling:
$sql = $procedure->toSQL();
$result = $con->query($sql);
echo($sql);
echo json_encode($result);
That results in following equivalent query string which is also reflected by the mysql log:
CALL get_invite( 'cec95191-23db-11e9-86d7-26374278e97f', NULL );
But the problem and question is that it produces an empty result:
{"current_field":null,"field_count":null,"lengths":null,"num_rows":null,"type":null}
Have anyone a clue why it coulds behave this way?

PHP get Array(s) when is directly defined in $string

When is Array defined in string (like in sample below), is possible get the information of this Arrays?
$id = 1;
$query =
"
SELECT *
FROM category
WHERE
id > " . [$id, 'int'] . " AND
title LIKE " . ['%test%', 'str'] . "
ORDER BY id ASC
";
$select = sql ($query);
var_dump($query) // output
//SELECT * FROM category WHERE id > Array AND title LIKE Array ORDER BY id ASC
/*
var_dump ($query->my_defined_arrays_are)
[0] => 1
[1] => '%test%'
*/
i need this for better orientation in most complex queries for $sql->bind_param();
SOLUTION:
I created small function bind() which create and store the bind data and then send it to the sql() function where are processed. This works on all queries types (SELECT, INSERT, UPDATE, DELETE..etc.).
Important for me is clear and easy readable code.
Query:
$query =
"
SELECT *
FROM category
WHERE
id > " . bind('1', 'int') . " AND
title LIKE " . bind('%test%', 'str') . "
ORDER BY id ASC
";
$select = sql ($query, $bind);
/*
var_dump ($query)
SELECT * FROM category WHERE id > {Bind_Array} AND title LIKE {Bind_Array} ORDER BY id ASC
*/
Functions:
function bind($param, $type) {
global $bind;
$bind[] = $param . ', ' . $type;
return '{Bind_Array}';
}
function sql($query, $bind_param = []) {
global $settings, $bind;
$bind = null; // clean variable; must be empty for all next operations
$mysqli = new mysqli (
$settings['db_hostname'],
$settings['db_username'],
$settings['db_password'],
$settings['db_database']
);
if ($mysqli->connect_errno) {
echo 'Failed to connect to MySQL: (' . $mysqli->connect_errno . ') ' . $mysqli->connect_error;
return;
}
if (!$mysqli->set_charset('utf8')) {
echo 'Error loading character set utf8: (' . $mysqli->character_set_name() . ') ' . $mysqli->error;
return;
}
if ($bind_param != null) {
unset ($tmp_bind, $tmp_type);
// reserve first key
$tmp_bind[0] = null;
// replace all occurences string in query
$query = preg_replace ('/({Bind_Array})/', ' ?', $query);
// create parameters from array
foreach ($bind_param as $params) {
// explode and clean parametres
$param = array_map ('trim', explode (',', $params));
if (count ($param) != 2) {
echo 'Too much or less parameters!';
return;
}
// first is content
$tmp_bind[] = $param[0];
// second is type; create right format
$tmp_type .= str_replace (array ('int', 'str', 'double'), array ('i', 's', 'd'), $param[1]);
}
// set types to the first reserved key
$tmp_bind[0] = $tmp_type;
// replace previdous incoming data with new created
$bind_param = $tmp_bind;
}
if (!($sql = $mysqli->prepare($query))) {
echo 'Prepare failed: (' . $mysqli->errno . ') ' . $mysqli->error;
$mysqli->close();
return;
}
if (!empty ($bind_param) && $type = array_shift ($bind_param)) {
// $sql->bind_param()
call_user_func_array (array ($sql, 'bind_param'), array_merge (array ($type), array_map (function (&$item) { return $item; }, $bind_param)));
}
if (!$sql->execute()) {
echo 'Execute failed: (' . $sql->errno . ') ' . $sql->error;
$sql->close();
return;
}
if (strpos ($query, 'SELECT ') !== false || strpos ($query, 'SHOW ') !== false) {
// proceed this only if is SELECT or SHOW query
if (!($res = $sql->get_result())) {
echo 'Getting result set failed: (' . $sql->errno . ') ' . $sql->error;
$res->close();
return;
}
// store data to $output for use
for ($row_no = ($res->num_rows - 1); $row_no >= 0; $row_no --) {
$res->data_seek($row_no);
$output[$row_no] = $res->fetch_assoc();
}
$res->close();
}
$sql->close();
unset ($tmp_bind);
return $output;
}
The answer to your question is "No". When you add an array to a string, you get a string, and the original array is lost in the string "Array".
So -- why are you doing this?
My guess is that you're trying to come up with a compact representation of a query - a single object containing information on the query and its parameters (if any). This can be done, but requires some code to go with it.
For example you can create a class of your own holding the query in PDO format, with placeholders:
SELECT * FROM category WHERE id > :param1 ...
and also an array of parameters with their types:
[ 'param1' => 'int', ... ]
Then you might need a __toString() method to yield a string representation that is of use for you. The above representation can immediately be used by PDO.
Now, to specify a query with its parameters, you can use a compact representation not too unlike the one you used, leveraging an array of arrays (note that there is no string concatenation here, but rather array concatenation):
$query = ["SELECT * FROM tbl WHERE id >",['int']," AND ..." ...];
To convert from this "shorthand" to the more useable PDO_String / PDO_Param_Array representation, you could walk $query concatenating all non-string elements with placeholder parameters:
$queryString = '';
$queryParams = [ ];
$n = 1;
foreach ($query as $item) {
if (is_string($item)) {
$queryString .= $item;
} else {
$queryString .= ':param' . $n;
$queryParams[":param{$n}"] = $item[0];
$n++;
}
}

remove quotes in post values based on array key name

I want to be able to remove quotations from a field on or abouts the name of 'quote'. On post, all my field names and values get matched up and put into an array then enter to the database. Before the SQL is built and after I build the value-key array, how can I single out the field quote, remove the quotation marks that the user inputted, and then add/keep the content in the $values array for my SQL? The questionable area starts with the comment "remove quotes"
public function insertIntoDb($table, $carryUrl = NULL, $ext = '')
{
if (in_array($table, $this->disallow_insert)) {
self::show_error("Inserting into the table '{$table}' is not possible, check the configuration file if this is an error.");
} elseif (!isset($table)) {
self::show_error('Missing `table` parameter in ' . __FUNCTION__);
}
$resultInsert = Nemesis::query("SHOW COLUMNS FROM {$table}");
if (!$resultInsert) {
self::show_error(QUERY_ERROR);
}
$fieldnames = array();
if ($resultInsert->num_rows > 0) {
while ($row = $resultInsert->fetch_array()) {
$fieldnames[] = $row['Field'];
$values = array_intersect_key($_POST, array_flip($fieldnames));
// $values = array_filter($values, function($x) { return $x !== ''; });
// <5.3 $values = array_filter($values, create_function('$x', 'return $x !== "";'));
}
}
// remove quotes for testimonials
if (array_key_exists('quote', array_change_key_case($values, CASE_LOWER))) {
$values['quote'] = preg_replace("/<!--.*?-->/", "", $values); // remove quotes
}
// filter the array
$values = self::filter($values);
$sql = sprintf("INSERT INTO %s (created, created_by, %s) VALUES (NOW(), '$_SESSION[user_id]', '%s')", $table, implode(', ', array_keys($values)), implode("', '", $values));
if ($this->debug) {
echo '<p>' . $sql . '</p>';
} elseif (Nemesis::query($sql)) {
$msg = new Messages();
$msg->add('s', QUERY_INSERT_SUCCESS);
if ($table == 'projects') {
$msg = new Messages();
$msg->add('s', "Information was added to the database. Time to add images!");
}
if (!is_null($carryUrl) && isset($carryUrl)) {
redirect($carryUrl . '?id=' . $_POST['id'] . '&table=' . $table . $ext);
}
} else {
self::show_error(QUERY_ERROR);
}
}
preg_replace is a function that returns a value, not a void. You'll need to assign the returned value back to $values['quote']:
// remove quotes for testimonials
if (array_key_exists('quote', array_change_key_case($values, CASE_LOWER))) {
$values['quote'] = preg_replace("/(\"|')/", "", $values['quote']); // remove quotes
}

Delete all sublevels of categories in select using PHP

Problem:
I am trying to delete all sublevels of a category by using a class. Currently I can only make it delete two sublevels, not three.
The database table:
CREATE TABLE betyg_category (
CID int(11) NOT NULL AUTO_INCREMENT,
Item varchar(100) NOT NULL,
Parent int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (CID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
The PHP class:
<?php
class ItemTree
{
var $itemlist = array();
function ItemTree($query)
{
$result = mysql_query($query) or die ('Database Error (' . mysql_errno() . ') ' . mysql_error());
while ($row = mysql_fetch_assoc($result))
{
$this->itemlist[$row['CID']] = array(
'name' => $row['Name'],
'parent' => $row['Parent']
);
}
}
function get_tree($parent, $with_parent=0)
{
$item_tree = array();
if ($with_parent == 1 && $parent != 0)
{
$item_tree[$parent]['name'] = $this->itemlist[$parent]['name'];
$item_tree[$parent]['parent'] = $this->itemlist[$parent]['parent'];
$item_tree[$parent]['child'] = $this->get_tree($parent);
return $item_tree;
}
foreach ($this->itemlist as $key => $val)
{
if ($val['parent'] == $parent)
{
$item_tree[$key]['name'] = $val['name'];
$item_tree[$key]['parent'] = $val['parent'];
$item_tree[$key]['child'] = $this->get_tree($key);
}
}
return $item_tree;
}
function make_optionlist ($id, $class='', $delimiter='/')
{
$option_list = '';
$item_tree = $this->get_tree(0);
$options = $this->make_options($item_tree, '', $delimiter);
if (!is_array($id))
{
$id = array($id);
}
foreach($options as $row)
{
list($index, $text) = $row;
$selected = in_array($index, $id) ? ' selected="selected"' : '';
$option_list .= "<option value=\"$index\" class=\"$class\"$selected>$text</option>\n";
}
return $option_list;
}
function make_options ($item_tree, $before, $delimiter='/')
{
$before .= empty($before) ? '' : $delimiter;
$options = array();
foreach ($item_tree as $key => $val)
{
$options[] = array($key, '- '.$before.$val['name']);
if (!empty($val['child'])) {
$options = array_merge($options, $this->make_options($val['child'], $before.$val['name'], $delimiter));
}
}
return $options;
}
function get_navlinks ($navid, $tpl, $startlink='', $delimiter=' ยป ')
{
// $tpl typ: {name}
$search = array('{id}', '{name}');
$navlink = array();
while (isset($this->itemlist[$navid]))
{
$replace = array($navid, $this->itemlist[$navid]['name']);
$navlink[] = str_replace($search, $replace, $tpl);
$navid = $this->itemlist[$navid]['parent'];
}
if (!empty($startlink))
{
$navlink[] = str_replace($search, array(0, $startlink), $tpl);
}
$navlink = array_reverse($navlink);
return implode($delimiter, $navlink);
}
function show_tree ($parent=0, $tpl='%s', $ul_class='', $li_class='')
{
$item_tree = $this->get_tree($parent);
return $this->get_node($item_tree, $parent, $tpl, $ul_class, $li_class);
}
function get_node ($item_tree, $parent, $tpl, $ul_class, $li_class)
{
// $tpl typ: {name}
$search = array('{id}', '{name}');
$output = "\n<ul class=\"$ul_class\">\n";
foreach ($item_tree as $id => $item)
{
$replace = array($id, $item['name']);
$output .= "<li class=\"$li_class\">".str_replace($search, $replace, $tpl);
$output .= !empty($item['child']) ? "<br />".$this->get_node ($item['child'], $id, $tpl, $ul_class, $li_class) : '';
$output .= "</li>\n";
}
return $output . "</ul>\n";
}
function get_id_in_node ($id)
{
$id_list = array($id);
if (isset($this->itemlist[$id]))
{
foreach ($this->itemlist as $key => $row)
{
if ($row['parent'] == $id)
{
if (!empty($row['child']))
{
$id_list = array_merge($id_list, get_id_in_node($key));
} else
{
$id_list[] = $key;
}
}
}
}
return $id_list;
}
function get_parent ($id)
{
return isset($this->itemlist[$id]) ? $this->itemlist[$id]['parent'] : false;
}
function get_item_name ($id)
{
return isset($this->itemlist[$id]) ? $this->itemlist[$id]['name'] : false;
}
}
?>
Scenario:
Say you have the following structure in a :
Literature
-- Integration of sources
---- Test 1
It will result in the following in the database table:
When I try to delete this sublevel, it will leave the last sublevel in the database while it should delete it. The result will be:
The PHP code:
//Check if delete button is set
if (isset($_POST['submit-deletecategory']))
{
//Get $_POST variables for category id
$CategoryParent = intval($_POST['CategoryList']);
//Check if category is selected
if ($CategoryParent != "#")
{
//Get parent category and subsequent child categories
$query = "SELECT CID, Item AS Name, Parent FROM " . TB_CATEGORY . " ORDER BY Name";
$items = new ItemTree($query);
if ($items->get_item_name($_POST['CategoryList']) !== false)
{
//Build up erase list
$CategoryErase = $items->get_id_in_node($CategoryParent);
$CategoryEraseList = implode(", ", $CategoryErase);
}
else
{
$CategoryEraseList = 0;
}
//Remove categories from database
$query = "DELETE FROM " . TB_CATEGORY . " WHERE CID IN ($CategoryEraseList)";
$result = mysql_query($query) or die ('Database Error (' . mysql_errno() . ') ' . mysql_error());
//Return a confirmation notice
header("Location: settings.php");
exit;
}
}
Thank you in advance for any guidance I can get to solve the issue.
Here is a way to do it : use a recursive function, which will first look for the leaf item (the deepest in your tree). You remove children first, then the parent. And for each child, you remove child's children first, etc...
deleteSub(1);
function deleteSub($cat_id) {
$request = "SELECT * FROM ". TB_CATEGORY ." WHERE Parent = ".$cat_id;
$results = mysql_query($request);
while($child = mysql_fetch_array($results))
{
deleteSub($child["CID"]);
}
$request = "DELETE FROM ". TB_CATEGORY ." WHERE CID = ".$cat_id;
return mysql_query($request);
}
A better way could be use this kind of recursive function to store CIDs in an array, then make a single DELETE request, but I think you'll be able to adapt this code.
I'm not going to read or try to understand the entire code, but it seems to me you need some sort of recursion function. What I basicly would do is create a function that goes up in the hierachy and one that goes down.
Note: It has been a while since i've written anything in procedural mysql, so please check if the mysql_num_rows(),mysql_fetch_array and so on is written in the correct manner
EDIT: I've just noticed you only wanted a downwards deletion and therefore zessx's answer is more valid
<?php
function recursiveParent($id) {
$sql = 'SELECT parent FROM betyg_category WHERE CID=' . $id;
$result = mysql_query($sql);
if(mysql_num_rows($result) > 0) {
while($r = mysql_fetch_array($result,MYSQLI_ASSOC)) {
recursiveParent($r['parent']);
}
}
$sql = 'DELETE FROM betyg_category WHERE CID=' . $id;
mysql_query($sql);
}
function recursiveChild($parent) {
$sql = 'SELECT CID FROM betyg_category WHERE parent=' . $parent;
$result = mysql_query($sql);
if(mysql_num_rows($result) > 0) {
while($r = mysql_fetch_array($result,MYSQLI_ASSOC)) {
recursiveChild($r['CID']);
}
}
$sql = 'DELETE FROM betyg_category WHERE parent=' . $parent;
mysql_query($sql);
}
function delete($id) {
recursiveParent($id);
recursiveChild($id);
}
?>
This is my way to do. instead of recursive the query to run, i get all the child's id first then only run query. here the code refer:-
First, defined a variable called $delete_node_list as array. (to store all node id that need to be delete)
function delete_child_nodes($node_id)
{
$childs_node = $this->edirectory_model->get_child_nodes($node_id);
if(!empty($childs_node))
{
foreach($childs_node as $node)
{
$this->delete_child_nodes($node['id']);
}
}
$this->delete_node_list[] = $node_id;
}
in mysql..
$sql = 'DELETE FROM betyg_category WHERE CID IN '.$this->delete_node_list;
mysql_query($sql);

Access to Access Transfer

I have asked a couple of questions on here in a rush, and I am getting nowhere fast, I keep changing things and just ending up with a different problem, and no closer to having a clue what is causing it. I am a PHP MYSQL man, and I am having to work with Access via the COM class.
Basically the client has multiple servers, each with an access database, and each with a CMS, the tables should contain the same data and have slipped out of Sync. It is my job to come up with a way of resyncing them.
I have came to bringing the data out into an Array, serializing it and saving it to a file on the main server (the one all should sync to) and then on the other servers, downloading the file, unserializing it and item by item, checking if it is in the DB, and if not inserting it. The insert is failing. I have tried building an sql query for each item and doing $this->conn->Execute(); and that is failing row by row on silly things, so I am now trying this:
function syncCMS()
{
$this->output['msg'] .= "<p><b>The following properties were added to the database on ." . $this->hsite . "</b></p>";
$this->get_field_names();
$this->make_connection(); //assigns connection to $this->conn
$rs = new COM('ADODB.Recordset');
$rs->CursorType = 2;
$rs->CursorLocation = 1;
$rs->LockType = 4;
$rs->Open($this->table_name,$this->conn);
foreach ($this->awayPropertyDetails as $key => $property)
{
$this->check_for_property($property['pname']);
if (!$this->property_exists || $this->mode == "fullSync")
{
unset($values);
$bfields = array("pshow","rent","best", "oda1", "oda2", "oda3", "oda4", "oda5", "oda6", "odap", "topool","tomountain","tofitness","tosauna"); //stores the yes/no values
$q = "INSERT INTO " . $this->table_name . " (" . $this->dbfields . ") VALUES (";
foreach ($property as $k => $value)
{
if ($k == "Kimlik") {
$value = null;
}
if ($k == "tarih")
{
$value = date("d/m/Y");
$value = "'" . $value . "'";
}
if (in_array($k,$bfields))
{
if ($value == "")
{
$value = 'FALSE';
}
else
{
$value = 'TRUE';
}
}
$rs->fields->$k = $value;
$this->output['msg'] .= $property['pname'] . " added";
}
$rs->BatchUpdate();
$rs->Close();
$this->conn->Close();
//$this->download_images($property['OBJECT_NR'],$k);
//$this->output['msg'] .= "<p>Images added</p>";
}
}
$message .= "</ul>";
$this->output['msg'] .= $message;
$this->sendOutput();
//print_r($property);*/
}
And getting this:
Fatal error: Uncaught exception 'com_exception' with message 'Unable to lookup `Kimlik': Unknown name. ' in D:\inetpub...
Kimlik is the name of the first field, the Auto Number field, I took it out and the problem shifted to the second field.
I got there with:
$sql = "SELECT * FROM " . $this->table_name;
if (!$this->property_exists || $this->mode == "fullSync")
{
$this->make_connection();
$rs->Open($sql,$this->conn);
$rs->addnew();
foreach($rs->Fields as $field)
{
if ($field->name != "Kimlik")
{
$rs->Fields[$field->name] = $property[$field->name];
}
}
$rs->Update();
$rs->Close();
$this->conn->Close();
$msg = $this->download_images($property['OBJECT_NR'],$k);
$this->output['msg'] .= $property['pname'] . " added<br/>" . $msg . "<br/><br/>";
}

Categories