How to make a proper mysqli extension class with prepared statements? - php

I am trying to extend the mysqli class to make a helper class that will abstract away some of the complexities. One of the main things I want to accomplish is to make use of prepared statements.
I don't really know where to start, or how to handle input and output properly in one class. Another problem is that I am unable to output data as an array while using prepared statements.
I could really use a simple example to point me in the right direction.

Check out the implementation of Zend_Db, and in particular, Zend_Db_Select. In fact, you might just opt to use that instead of developing your own. Examples:
//connect to a database using the mysqli adapter
//for list of other supported adapters see
//http://framework.zend.com/manual/en/zend.db.html#zend.db.adapter.adapter-notes
$parameters = array(
'host' => 'xx.xxx.xxx.xxx',
'username' => 'test',
'password' => 'test',
'dbname' => 'test'
);
try {
$db = Zend_Db::factory('mysqli', $parameters);
$db->getConnection();
} catch (Zend_Db_Adapter_Exception $e) {
echo $e->getMessage();
die('Could not connect to database.');
} catch (Zend_Exception $e) {
echo $e->getMessage();
die('Could not connect to database.');
}
//a prepared statement
$sql = 'SELECT * FROM blah WHERE id = ?';
$result = $db->fetchAll($sql, 2);
//example using Zend_Db_Select
$select = $db->select()
->from('blah')
->where('id = ?',5);
print_r($select->__toString());
$result = $db->fetchAll($select);
//inserting a record
$row = array('name' => 'foo',
'created' => time()
);
$db->insert('blah',$row);
$lastInsertId = $db->lastInsertId();
//updating a row
$data = array(
'name' => 'bar',
'updated' => time()
);
$rowsAffected = $db->update('blah', $data, 'id = 2');

Assuming you're actually wanting to write your own version (as opposed to utilizing one of the existing libraries other answers have suggested - and those are good options, too)...
Here are a couple of functions which you may find it useful to examine. The first allows you to bind the results of a query to an associative array, and the second allows you to pass in two arrays, one an ordered array of keys and the other an associative array of data for those keys and have that data bound into a prepared statement:
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);
}
function stmt_bind_params($stmt, $fields, $data) {
// Dynamically build up the arguments for bind_param
$paramstr = '';
$params = array();
foreach($fields as $key)
{
if(is_float($data[$key]))
$paramstr .= 'd';
elseif(is_int($data[$key]))
$paramstr .= 'i';
else
$paramstr .= 's';
$params[] = $data[$key];
}
array_unshift($params, $stmt, $paramstr);
// and then call bind_param with the proper arguments
call_user_func_array('mysqli_stmt_bind_param', $params);
}

Related

How to create an object of a class only once in a Multi Step form PHP

I have a class formhandller like this
<?php
include "config.php";
class formhandller {
var $dbinstance;
var $lastinsertedid;//The id from the basic information tabel
function __construct(){
$this->connectDb();
}
function pdoMultiInsert($tableName, $data, $pdoObject){
//Will contain SQL snippets.
$rowsSQL = array();
//Will contain the values that we need to bind.
$toBind = array();
//Get a list of column names to use in the SQL statement.
$columnNames = array_keys($data[0]);
//Loop through our $data array.
foreach($data as $arrayIndex => $row){
$params = array();
foreach($row as $columnName => $columnValue){
$param = ":" . $columnName . $arrayIndex;
$params[] = $param;
$toBind[$param] = $columnValue;
}
$rowsSQL[] = "(" . implode(", ", $params) . ")";
}
//Construct our SQL statement
$sql = "INSERT INTO `$tableName` (" . implode(", ", $columnNames) . ") VALUES " . implode(", ", $rowsSQL);
//Prepare our PDO statement.
$pdoStatement = $pdoObject->prepare($sql);
//Bind our values.
foreach($toBind as $param => $val){
$pdoStatement->bindValue($param, $val);
}
//Execute our statement (i.e. insert the data).
try {
return $pdoStatement->execute();
} catch(PDOException $e) {
var_dump($e->getMessage());
//show error
error_log($query." :".$e->getMessage(). "\n", 3, getcwd() . "/var/tmp/sql_error.log");
exit;
}
}
private function connectDb(){
try {
//create PDO connection
$this->dbinstance = new PDO("mysql:host=".DBHOST.";dbname=".DBNAME, DBUSER, DBPASS);
$this->dbinstance->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e) {
//show error
error_log($query." :".$e->getMessage(). "\n", 3, getcwd() . "/var/tmp/sql_error.log");
exit;
}
}
public function postBasicinformation(){
//Add the variables coming from the form .
$stmt = $this->dbinstance->prepare('INSERT INTO basic_information (company,name,designation,email,direct_line,mobile) VALUES (:company, :name, :designation, :email, :directline, :mobile)');
$stmt->execute(array(
':company' => $_POST['company'],
':name' => $_POST['name'],
':designation' => $_POST['designation'],
':email' => $_POST['email'],
':directline' => $_POST['directline'],
':mobile' => $_POST['mobile'],
));
$this->lastinsertedid = $this->dbinstance->lastInsertId('id');
//echo $this->lastinsertedid;
//$this->dbinstance=null;
}
public function postProjectawards(){
//An example of adding to our "rows" array on the fly.
for($i=0;$i<sizeof($_POST['nominee_company']);$i++){
$rowsToInsert[] = array(
'biid' => $this->lastinsertedid,
'award_type' => 'pa',
'category' => $_POST['nominee_category'][$i],
'company' => $_POST['nominee_company'][$i],
'name' => $_POST['nominee_name'][$i],
'designation' => $_POST['nominee_designation'][$i],
'award_title' => $_POST['nominee_title'][$i],
'contact' => $_POST['nominee_contact'][$i],
'email' => $_POST['nominee_email'][$i],
'remark' => $_POST['remarks'][$i]
);
}
//var_dump($rowsToInsert);
//Call our custom function.
$y =$this->pdoMultiInsert('nominee', $rowsToInsert, $this->dbinstance);
//$this->dbinstance=null;
}
}
Now my redirect page is like this
<?php
include "controller/formhandller.php";
$x = new formhandller();
if(isset($_POST['steps'])){
if($_POST['steps']==1){
$x->postBasicinformation();
$url = "nominee.php";
header('Location: '.$url);
die();
}
if($_POST['steps']==2){
$x->postProjectawards();
$url = "nominee2.php";
header('Location: '.$url);
die();
}
}
else {
header('Location: '.'index.php');
die();
}
When I am saving the first step that is using postBasicinformation() this function .
It saves in a table called basic_information and gets the id of the inserted row and initialize it to the variable
$lastinsertedid
I want to use this variable in all the next steps to store in other tables.
But right now I am getting NULL
any Idea
Thanks.
I think you are getting confused about the life cycle of a php script.
Anything you do in xxx.php is lost once that script finishes. All objects instantiated during the execution of xxx.php with be lost forever once it finishes.
If you want to preserve some information created in xxx.php for use in yyy.php you have to save it somewhere, either a file or a database or the SESSION or possibly a caching system.
I think this is where you are getting confused. So as you said in a comment if you want to use this lastinsertid in another script the most obvious place to save it between scripts is the $_SESSION array

returning multiple rows from mysql in php

I'm trying to write a PHP-script that will fetch multiple rows from MySQL and return them as a JSONObject, the code works if I try to only fetch 1 row but if I try to get more than one at a time the return string is empty.
$i = mysql_query("select * from database where id = '$v1'", $con);
$temp = 2;
while($row = mysql_fetch_assoc($i)) {
$r[$temp] = $row;
//$temp = $temp +1;
}
If I write the code like this it returns what I expect it to, but if I remove the // from the second row in the while loop it will return nothing. Can anyone explain why this is and what I should do to solve it?
You are using an obsolete mysql_* library.
You are SQL injection prone.
Your code is silly and makes no sense.
If you really wan to stick to it, why simply not do:
while($row = mysql_fetch_assoc($i)) {
$r[] = $row;
}
echo json_encode($r);
And finally, an example using PDO:
$database = 'your_database';
$user = 'your_db_user';
$pass = 'your_db_pass';
$pdo = new \PDO('mysql:host=localhost;dbname='. $database, $user, $pass);
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
try
{
$stmt = $pdo->prepare("SELECT * FROM your_table WHERE id = :id");
$stmt->bindValue(':id', $id);
$stmt->execute();
$results = $stmt->fetchAll(\PDO::FETCH_ASSOC);
}
catch(\PDOException $e)
{
$results = ['error' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine());
}
echo json_encode($results);
You don't need the $temp variable. You can add an element to an array with:
$r[] = $row;

PDO insert query, why isn't this working

I stupidly built my web application with mysqli. Now, I'm trying to convert my data abstraction layer to pdo, but for some reason the insert query is giving me trouble. my shortcut insert function is called from the controller, and I was hoping to keep it in the name format with the table name and column/values array as the parameters.
I commented where I think the problem is below. Please help.
function insert($table, array $columns_values) {
// connect to db
$dbh = $this->db_connect();
$i = 0;
$columns = array();
$values = array();
$params = array();
foreach($columns_values as $column => $value) {
$i++;
$param = array($i => $value);
array_push($params, $param);
array_push($columns, $column);
array_push($values, '?');
}
// turn arrays into comma separated list
$columns = implode(",", $columns);
$values = implode(",", $values);
$stmt = $dbh->prepare("INSERT INTO $table ($columns) VALUES ($values)");
foreach ($params as $param_stmt) {
// i think this is where the problem is
foreach ($param_stmt as $placeholder => $value) {
$stmt->bindParam($placeholder, $value);
}
}
$stmt->execute();
return $stmt;
} // end insert()
I wouldn't do it your way. After a few minutes, I came up with this:
/**
* Function to insert a list of values to the database.
*
* #param PDO $pdo
* #param string $table
* #param array $columns_values
*
* #throws \Exception
* #throws \PDOException
*/
function insert_to_db(PDO $pdo, $table, array $columns_values) {
//Some data validation.
if (empty($columns_values)) {
throw new \Exception("Insert at least one value.");
}
if (empty($table)) {
throw new \Exception("Table may not be empty.");
}
//Implode all of column names. Will become the columns part of the query.
$str_columns = implode(", ", array_keys($columns_values));
//Implode all column names after adding a : at the beginning.
//They will become the placeholders on the values part.
$prepared_column_names = array_map(function ($el) {
return ":$el";
}, array_keys($columns_values));
$prepared_str_columns = implode(", ", $prepared_column_names);
//The query itself. Will look like "INSERT INTO `$table` (col1, col2, col3) VALUES (:col1, :col2, :col3);"
$query = "INSERT INTO `$table` ($str_columns) VALUES ($prepared_str_columns);";
//Prepare the query
$stmt = $pdo->prepare($query);
//Iterate over the columns and values, and bind the value to the placeholder
foreach ($columns_values as $column => $value) {
$stmt->bindValue(":$column", $value);
}
//Execute the query
$stmt->execute();
}
Things I changed
I don't instantiate the PDO object inside of the function. The function needs one in order to work, so it should be one of the arguments!
I throw Exceptions in case of an error. It's a better way of handling errors.
I use named placeholders instead of unnamed ones (:name vs ?). Produces more readable, easier to follow queries, should you ever need to debug.
Added comments to code. Again, you understand what you wrote now, but will you 6 months from now?
I made use of array_keys() to automatically generate an array full of keys (i.e. the columns), instead of looping and manually adding one.
Some tips
When you instantiate a PDO object, make sure it throws PDOExceptions on error! Like so:
new PDO($dsn, $user, $pass, array(PDO::PARAM_ERRMODE => PDO::ERRMODE_EXCEPTION));
or
$pdo = new PDO($dsn, $user, $pass);
$pdo->setAttribute(PDO::PARAM_ERRMODE, PDO::ERRMODE_EXCEPTION);
That way, you don't need to explicitly check for errors each time, you use a single try catch block for the whole thing, and you're good:
try {
insert_to_db($pdo, $table, $array_of_columns_and_values);
}
catch (\Exception $e) { //Will catch all kinds of exceptions, including PDOExceptions
echo $e->getMessage();
}
You haven't checked that your prepare() actually succeeded:
$sql = "INSERT ....";
$stmt = $dbh->prepare($sql);
if (!$stmt) {
die($sql . $dbh->errorInfo());
}
Never assume a query succeeded, especially when you're building one totally dynamically as you are.
Without seeing what your original $columns_values array looks like.
Hope it helps
<?php
function insert($table, $values){
$dbh = $this->db_connect();
$fieldnames = array_keys($values[0]);
$sql = "INSERT INTO $table";
/*** set the field names ***/
$fields = '( ' . implode(' ,', $fieldnames) . ' )';
/*** set the placeholders ***/
$bound = '(:' . implode(', :', $fieldnames) . ' )';
/*** put the query together ***/
$sql .= $fields.' VALUES '.$bound;
//INSERT INTO testtable( id ,col1 ,col2 ) VALUES (:id, :col1, :col2 )
/*** prepare and execute ***/
$query = $dbh->prepare($sql);
foreach($values as $vals){
$query->execute($vals);
/* Array
(
[id] =
[col1] = someval1
[col2] = Someval21
)*/
}
}
//Multi Insert
$insert = array(array('id'=>'','col1'=>'someval1','col2'=>'Someval21'),
array('id'=>'','col1'=>'someval2','col2'=>'Someval22'),
array('id'=>'','col1'=>'someval3','col2'=>'Someval23'),
array('id'=>'','col1'=>'someval4','col2'=>'Someval24')
);
insert('testtable',$insert);
?>

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

Store variables from MySQL select to PHP class variables more efficiently when the table field names are identical to the class variable names?

I am selecting data from a database. The database field names are exactly the same as the class variable names. Is there a way to store this data into the class variables without specifying each one individually?
//gets info about a specified file.
//chosen based on a supplied $fileId
function getFileInfo($fileId)
{
//select info from database
$sql = "SELECT id, companyId, url, name, contentType, fileSize, saved, retrieved
FROM files
WHERE id = $fileId";
$results = $this->mysqli->query($sql);
$results = $results->fetch_object();
//store info into class variables
$this->id = $results->id;
$this->companyId = $results->companyId;
$this->url = $results->url;
$this->name = $results->name;
$this->contentType = $results->contentType;
$this->fileSize = $results->fileSize;
$this->saved = $results->saved;
$this->retrieved = $results->retrieved;
}
A quick and dirty way would ba a loop:
foreach($result as $var => $value) {
$this->$var = $value;
}
I'd propose this approach:
$nameMap = array(
'id',
'companyId',
'url',
'name',
'contentType',
'fileSize',
'saved',
'retrieved',
);
foreach( $nameMap as $attributeName ) {
$this->$attributeName = $results->$attributeName ;
}
While one could write
foreach($result as $var => $value) {
...
}
the outcome fully depends on backing table's structure. If you add further attributes to the table, your code might break.
Using $nameMap, the application still works.
Just use foreach structure:
foreach ($result as $column => $value) {
$this->$column = $value;
}
Not nice but will work.
Humm. Well, PDO has native functions for that, if you're not married to mysqli for some reason:
<?php
try {
$dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass);
} catch (PDOException $e) {
print "Error!: " . $e->getMessage() . "<br/>";
die();
}
$sth = $dbh->prepare("SELECT name, colour FROM fruit");
$sth->execute();
$result = $sth->fetch(PDO::FETCH_OBJ);
print $result->NAME;
The biggest disadvantage I've found is that PDO doesn't support SSL connections between the PHP machine and the MySQL machine.

Categories