Despite coming from a programming background, I'm still relatively new to PHP and trying to grasp it properly. I have decided to use MySQLi instead of PDO (which turned out to be a grave mistake on my part) for my database needs, but I've run into an issue where bind_result only seems to return one result, despite the results, to my knowledge, being looped through. I've been googling for hours, but I just can't figure out what's going on. I'm sure the solution is very simple, I'm overlooking something, and/or am doing something terribly wrong. If so, I hope you can find it in your heart to forgive me and help me solve my pesky issue, Stack Overflow.
Here is the ever-so-malicious code.
public function query($query, $types, $params)
{
if ($stmt = $this->prepareQuery($query, $types, $params))
{
$stmt->execute();
// we prepare the array in which the results will be stored for dynamic results
$meta = $stmt->result_metadata();
$results = array();
while ($field = $meta->fetch_field())
{
$name = $field->name;
$$name = null;
// pass reference because bind_result() requires it
$results[$name] = &$$name;
}
while ($stmt->fetch())
{
// call_user_func_array is used for dynamic results
call_user_func_array(array($stmt, "bind_result"), $results);
var_dump($results) . "<br />";
}
$data = array();
while ($stmt->fetch()) {
array_push($data, $results);
}
return $data;
}
else
{
echo $this->db->error;
}
}
Related
I have been using a wrapper class for my database functions for a long time now. A few years back I upgraded it to use mysqli_ instead of the depreciated mysql_ functions which made the website transgress perfectly during PHP version upgrades.
With a more recent upgrade to my wrapper I wrote a class to do prepared statemtnts from an mulit-array to help keep things a little neater.
This is the way I've done it.
index.php - Testing File
/* Set Parameters
i - integer
d - double
s - string
b - BLOB
*/
$params = array(
array('s' => $_GET["id"]),
array('i' => $_GET["two"])
);
$db->prepare("INSERT INTO `".$db->prefix."articles` SET `categoryID`= ? , `articleID`= ? ");
$select2 = $db->execute($params);
$db->close();
You can also reset the $params array with a different data set and $db->execute($params) again to insert another row.
database.class.php
function prepare($sql) {
$this->last_query = $sql; //Save query to show later on.
$this->stmt = $this->conn->prepare($sql);
}
function close() {
$this->stmt->close();
}
function execute($params) {
$ident = '';
$parameters = '';
$vars = array();
if($this->stmt) {
foreach($params as $k => $v) {
foreach($v as $a => $b) {
$ident .= $a;
$vars[] .= $b;
$parameters .= $a.' => '.$b.'<br>'; // For MySQL error reporting
}
}
$this->stmt->bind_param( $ident , ...$vars );
$this->parameters = $parameters;
$this->stmt->execute();
$result = $this->stmt->get_result();
return $result;
}
else {
if($this->conn->error) {
$this->last_error = $this->conn->error;
$sql = trim(preg_replace('/\s+/', ' ', $sql));
$this->log_error("MySQL",$this->last_error." ☼ ".$sql);
}
}
//$data = $result->fetch_all();
}
My questions are:
Will the way I have it slow it down any?
Can anyone come up with a better solution and keep it neat? I like the neatness of having the info lined up in the array; not saying it's the best solution but it does work. I haven't found a nice solution to keep things totally organized.
Do you see any downfalls to this route?
The website I work on will all be running PHP 7.0 (or at least PHP 5.7 until I can upgrade them to the newer version).
I'm running into an "Cannot redeclare" error and I can't figure out how to fix it. So I have a few functions in a php file located below. Now these functions iterate over an array of data.
I think I've surmised that the problem is that I'm looping the function over and over again in the foreach loop, and its the foreach loop thats been the problem. It seems like its already writing one the function to memory the first time and then for some reason it doesn't like being evoked again.
Your help appreciated.
P.S I've seen a number of similar posts on the issue such as Fatal error: Cannot redeclare but that doesn't seem to work.
<?php
// *****Code Omitted from Stack****
function postHelper($data, $field1, $field2)
{ //TODO Abstract and make sure post Helper and modify Post can be the same thing.
$result = array();
for ($j = 0; $j < count($data); ++$j) { //iterator over array
if ($field2 == "") {
$result[$j] = $data[$j][$field1];
} else {
return $result[$j] = $data[$j][$field1][$field2];
}
}
return $result;
}
//returns an array with only # and # values
function modifyPost($data)
{
//puts symbol # before read data
function addSymbol($data, $field1, $field2)
{
$info = postHelper($data, $field1, $field2);
foreach ($info as &$n) {
$n = '#' . $n;
}
print_r($info);
}
/*
Parse texts and returns an array with only # or # signs used
*/
function parseText($data)
{
$newarr = array();
$text = postHelper($data, "text", "");
foreach ($text as &$s) { //separates into words
$ex = explode(" ", $s);
foreach ($ex as &$n) { //if text doesnt' begin with '#' or '#' then throw it out.
if (substr($n, 0, 1) === '#' || strpos($n, '#') !== false) {
array_push($newarr, $n . ',');
}
}
}
return $newarr;
}
}
foreach ($posts as $entry) {
if (!function_exists('modifyPost')) {
$nval = "hello";
modifyPost($entry);
$entry['mod_post'] = $nval;
}
}
?>
EDIT: I've solved the error. Turns out that the original posts did actually work. I messed in naming. I will give points to anyone who can explain to me why this is necessary for a call. Moreover, I will update post if there is an additional questions that I have.
Php doesn't support nested functions. Although you technically can declare a function within a function:
function modifyPost($data)
{
function addSymbol($data, $field1, $field2)
the inner function becomes global, and the second attempt to declare it (by calling the outer function once again) will fail.
This behaviour seems counter-intuitive, but this is how it works at the moment. There's RFC about real nested functions, which also lists several workarounds for the problem.
The error says it all. You have duplicate modifyData() & parseText functions.
Remove the top half of the php file so only one of each occurs.
In the code below, can someone tell me where the variable $row is coming from in the foreach statement?
public function getProcResultSet($cmd)
{
try
{
$meta = $cmd->result_metadata();
while ($field = $meta->fetch_field())
{
$params[] = &$row[$field->name];
}
call_user_func_array(array(
$cmd,
'bind_result'), $params);
while ($cmd->fetch())
{
foreach ($row as $key => $val)
{
$c[$key] = $val;
}
$results[] = $c;
}
return $results;
}
catch (Exception $e)
{
logToFile("Exception: " . $e);
return resultFailedUnknown();
}
}
Edit, here is the caller of this function:
public static function getPlayerUnits($playerId, $unitTypeId)
{
$mysqli = new mysqli(GDB_HOST, GDB_USERNAME, GDB_PASSWORD, GDB_NAME);
if ($mysqli->connect_errno)
{
throw new Exception('DB Connection Failed. Error Code: ' . $mysqli->connect_errno);
}
$cmd = $mysqli->prepare('CALL sp_get_player_units(?, ?)');
if (!$cmd)
{
throw new Exception($mysqli->error);
}
$cmd->bind_param('ii', $playerId, $unitTypeId);
$cmd->execute();
$results = parent::getProcResultSet($cmd);
$cmd->close();
$mysqli->close();
return $results;
}
Here is the result array which all I did was receive on a client and JSON.stringify(..):
[{"Id":1,"Name":"Machine Gunner","Quantity":0},{"Id":2,"Name":"Rocket Soldier","Quantity":0},{"Id":3,"Name":"Paratrooper","Quantity":0},{"Id":4,"Name":"Demolition Soldier","Quantity":0}]
As you can see, the result is showing the columns per row as expected.
In the first while loop, because of the reference operator &. It doesn't exist before though.
The line $params[] = &$row[$field->name] creates the $row variable. What happens is that PHP wants to take a reference to $row that doesn't exist, so it creates it without any message (quite a bug source if you'd ask me). The $row is created as array with a key $field->name set to nothing.
$row does exist in the foreach loop, but not in the while loop. There it is created as an array with empty values.
The whole thing is quite obfuscated code and not very readable. Sometimes being verbose in code is a Good Thing.
Normally people declare $row as the result of mysql_fetch_row($result), where $result is what you get after querying your SQL database. However, I don't see that mentioned anywhere here so I don't think your code should actually work.
edit: obviously the exception to this would be if this isn't your entire file and it's defined somewhere above this. but yeah, shouldn't work otherwise.
Hi I'm currently using php 5.3 in combination with mysql server 5.1.61.
I'm currently trying to do a loginscript, but I'm running into the problem
that I'm getting no result data and no error message.
The function that handles the login is the following:
public function doLogin($username,$pw)
{
$db=new incdb();
$row['name']=':username';
$row['value']=$username;
$row['type']=PDO::PARAM_STR;
$parameters[]=$row;
$row['name'] = ':password';
$row['value'] = $pw;
$row['type'] = PDO::PARAM_STR;
$parameters[] = $row;
$query=$db->execSql('SELECT * FROM tbUser WHERE '
.'username=:username AND password=MD5(:password)',$parameters);
unset($parameters);
unset($db);
$data=$query->fetch();
if (isset($data) && is_array($data))
{
$_SESSION['loggedIn']=$data['id'];
$_SESSION['loggedInData']=$data;
return 1;
}
else
{
echo 'error';
return 0;
}
}
The incdb class has the execSql function as follows:
public function execSql($sql, $parameters)
{
$query=$this->pdo->prepare($sql);
foreach ($parameters as $param)
{
$query->bindParam($param['name'], $param['value'], $param['type']);
}
$query->execute();
return $query;
}
Can someone tell me what I'm doing wrong here? (I'm relatively new to using php PDO....in the past I always used the mysql functions directly). Tnx
I think you cannot bind a parameter as a argument of a function.
Change your code like this:
$row['name'] = ':password';
$row['value'] = MD5($pw);
$row['type'] = PDO::PARAM_STR;
And your querty like this:
$query=$db->execSql(
'SELECT * FROM tbUser WHERE '
.'username=:username AND password=:password', $parameters);
Keep in mind that depending on the character set of the database the username comparison can be case sensitive!
I think you are passing the wrong parameter, you are passing an array with an array in the position 0. You only have to pass one associative array with the values. E.g.
array('username' => 'mjuarez')
check out the method below. If entered value in text box is \ mysql_real_escape_string will return duble backslash but preg_replace will return SQL with only one backslash. Im not that good with regular expression so plz help.
$sql = "INSERT INTO tbl SET val='?'";
$params = array('someval');
public function execute($sql, array $params){
$keys = array();
foreach ($params as $key => $value) {
$keys[] = '/[?]/';
if (get_magic_quotes_gpc()) {
$value = stripslashes($value);
}
$paramsEscaped[$key] = mysql_real_escape_string(trim($value));
}
$sql = preg_replace($keys, $paramsEscaped, $sql, 1, $count);
return $this->query($sql);
}
For me it basically looks like you're re-inventing the wheel and your concept has some serious flaws:
It assumes get_magic_quotes_gpc could be switched on. This feature is broken. You should not code against it. Instead make your application require that it is switched off.
mysql_real_escape_string needs a database link identifier to properly work. You are not providing any. This is a serious issue, you should change your concept.
You're actually not using prepared statements, but you mimic the syntax of those. This is fooling other developers who might think that it is safe to use the code while it is not. This is highly discouraged.
However let's do it, but just don't use preg_replace for the job. That's for various reasons, but especially, as the first pattern of ? results in replacing everything with the first parameter. It's inflexible to deal with the error-cases like too less or too many parameters/placeholders. And additionally imagine a string you insert contains a ? character as well. It would break it. Instead, the already processed part as well as the replacement needs to be skipped (Demo of such).
For that you need to go through, take it apart and process it:
public function execute($sql, array $params)
{
$params = array_map(array($this, 'filter_value'), $params);
$sql = $this->expand_placeholders($sql, $params);
return $this->query($sql);
}
public function filter_value($value)
{
if (get_magic_quotes_gpc())
{
$value = stripslashes($value);
}
$value = trim($value);
$value = mysql_real_escape_string($value);
return $value;
}
public function expand_placeholders($sql, array $params)
{
$sql = (string) $sql;
$params = array_values($params);
$offset = 0;
foreach($params as $param)
{
$place = strpos($sql, '?', $offset);
if ($place === false)
{
throw new InvalidArgumentException('Parameter / Placeholder count mismatch. Not enough placeholders for all parameters.');
}
$sql = substr_replace($sql, $param, $place, 1);
$offset = $place + strlen($param);
}
$place = strpos($sql, '?', $offset);
if ($place === false)
{
throw new InvalidArgumentException('Parameter / Placeholder count mismatch. Too many placeholders.');
}
return $sql;
}
The benefit with already existing prepared statements is, that they actually work. You should really consider to use those. For playing things like that is nice, but you need to deal with much more cases in the end and it's far easier to re-use an existing component tested by thousand of other users.
It's better to use prepared statement. See more info http://www.php.net/manual/en/pdo.prepared-statements.php