Dynamic Database function for getting and setting data - php

I have created a class to handle all database operations for my application. So far I have gathered and displayed the data in foreach statements within my view. What I wish to be able to do is get and set individual elements from a function from my database class.
Here is my database function for displaying data within a foreach:
public function test($sql, $type, $param)
{
$variables = array();
$results = array();
// Mysqli
if($stmt = $this->AC->Mysqli->prepare($sql))
{
$params = array_merge($type, $param);
call_user_func_array(array($stmt, 'bind_param'), $this->make_values_referenced($params));
$stmt->execute();
$stmt->store_result();
// Existance
if($stmt->num_rows > 0)
{
$meta = $stmt->result_metadata();
while($field = $meta->fetch_field())
{
$parameters[] = &$row[$field->name];
}
call_user_func_array(array($stmt, 'bind_result'), $parameters);
while($stmt->fetch())
{
$elemet = array();
foreach($row as $key => $val)
{
$element[$key] = $val;
}
$results[] = $element;
}
return $results;
}
else
{
$results = FALSE;
return $results;
}
}
else
{
die("ERROR: We could not connect.");
}
}
The function below is called from my model:
public function profile_information($account_name)
{
// Mysqli Variables
$sql = "SELECT $this->account_details.bio
FROM account_details
WHERE $this->account_details.account_name = ? LIMIT 10";
$type = array("s");
$param = array($account_name);
$results = $this->AC->Database->prepared_select_loop($sql, $type, $param);
$this->AC->Template->set_data('profile_information', $results, FALSE);
}
Once set within my model, I call the function within my controller and access it within my view with a foreach for displaying data:
$profile_information = $this->get_data('profile_information');
foreach ($profile_information as $row) :
//Displaying data here
endforeach;
The above works fine for displaying a large amount of data, but what I'm wanting to do is call the a database function that will allow me set individual data elements. Therefore not having to use a foreach if im only getting a limited amount of data (i.e. one row of Name, age, address)
A non dynamic way I have tackled this problem is to write the database for every function that only desires one row from the database:
function name($variable)
{
$sql = 'statement here';
$stmt = $this->AC->Mysqli->prepare($sql);
$stmt->bind_param("i", $id);
$stmt->execute();
$stmt->store_result();
$stmt->bind_result(bind results);
$stmt->fetch();
$this->AC->Template->set_data('id', $id);
$this->AC->Template->set_data('account_name', $account_name);
}
So in basically I want to make the above statement refactored into my database class and thus making it more dynamic.
I dont know how I would be able to tackle this problem, I don't want to use PDO, as I wish to find a solution within Mysqli. Any help would be appreciated.

Would you recommend just switching over to PDO?
Definitely.
Whole your function test() could be rewritten into three lines (assuming $param contains an array with parameters for the prepared statement):
public function test($sql, $param)
{
$stmt = $this->AC->pdo->prepare($sql);
$stmt->execute($param);
return $stmt->fetchAll();
}

Related

MySQL Prepared statement confusion

Ok, so I am having a lot of trouble with Prepared statements. I've done hours of research and still can't seem to fully understand everything...
I really feel like I need to understand Prepared statements because I was just about to release a few new free APIs on my website (which require API Key to execute API) but I recently realized how insecure everything is.... I can simply use SQL injection to bypass API Key check, e.g. 'OR'1'='1
Here is how I validate API Key:
$apikey = $_GET['key'];
$sql = "SELECT * FROM `table` WHERE `key` = '$apikey'";
$query = mysqli_query($con, $sql);
if($query)
{
$fetchrow = mysqli_fetch_row($query);
if(isset($fetchrow[0]))
{
echo "API Key is valid!";
}
else
{
echo "API KEY is invalid";
}
}
And like mentioned above this can easily be bypassed by executing my API like this
http://website.com/api.php?key='OR'1'='1
This really scared me at first, but then I did some research and learned a good way to prevent any form of SQL injection is to use prepared statement, so I did a lot of research and it just seems quite complicated to me :/
So I guess my question is, how can I take my above code, and make it function the same way using prepared statements?
Probably everything you need:
class Database {
private static $mysqli;
Connect to the DB:
public static function connect(){
if (isset(self::$mysqli)){
return self::$mysqli;
}
self::$mysqli = new mysqli("DB_HOST", "DB_USER", "DB_PASS", "DB_NAME");
if (mysqli_connect_errno()) {
/*Log error here, return 500 code (db connection error) or something... Details in $mysqli->error*/
}
self::$mysqli->query("SET NAMES utf8");
return self::$mysqli;
}
Execute statement and get results:
public static function execute($stmt){
$stmt->execute();
if ($mysqli->error) {
/*Log it or throw 500 code (sql error)*/
}
return self::getResults($stmt);
}
Bind results to the pure array:
private static function getResults($stmt){
$stmt->store_result();
$meta = $stmt->result_metadata();
if (is_object($meta)){
$variables = array();
$data = array();
while($field = $meta->fetch_field()) {
$variables[] = &$data[$field->name];
}
call_user_func_array(array($stmt, "bind_result"), $variables);
$i = 0;
while($stmt->fetch()) {
$array[$i] = array();
foreach($data as $k=>$v)
$array[$i][$k] = $v;
$i++;
}
$stmt->close();
return $array;
} else {
return $meta;
}
}
Class end :)
}
Example of usage:
public function getSomething($something, $somethingOther){
$mysqli = Database::connect();
$stmt = $mysqli->prepare("SELECT * FROM table WHERE something = ? AND somethingOther = ?");
$stmt->bind_param("si", $something, $somethingOther); // s means string, i means number
$resultsArray = Database::execute($stmt);
$someData = $resultsArray[0]["someColumn"];
}
Resolving your problem:
public function isKeyValid($key){
$mysqli = Database::connect();
$stmt = $mysqli->prepare("SELECT * FROM table WHERE key = ? LIMIT 1");
$stmt->bind_param("s", $key);
$results = Database::execute($stmt);
return count($results > 0);
}
PHP automatically closes DB connection so no worries about it.
$sql = "SELECT * FROM `table` WHERE `key` = ?";
if(stmt = $mysqli->prepare($sql)) {
$stmt->bind_param("i", $apikey);
$stmt->execute();
$stmt->bind_result($res);
$stmt->fetch();
$stmt->close();
}
See more - http://php.net/manual/en/mysqli.prepare.php

Cannot bind multiple parameters using call_user_func_array()

I know they're many, many questions already about this, but I have no idea why mine doesn't work.
If I just use the following, my code works fine:
$this->stmt->bind_param("ii", $params[0], $params[1]);
But if I use the call_user_func_array, it breaks. One suggestion I got was passing the $parameters array by reference, but adding an & before the variable also broke my code...
Any help is greatly recieved!
Here's my code:
DB class:
function selectQuery($sql, $paramTypes = false, $params = false) {
//Prepare statement
$this->stmt = $this->conn->prepare($sql);
if($this->stmt === false) {
//We have an error
echo 'Wrong SQL: ' . $sql . ' Error: ' . $this->conn->error;
}
//This part doesn't work...
// if ($params) {
// //Bind an unknown number of parameters
// $parameters = array_merge(array($paramTypes), $params);
// call_user_func_array(array($this->stmt, 'bind_param'), $parameters);
// }
//This works.
$this->stmt->bind_param("ii", $params[0], $params[1]);
//Execute statement
$this->stmt->execute();
if ($this->stmt->error) {
echo $this->stmt->error;
return false;
}
//Get the results
$result = $this->stmt->get_result();
$data = $result->fetch_all(MYSQLI_ASSOC);
//Close statement
$this->stmt->close();
//Return the results
return $data;
}
Test page:
<?php
require_once('DatabaseAccess.php');
$db = new DB();
$sql = "SELECT * FROM table WHERE id = ? OR id = ?";
echo "Fetching data....<br>";
$result = $db->selectQuery($sql, "ii", array(1, 2));
foreach($result as $r) {
echo "<pre>".print_r($r, 1)."</pre>";
}
?>
Decided to add more information:
I'll be using this function to pass in the parameter types and parameters, but the amount will vary. When I looked up how to do this everyone suggested the call_user_func thing, but each time I try it (tried a few different ways) it won't work. Read through many threads, but it never seems to work. If I just use the bind_params function directly it works and I get the correct data returned.
Using the call_user_func thing I was getting the no data for the ? mysqli error, which is when I tried passing by reference and the code just broke completely...
Put it before call_user_func_array()
$res = array();
foreach($parameters as $key => $value) {
$res[$key] = &$parameters[$key];
}

Structuring my MySQL search query for use with PDO

I'm attempting to write a parameterized query with PDO that accepts a number of inputs and acts as a search on a specific table.
There are a number of columns I wish to search on but each of which could be optional. The query simplified might look like:
SELECT * FROM item
WHERE name LIKE '%?%'
AND col2 IN (?, ?, .......)
AND col3 IN (?, ?, .......)
...
and with the IN clause, there could be any number (0 or more) of terms for each column.
Since the IN clause won't work with 0 values, and in PDO I'd have to iterate over each array passed in for each IN clause - I'm wondering if there's a better way to structure this as it seems like a big mess.
You can use "call_user_func_array" to make it dynamic. This is what I use:
public function selectMSData($handlename, $sql, $type='', $params = array())
{
if ( ! is_string($sql) || ! is_array($params) ) {
die('wrong param types');
}
$this->dbconnect($handlename); //connect to db and save connection with handle-name
$result = array();
$aRows = 0;
if(sizeof($params)==0) {
//simple query without runtime parameters
$msres = $this->dbhandle[$handlename]->query($sql);
if($msres === false) {
//log error
} else {
while($mres = $msres->fetch_array(MYSQLI_ASSOC)) {
$aRows++;
$result[] = $mres;
}
}
} else {
//prepared statement using runtime parameters
$stmt = $this->dbhandle[$handlename]->prepare($sql);
if(!$stmt) {
//log error
}
$valArr = array();
$valArr[] = $type;
foreach($params as $pkey => $pval) {
$valArr[] = &$params[$pkey];
}
call_user_func_array(array(&$stmt, 'bind_param'), $valArr);
if(!$stmt->execute()) {
//log error
};
$stmt->store_result(); //fetch is super-slow for text-fields if you don't buffer the result!!
$meta = $stmt->result_metadata();
while ($field = $meta->fetch_field()) {
$resfields[] = &$row[$field->name];
}
call_user_func_array(array($stmt, 'bind_result'), $resfields);
while ($stmt->fetch()) {
foreach($row as $key => $val) {
$c[$key] = $val;
}
$result[] = $c;
$aRows++;
}
$stmt->close();
}
$this->result = $result;
return $aRows;
}
and you call it like this:
$db->selectMSData('my_db_name', 'SELECT * FROM example WHERE a=? AND b=? LIMIT 1', 'ss', array($a, $b));

mysqli get_result alternative with multiple parameters

SELECT * FROM `ware_house_indents` WHERE `has_cancel` = ? AND `eid`= ?
i need to get result above query in mysqli. but i cant use get_result() function because it is not working in server.i have found this below function and it is working fine.but that function not possible to pass multiple parameters.please help me to solve this problem.
function db_bind_array($stmt, &$row)
{
$md = $stmt->result_metadata();
$params = array();
while($field = $md->fetch_field()) {
$params[] = &$row[$field->name];
}
return call_user_func_array(array($stmt, 'bind_result'), $params);
}
function db_query($db, $query, $types, $params)
{
$stmt = $db->prepare($query);
$bindRet = call_user_func_array(array($stmt,'bind_param'),
array_merge(array($types), $params));
$stmt->execute();
$result = array();
if (db_bind_array($stmt, $result) !== FALSE) {
return array($stmt, $result);
}
$stmt->close();
return FALSE;
}
if (($qryRes = db_query($mysqli, $sql, 'i', array(&$var))) !== FALSE) {
$stmt = $qryRes[0];
$row = $qryRes[1];
while ($stmt->fetch()) {
print_r($row);
}
$stmt->close();
}
Let me suggest you a quite indirect solution.
You'd do yourself a huge favor if start using PDO instead of mysqli
It has no mysqli disadvantages when dealing with prepared statements as it has a way better implementation of above functions out of the box. So, it will let you to get your data in a few lines:
$sql = "SELECT * FROM ware_house_indents WHERE has_cancel = ? AND eid= ?";
$stm = $pdo->prepare($sql);
$stm->execute(array($cancel,$eid));
$data = $stm->fetchAll(); // or fetch() if you need only one row
that's all

Prepared mysqli select statement on longtext field is coming back empty

I've got a database query function that works well -- except that I'm running into what's apparently a known issue with mysqli prepared statements and longtext fields. What happens is that the longtext field always comes up empty even though running the query through phpMyAdmin works fine. According to http://www.workinginboxershorts.com/php-mysqli-returns-empty-variables-from-longtext-column, switching the datatype to text solves the problem. However, in my case I'd really prefer to leave the field as longtext as I can foresee times when that extra space would be valuable.
I'm using parameterized queries, which evidently is the problem. Here's my function:
// Bind results to an array
// $stmt = sql query, $out = array to be returned
function stmt_bind_assoc (&$stmt, &$out) {
$data = mysqli_stmt_result_metadata($stmt);
$fields = array();
$out = array();
$fields[0] = $stmt;
$count = 1;
while($field = mysqli_fetch_field($data)) {
$fields[$count] = &$out[$field->name];
$count++;
}
call_user_func_array('mysqli_stmt_bind_result', $fields);
}
// DB Query
// $query = SQL query, $params = array of parameters, $rs = whether or not a resultset is expected, $newid = whether or not to retrieve the new ID value;
// $onedimensionkey = key required to convert array into simple one dimensional array
function db_query($query, $params, $rs = true, $newid = false, $onedimensionkey = false) {
$link = mysqli_connect(DB_SERVER, DB_USER, DB_PASS, DB_NAME);
if (!$link) {
print 'Error connecting to MySQL Server. Errorcode: ' . mysqli_connect_error();
exit;
}
// Prepare the query and split the parameters array into bound values
if ($sql_stmt = mysqli_prepare($link, $query)) {
if ($params) {
$types = '';
$new_params = array();
$params_ref = array();
// Split the params array into types string and parameters sub-array
foreach ($params as $param) {
$types .= $param['type'];
$new_params[] = $param['value'];
}
// Cycle the new parameters array to make it an array by reference
foreach ($new_params as $key => $parameter) {
$params_ref[] = &$new_params[$key];
}
call_user_func_array('mysqli_stmt_bind_param', array_merge(array($sql_stmt, $types), $params_ref));
}
}
else {
print 'Error: ' . mysqli_error($link);
exit();
}
// Execute the query
mysqli_stmt_execute($sql_stmt);
// If there are results to retrive, do so
if ($rs) {
$results = array();
$rows = array();
$row = array();
stmt_bind_assoc($sql_stmt, $results);
while (mysqli_stmt_fetch($sql_stmt)) {
foreach ($results as $key => $value) {
$row[$key] = $value;
}
$rows[] = $row;
}
if ($onedimensionkey) {
$i = 0;
foreach ($rows as $row) {
$simplearray[$i] = $row[$onedimensionkey];
$i++;
}
return $simplearray;
}
else {
return $rows;
}
}
// If there are no results but we need the new ID, return it
elseif ($newid) {
return mysqli_insert_id($link);
}
// Close objects
mysqli_stmt_close($sql_stmt);
mysqli_close($link);
}
According to the link that I posted there is a workaround involving the order in which things are done, but either I'm handling my query in a completely different manner than the example or I'm simply not understanding something important.
Thanks to anyone who can help!
EDIT: Thanks to Corina's answer, I've solved this -- for anyone else who runs into the problem, you will simply need to add the following after the mysql_stmt_execute command:
// Execute the query
mysqli_stmt_execute($sql_stmt);
// Store results
mysqli_stmt_store_result($sql_stmt);
I managed to solve the same issue by calling mysqli_stmt_store_result before binding the data.
Someone had the same problem and shared the answer on the php.net website:
Apparently, if you have longtext present, you HAVE to call
store_result before using bind_result.
http://bugs.php.net/bug.php?id=47928

Categories