Insert/update helper function using PDO - php

I have a very simple helper function to produce SET statement for traditional plain mysql driver usage:
function dbSet($fields) {
$set='';
foreach ($fields as $field) {
if (isset($_POST[$field])) {
$set.="`$field`='".mysql_real_escape_string($_POST[$field])."', ";
}
}
return substr($set, 0, -2);
}
used like this
$id = intval($_POST['id']);
$fields = explode(" ","name surname lastname address zip fax phone");
$_POST['date'] = $_POST['y']."-".$_POST['m']."-".$_POST['d'];
$query = "UPDATE $table SET ".dbSet($fields)." stamp=NOW() WHERE id=$id";
it makes code quite DRY and easy but flexible at the same time.
I gotta ask if anyone willing to share a similar function, utilizing PDO prepared statements feature?
I am still in doubts, how to accomplish this.
Is there a straight and simple way to use PDO prepared statements to insert data?
What form it should be? Query builder helper? Or insert query helper? What parameters it should take?
I hope it can be easy enough to be used as an answer here on SO. Because in the every topic we can see prepared statements usage recommendation, but there is not a single good example. Real life example, I mean. To type bind_param() 20 times is not a good programming style I believe.
And even 20 question marks too.

I usually have a class extending PDO, but my class is pretty custom. If I get it cleaned up and tested I will post it at a later time. Here is a solution to your system, however.
function dbSet($fields, &$values) {
$set = '';
$values = array();
foreach ($fields as $field) {
if (isset($_POST[$field])) {
$set .= "`$field` = ?,";
$values[] = $_POST[$field];
}
}
return rtrim($set, ',');
}
$fields = explode(" ","name surname lastname address zip fax phone date");
$_POST['date'] = $_POST['y']."-".$_POST['m']."-"$_POST['d'];
$query = "UPDATE $table SET ".dbSet($fields, $values).", stamp=NOW() WHERE id=?";
$values[] = $id;
$dbh->prepare($query);
$dbh->execute($values);
This may not be perfect and could use tweaking. It takes into account that $dbh is setup with a PDO Connection. Pending any minor syntax issues I made, that should work.
EDIT
Really though, I think I would go for Doctrine ORM (or another ORM). As you setup the model and add all the validation there, then it is as simple as:
$table = new Table();
$table->fromArray($_POST);
$table->save();
That should populate the contents easily. This is of course with an ORM, like Doctrine.
UPDATED
Did some minor tweaks to the first code, such as putting isset back and using rtrim over substr. Going to work on providing a mock up of a PDO Extension class just gotta layout the way to do it and do some unit tests to make sure it works.

Thanks to everyone.
Every answer was helpful and I wish I could split the bounty.
At the end, to my surprise, I was able to make it the same way as before, based on on accepted answer
$fields = array("login","password");
$_POST['password'] = MD5($_POST['login'].$_POST['password']);
$stmt = $dbh->prepare("UPDATE users SET ".pdoSet($fields,$values)." WHERE id = :id");
$values["id"] = $_POST['id'];
$stmt->execute($values);
It can be wrapped into a helper function, but I doubt there is necessity. It will shorten the code by just one line.
pdoSet code:
function pdoSet($fields, &$values, $source = array()) {
$set = '';
$values = array();
if (!$source) $source = &$_POST;
foreach ($fields as $field) {
if (isset($source[$field])) {
$set.="`$field`=:$field, ";
$values[$field] = $source[$field];
}
}
return substr($set, 0, -2);
}

i would extend the Core PDO Class andd a method like so:
class Database extends PDO
{
public function QueryFromPost($Query,$items)
{
$params = array();
$Query .= ' WHERE ';
foreach($items as $key => $default)
{
$Query .= ' :' . $key. ' = ' . $key;
if(isset($_POST[$key]))
{
$params[':' . $key] = $_POST[$key];
}else
{
$params[':' . $key] = $default;
}
}
$s = $this->prepare($Query);
return $s->execute($params);
}
}
Then use like so
$db = new Database(/*..Default PDO Params*/);
$statement = $db->QueryFromPost('SELECT * FROM employees',array('type' => 'plc'));
foreach($preparedStatement->fetchAll() as $row)
{
//...
}
But as its already been said that you should be VERY weary of what your trying to do, you need to validate your data, its been sanitized but you not validated.

I've been patching something trivial together for what I consider recurring parameter binding cases.
http://fossil.include-once.org/hybrid7/wiki/db
Anyway; it provides some alternative prepared statement placeholders. Your example could be shortened into:
db("UPDATE table SET :, WHERE id=:id", $columns[], $where[]);
This case only works with named parameters, so $set would be array("name"=>..) and $where=array("id"=>123). The :, gets expanded on the first array you pass. It's replaced with comma-separated name=:name pairs (that's why its mnemonic is :,).
There are a few more placeholders :, :& :: and :? for different use cases. Only the ?? is really somewhat of a standard. So it needs some getting used to, but it significantly simplifies prepared statements and array binding (which PDO doesn't do natively).

Here's my general database abstraction class. Take a look at the autoExecute() function. It offers tons of flexibility for whatever it is you might want to accomplish. I should warn that this was written for PHP 5.3, and has been slightly tailored for PostgreSQL.
<?php
/**
* Database abstraction and query result classes
* Requires PHP 5.3
*
* Events:
* - on_commit - Dispatched when the transaction is successfully committed to the DB
* - on_rollback - Dispatched when the transaction is rolled back in the DB
*
* #author Kenaniah Cerny <kenaniah#gmail.com>
* #version 1.1.2
* #license http://creativecommons.org/licenses/by/3.0/us/
* #copyright Copyright (c) 2009, Kenaniah Cerny
*/
class Database extends PDO {
private $stmt;
private $good_trans = null;
private $nested_transactions = 0; //Keeps track of virtual transaction nesting level
private $callbacks = array();
private static $connections = array(); //Keeps track of opened connections
/**
* Returns a database instance using lazy instantiation
* #param string $name a database connection name
* #param array $config database config details for a new connection
*/
static function getInstance($name = 'main', $config=array()){
//Attempt to return an existing connection
if(array_key_exists($name, self::$connections)):
return self::$connections[$name];
endif;
//Attempt to create a new connection
$host = in_array($config['host'], array('localhost', '127.0.0.1')) ? "" : ";host=" . $config['host'];
$db = new Database($config['driver'].":dbname=".$config['name'].$host, $config['user'], $config['pass']);
//Save to connection pool
self::$connections[$name] = $db;
return $db;
}
/**
* Registers a callback to be run when the given event is invoked
* #param string $event Event name
* #param callable $callable
*/
public function register_listener($event, $callable){
if(!array_key_exists($event, $this->callbacks)):
$this->callbacks[$event] = array($callable);
else:
$this->callbacks[$event][] = $callable;
endif;
}
/**
* Invokes callbacks for the given event type
* #param string $event Event name
* #param boolean $stop_on_false Stops bubbling this event if one of the handlers returns false
*/
protected function dispatch_event($event, $stop_on_false = true){
if(!array_key_exists($event, $this->callbacks)) return;
foreach($this->callbacks[$event] as $callable):
$res = call_user_func($callable, $this, $event);
if($stop_on_false && $res === false) return false;
endforeach;
return true;
}
/**
* PDO Constructor
* #param $dsn
* #param $username
* #param $password
*/
function __construct($dsn, $username, $password) {
parent::__construct($dsn, $username, $password);
}
/**
* Prepares an SQL statement
* #param string $sql
*/
function prepare($sql) {
$stmt = parent::prepare($sql, array(PDO::ATTR_STATEMENT_CLASS => array(__NAMESPACE__.'\DatabaseStatement')));
$stmt->setFetchMode(PDO::FETCH_ASSOC);
return $stmt;
}
/**
* Prepares an executes an SQL statement with the parameters provided
* #param string $sql
* #param array $params
*/
function execute($sql, $params = array()) {
if($this->debug):
var_dump("Statement:\n".$sql."\nParams: ".$this->fmt($params));
endif;
try {
$stmt = $this->prepare($sql);
$val = $stmt->execute((array) $params);
if($stmt->errorCode() != '00000') error_log($this->errormsg());
if($this->debug && $stmt->errorCode() != '00000'){
var_dump($stmt->errorInfo());
Errors::add("Database error: ".$this->errormsg(), E_USER_ERROR);
}
if(!$val) return false;
} catch (PDOException $e){
if($this->debug) var_dump($stmt->errorInfo());
error_log($this->errormsg());
Errors::add("Database error: ".$this->errormsg(), E_USER_ERROR);
if($this->nested_transactions) $this->failTrans();
else throw $e;
}
$this->stmt = $stmt;
return $stmt;
}
/**
* Returns the value of the first column of the first row
* of the database result.
* #param $sql
* #param $params
*/
function getOne($sql, $params = array()){
$stmt = $this->execute($sql, $params);
return $stmt ? $stmt->getOne() : false;
}
/**
* Fetches a single column (the first column) of a result set
* #param $sql
* #param $params
*/
function getCol($sql, $params = array()){
$stmt = $this->execute($sql, $params);
return $stmt ? $stmt->getCol() : false;
}
/**
* Fetches rows in associative array format
* #param $sql
* #param $params
*/
function getAssoc($sql, $params = array()){
$stmt = $this->execute($sql, $params);
return $stmt ? $stmt->getAssoc() : false;
}
/**
* Fetches rows in array format with columns
* indexed by ordinal position
* #param $sql
* #param $params
*/
function getArray($sql, $params = array()){
$stmt = $this->execute($sql, $params);
return $stmt ? $stmt->getArray() : false;
}
/**
* Fetches all rows in associative array format
* #param $sql
* #param $params
*/
function getAll($sql, $params = array()){
return $this->getAssoc($sql, $params);
}
/**
* Fetches rows in array format where the first column
* is the key name and all other columns are values
* #param $sql
* #param $params
*/
function getKeyPair($sql, $params = array()){
$stmt = $this->execute($sql, $params);
return $stmt ? $stmt->getKeyPair() : false;
}
/**
* Fetches rows in multi-dimensional format where the first
* column is the key name and all other colums are grouped
* into associative arrays for each row
* #param $sql
* #param $params
*/
function getGroup($sql, $params = array()){
$stmt = $this->execute($sql, $params);
return $stmt ? $stmt->getGroup() : false;
}
/**
* Fetches only the first row and returns it as an
* associative array
* #param $sql
* #param $params
*/
function getRow($sql, $params = array()){
$stmt = $this->execute($sql, $params);
return $stmt ? $stmt->getRow() : false;
}
/**
* Internal function used for formatting parameters in debug output
* #param unknown_type $params
*/
private function fmt($params){
$arr = array();
foreach((array) $params as $k=>$v){
if(is_null($v)) $v = "NULL";
elseif(is_bool($v)) $v = $v ? "TRUE" : "FALSE";
$arr[] = "[".$k."] => ".$v;
}
return "Array(".join(", ", $arr).")";
}
/**
* Returns the number of affected rows from an executed statement
*/
function affected_rows(){
return $this->stmt ? $this->stmt->rowcount() : false;
}
/**
* Automated statement processing
*
* Params array takes the following fields:
*
* - table The name of the table to run the query on
*
* - data A key-value paired array of table data
*
* - mode INSERT, UPDATE, REPLACE, or NEW
*
* - where Can be a string or key-value set. Not used on INSERTs
* If key-value set and numerically indexed, uses values from data
* If key-value and keys are named, uses its own values
*
* - params An array of param values for the where clause
*
* - returning Optional string defining what to return from query.
* Uses PostgreSQL's RETURNING construct
*
* This method will return either a boolean indicating success, an array
* containing the data requested by returning, or a boolean FALSE indicating
* a failed query.
*
*/
function autoExecute($table, $params, $data){
$fields = array(); //Temp array for field names
$values = array(); //Temp array for field values
$set = array(); //Temp array for update sets
$ins = array(); //Insert value arguments
$params['table'] = $table;
$params['data'] = $data;
$params['params'] = (array) $params['params'];
//Parse the data set and prepare it for different query types
foreach((array) $params['data'] as $field => $val):
$fields[] = $field;
$values[] = $val;
$ins[] = "?";
$set[] = $field . " = ?";
endforeach;
//Check for and convert the array/object version of the where clause param
if(is_object($params['where']) || is_array($params['where'])):
$clause = array();
$params['params'] = array(); //Reset the parameters list
foreach($params['where'] as $key => $val):
if(is_numeric($key)):
//Numerically indexed elements use their values as field names
//and values from the data array as param values
$field = $val;
$params['params'][] = $params['data'][$val];
else:
//Named elements use their own names and values
$field = $key;
$params['params'][] = $val;
endif;
$clause[] = $field . " = ?";
endforeach;
$params['where'] = join(" AND ", $clause);
endif;
//Figure out what type of query we want to run
$mode = strtoupper($params['mode']);
switch($mode):
case 'NEW':
case 'INSERT':
//Build the insert query
if(count($fields)):
$sql = "INSERT INTO " . $params['table']
. " (" . join(", ", $fields) . ")"
. " SELECT " . join(", ", $ins);
else:
$sql = "INSERT INTO " . $params['table']
. " DEFAULT VALUES";
endif;
//Do we need to add a conditional check?
if($mode == "NEW" && count($fields)):
$sql .= " WHERE NOT EXISTS ("
. " SELECT 1 FROM " . $params['table']
. " WHERE " . $params['where']
. " )";
//Add in where clause params
$values = array_merge($values, $params['params']);
endif;
//Do we need to add a returning clause?
if($params['returning']):
$sql .= " RETURNING " . $params['returning'];
endif;
//Execute our query
$result = $this->getRow($sql, $values);
//Return our result
if($params['returning']):
return $result;
else:
return $result !== false;
endif;
break;
case 'UPDATE':
if(!count($fields)) return false;
//Build the update query
$sql = "UPDATE " . $params['table']
. " SET " . join(", ", $set)
. " WHERE " . $params['where'];
//Do we need to add a returning clause?
if($params['returning']):
$sql .= " RETURNING " . $params['returning'];
endif;
//Add in where clause params
$values = array_merge($values, $params['params']);
//Execute our query
$result = $this->getRow($sql, $values);
//Return our result
if($params['returning']):
return $result;
else:
return $result !== false;
endif;
break;
case 'REPLACE': //UPDATE or INSERT
//Attempt an UPDATE
$params['mode'] = "UPDATE";
$result = $this->autoExecute($params['table'], $params, $params['data']);
//Attempt an INSERT if UPDATE didn't match anything
if($this->affected_rows() === 0):
$params['mode'] = "INSERT";
$result = $this->autoExecute($params['table'], $params, $params['data']);
endif;
return $result;
break;
case 'DELETE':
//Don't run if we don't have a where clause
if(!$params['where']) return false;
//Build the delete query
$sql = "DELETE FROM " . $params['table']
. " WHERE " . $params['where'];
//Do we need to add a returning clause?
if($params['returning']):
$sql .= " RETURNING " . $params['returning'];
endif;
//Execute our query
$result = $this->getRow($sql, $params['params']);
//Return our result
if($params['returning']):
return $result;
else:
return $result !== false;
endif;
break;
default:
user_error('AutoExecute called incorrectly', E_USER_ERROR);
break;
endswitch;
}
/**
* #see $this->startTrans()
*/
function beginTransaction(){
$this->startTrans();
}
/**
* Starts a smart transaction handler. Transaction nesting is emulated
* by this class.
*/
function startTrans(){
$this->nested_transactions++;
if($this->debug) var_dump("Starting transaction. Nesting level: " . $this->nested_transactions);
//Do we need to begin an actual transaction?
if($this->nested_transactions === 1):
parent::beginTransaction();
$this->good_trans = true;
$this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
endif;
}
/**
* Returns TRUE if the transaction will attempt to commit, and
* FALSE if the transaction will be rolled back upon completion.
*/
function isGoodTrans(){
return $this->good_trans;
}
/**
* Marks a transaction as a failure. Transaction will be rolled back
* upon completion.
*/
function failTrans(){
if($this->nested_transactions) $this->good_trans = false;
if($this->debug):
Errors::add("Database transaction failed: ".$this->errorMsg());
endif;
$this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
}
/**
* #see $this->rollbackTrans()
*/
function rollback(){
$this->rollbackTrans();
}
/**
* Rolls back the entire transaction and completes the current nested
* transaction. If there are no more nested transactions, an actual
* rollback is issued to the database.
*/
function rollbackTrans(){
if($this->nested_transactions):
$this->nested_transactions--;
if($this->debug) var_dump("Rollback requested. New nesting level: " . $this->nested_transactions);
$this->good_trans = false;
if($this->nested_transactions === 0):
$this->good_trans = null;
$this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
if($this->debug) var_dump("Transaction rolled back.");
parent::rollback();
$this->dispatch_event('on_rollback');
endif;
endif;
}
/**
* Clears the nested transactions stack and issues a rollback to the database.
*/
function fullRollback(){
while($this->nested_transactions) $this->rollbackTrans();
}
/**
* Returns the number of nested transactions:
* 0 - There is no transaction in progress
* 1 - There is one transaction pending
* >1 - There are nested transactions in progress
*/
function pending_trans(){
return $this->nested_transactions;
}
/**
* #see $this->completeTrans()
*/
function commit($fail_on_user_errors = false){
return $this->completeTrans($fail_on_user_errors);
}
/**
* Completes the current transaction and issues a commit or rollback to the database
* if there are no more nested transactions. If $fail_on_user_errors is set, the
* transaction will automatically fail if any errors are queued in the Errors class.
* #param boolean $fail_on_user_errors
*/
function completeTrans($fail_on_user_errors = false){
if(!$this->nested_transactions) return;
//Fail the transaction if we have user errors in the queue
if($fail_on_user_errors && Errors::exist()) $this->good_trans = false;
//Do we actually need to attempt to commit the transaction?
if($this->nested_transactions === 1):
if(!$this->good_trans || !parent::commit()){
if($this->debug) var_dump("Transaction failed: " . $this->errormsg());
$this->rollbackTrans();
return false;
}
//Transaction was good
$this->nested_transactions--;
$this->good_trans = null;
$this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
if($this->debug) var_dump("Transaction committed.");
$this->dispatch_event('on_commit', false);
return true;
else:
//Don't take action just yet as we are still nested
$this->nested_transactions--;
if($this->debug) var_dump("Virtual commit. New nesting level: " . $this->nested_transactions);
endif;
return $this->good_trans;
}
/**
* Returns the text of the most recently encountered error
*/
function errormsg(){
$msg = $this->errorInfo();
return $msg[2];
}
}
class DatabaseStatement extends \PDOStatement implements \Countable {
/**
* Binds passed parameters according to their PHP type and executes
* the prepared statement
*/
function execute($params = array()) {
$i = 1;
foreach($params as $k => $v):
$mode = PDO::PARAM_STR;
if(is_null($v)) $mode = PDO::PARAM_NULL;
elseif(is_bool($v)) $mode = PDO::PARAM_BOOL;
elseif(is_resource($v)) $mode = PDO::PARAM_LOB;
$this->bindParam($i, $params[$k], $mode);
$i++;
endforeach;
$ok = parent::execute();
return $ok ? $this : false;
}
/**
* Returns the value of the first column of the first row
*/
function getOne() {
return $this->fetchColumn(0);
}
/**
* Returns an array of values of the column found at $index
* position.
* #param $index
*/
function getCol($index=0) {
return $this->fetchAll(PDO::FETCH_COLUMN, $index);
}
/**
* Returns all rows in numeric array format
*/
function getArray(){
return $this->fetchAll(PDO::FETCH_NUM);
}
/*
* Returns all rows in associative array format
*/
function getAll(){
return $this->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Returns all rows in associative array format
*/
function getAssoc() {
return $this->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Returns rows in multi-dimensional format where the first
* column is the key name and all other colums are grouped
* into associative arrays for each row
*/
function getGroup() {
return $this->fetchAll(PDO::FETCH_GROUP);
}
/**
* Returns a single row in associative format
*/
function getRow(){
return $this->fetch(PDO::FETCH_ASSOC);
}
/**
* Fetches rows in array format where the first column
* is the key name and all other columns are values
*/
function getKeyPair(){
//Emulate it
$tmp = $this->fetchAll(PDO::FETCH_ASSOC);
$arr = array();
for($i = 0; $i < count($tmp); $i++){
$arr[array_shift($tmp[$i])] = count($tmp[$i]) > 1 ? $tmp[$i] : array_shift($tmp[$i]);
}
return $arr;
}
/**
* Returns the number of rows returned by this statement
*/
function recordCount(){
return $this->rowCount();
}
/**
* Returns the number of rows returned by this statement
*/
function count(){
return $this->rowCount();
}
}

Even though my DB class does not use prepared statements I still want to mention it here. I see no reason at all to implement everything with prepared statements. I do know that prepared statements are faster, but only when used multiple times. If you execute the query only once (and this is the only type of query I normally need to use), it is slower. Thus it is counterproductive to use prepared statements everywhere.
Proper description of the class may be found some place else at stackoverflow. But here some of the good stuff:
Less then 100 lines database layer
DB::x for DB::instance()->execute and DB::q for DB::instance()->query
autoQuoting with two types of placeholders ? and ?x (where x may be ,, & and |). The ?, placeholder may be used as an UPDATE helper here.
But for full information see the stackoverflow post linked above ;)
PS: The README in the repo does not apply to this class. It is for the normal DB.php, not for DB_intelligent.php.
PPS: The class is written for PHP 5.3. If you want to use it on PHP 5.2 simply copy all those PDO methods from DB_forPHP52.php to DB_intelligent.php and remove the __callStatic method.

Just in addition to other answers: a method for proper quote of column names:
/**
* Escape identifier (database/table/column name) - ie. if you're using MySQL:
* db_name.tbl_name.col_name -> `db_name`.`tbl_name`.`col_name`
**/
protected function quoteIdentifier($identifier) {
static $escapeChars = array(
'mysql' => '``',
'oracle' => '""',
'mssql' => '[]',
//...
);
$escape = $escapeChars[$this->getAttribute(self::ATTR_DRIVER_NAME)];
$identifier = (array) explode('.', $identifier);
$identifier = array_map(function($segment) use($escape) {
return $escape[0] . $segment . $escape[1];
}, $identifier);
return implode('.', $identifier);
}

You can extend PDO like that:
class CustomPDO extends PDO {
public function updateTable($sTable, array $aValues = array()){
if (!empty($aValues) && !empty($sTable)){
# validation of table / columns name
$sTable = mysql_real_escape_string($sTable);
$aColumns = array_map('mysql_real_escape_string',array_keys($aValues));
$aElements = array();
foreach ($aColumns as $sColumn){
$aElements[] = "`$sColumn`= :$sColumn";
} // foreach
$sStatement = "UPDATE $sTable SET " . implode(',', $aElements);
$oPDOStatement = $this->prepare($sStatement);
if ($oPDOStatement){
return $oPDOStatement->execute($aValues);
} // if
} // if
return false;
} // updateTable
}
# usage :
# $oDb->updateTable('tbl_name',$_POST);
# test
error_reporting (E_ALL);
ini_Set('display_errors',1);
$oDb = new CustomPDO('sqlite::memory:');
$oDb->exec('CREATE TABLE t1(c1 TEXT, c2 INTEGER)');
$oDb->exec("INSERT INTO t1(c1, c2) VALUES ('X1',1)");
var_dump($oDb->query('SELECT * FROM t1')->fetchAll(PDO::FETCH_ASSOC));
$oDb->updateTable('t1', array('c1'=>'f1','c2**2'=>2));
var_dump($oDb->query('SELECT * FROM t1')->fetchAll(PDO::FETCH_ASSOC));

Like others I've extended the standard PDO class to suit my needs. Something along the lines of this may suit you:
Class ExtendedPDO extends PDO
{
public function prepareArray($sql, array $data)
{
// Call the standard prepare method
$statement = parent::prepare($sql);
foreach ($data as $field=>$value) {
$statement->bindValue(':' . $field, $value);
}
return $statement;
}
}
Then you can use it quite simply:
// Include connection variables
include '../includes/config/database.php';
// The data to use in the query
$data = array(
'title' => 'New value',
'id' => 1,
);
// The query you want to run
$sql = '
UPDATE
test
SET
title = :title
WHERE
id = :id
';
try {
// Connect to the database
$dbh = new ExtendedPDO(PDO_DSN, PDO_USERNAME, PDO_PASSWORD);
// Attach the data to your query
$stmt = $dbh->prepareArray($sql, $data);
// Run it
$stmt->execute();
} catch (PDO Exception $e) {
echo $e->getMessage();
}

Insert queries often require many placeholders. The question mark style is then hard to read, and named parameters are repetitive and prone to typing errors. So, I created a function for the whole insert query:
function insert($table, $col_val){
global $db;
$table = preg_replace('/[^\da-z_]/i', '', $table);
$smt = $db->prepare("DESCRIBE `$table`");
$smt->execute();
$columns = $smt->fetchAll(PDO::FETCH_COLUMN);
$sets = array();
$exec = array();
foreach($col_val as $col => $val){
if(!in_array($col, $columns))
return false;
$sets[] .= "`$col`=?";
$exec[] = $val;
}
$set = implode(',', $sets);
$smt = $db->prepare("INSERT INTO `$table` SET $set");
$smt->execute($exec);
return $db->lastInsertId();
}
Usage is simple:
insert('table_name', array(
'msg' => 'New message',
'added' => date('Y-m-d H:i:s'),
));
And if you need lastInsertId():
$new_id = insert(...

Reference: How can I prevent SQL injection in PHP?
$preparedStatement = $db->prepare('SELECT * FROM employees WHERE name = :name');
$preparedStatement->execute(array(':name' => $name));
$rows = $preparedStatement->fetchAll();

Related

Inserting multiple rows at once with prepared statements

I would like to know how can I insert multiple values in an array via prepared statements. I've looked at these two (this question and this other one ) questions but they don't seem to do what I'm trying. This is what I have:
$stmt = $this->dbh->prepare("INSERT INTO
t_virtuemart_categories_en_gb
(category_name, virtuemart_category_id)
VALUES
(:categoryName, :categoryId)
;");
foreach($this->values as $insertData){
$categoryName = $insertData['categoryName'];
$categoryId = $insertData['categoryId'];
$stmt->bindParam(':categoryName', $categoryName);
$stmt->bindParam(':categoryId', $categoryId);
$stmt->execute();
}
I tried placing the prepare line inside the foreach loop and outside, but it only adds the first key in the array, and I don't understand why.
This is my Connection.php file:
<?php
$hostname = 'localhost';
$username = 'root';
$password = '';
function connectDB ($hostname, $username, $password){
$dbh = new PDO("mysql:host=$hostname;dbname=test", $username, $password);
return $dbh;
}
try {
$dbh = connectDB ($hostname, $username, $password);
} catch(PDOException $e) {
echo $e->getMessage();
}
And my Import.php file:
<?php
class Import{
public function __construct($dbh, $values) {
$this->dbh = $dbh;
$this->values = $values;
}
public function importData() {
$stmt = $this->dbh->prepare("INSERT INTO
t_virtuemart_categories_en_gb
(category_name, virtuemart_category_id)
VALUES
(:categoryName, :categoryId)
;");
foreach($this->values as $insertData){
$categoryName = $insertData['categoryName'];
$categoryId = $insertData['categoryId'];
$stmt->bindParam(':categoryName', $categoryName);
$stmt->bindParam(':categoryId', $categoryId);
$stmt->execute();
}
}
}
Working principle:
Use only one INSERT sql statement to add multiple records, defined by your values pairs. In order to achieve this you have to build the corresponding sql statement in the form
INSERT INTO [table-name] ([col1],[col2],[col3],...) VALUES (:[col1],:[col2],:[col3],...), (:[col1],:[col2],:[col3],...), ...
by iterating through your values array.
Notes:
I hope you'll understand all. I commented as much as I could. I
didn't test it, but it should work. Maybe an answer I wrote a
short time ago will give you further ideas regarding structuring of
data access classes/functions as well.
Never use ";" at the end of the sql statements when you define them in PHP.
Never use one input marker to bind multiple values. For each value to bind use a unique named input marker.
Good luck.
Connection.php
<?php
$hostname = 'localhost';
$username = 'root';
$password = '';
$port = 3306;
try {
// Create a PDO instance as db connection to a MySQL db.
$connection = new PDO(
'mysql:host='. $hostname .';port='.$port.';dbname=test'
, $username
, $password
);
// Assign the driver options to the db connection.
$connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, FALSE);
$connection->setAttribute(PDO::ATTR_PERSISTENT, TRUE);
} catch (PDOException $exc) {
echo $exc->getMessage();
exit();
} catch (Exception $exc) {
echo $exc->getMessage();
exit();
}
Import.php:
<?php
class Import {
/**
* PDO instance as db connection.
*
* #var PDO
*/
private $connection;
/**
*
* #param PDO $connection PDO instance as db connection.
* #param array $values [optional] Values list.
*/
public function __construct(PDO $connection, array $values = array()) {
$this->connection = $connection;
$this->values = $values;
}
/**
* Import data.
*
* #return int Last insert id.
* #throws PDOException
* #throws UnexpectedValueException
* #throws Exception
*/
public function importData() {
/*
* Values clauses list. Each item will be
* later added to the sql statement.
*
* array(
* 0 => '(:categoryName0, :categoryId0)',
* 1 => '(:categoryName1, :categoryId1)',
* 2 => '(:categoryName2, :categoryId2)',
* )
*/
$valuesClauses = array();
/*
* The list of the input parameters to be
* bound to the prepared statement.
*
* array(
* :categoryName0 => value-of-it,
* :categoryId0 => value-of-it,
* :categoryName1 => value-of-it,
* :categoryId1 => value-of-it,
* :categoryName2 => value-of-it,
* :categoryId2 => value-of-it,
* )
*/
$bindings = array();
/*
* 1) Build a values clause part for each array item,
* like '(:categoryName0, :categoryId0)', and
* append it to the values clauses list.
*
* 2) Append each value of each item to the input
* parameter list.
*/
foreach ($this->values as $key => $item) {
$categoryName = $item['categoryName'];
$categoryId = $item['categoryId'];
// Append to values clauses list.
$valuesClauses[] = sprintf(
'(:categoryName%s, :categoryId%s)'
, $key
, $key
);
// Append to input parameters list.
$bindings[':categoryName' . $key] = $categoryName;
$bindings[':categoryId' . $key] = $categoryId;
}
/*
* Build the sql statement in the form:
* INSERT INTO [table-name] ([col1],[col2],[col3]) VALUES
* (:[col1],:[col2],:[col3]), (:[col1],:[col2],:[col3]), ...
*/
$sql = sprintf('INSERT INTO t_virtuemart_categories_en_gb (
category_name,
virtuemart_category_id
) VALUES %s'
, implode(',', $valuesClauses)
);
try {
// Prepare the sql statement.
$statement = $this->connection->prepare($sql);
// Validate the preparing of the sql statement.
if (!$statement) {
throw new UnexpectedValueException('The sql statement could not be prepared!');
}
/*
* Bind the input parameters to the prepared statement
* and validate the binding of the input parameters.
*
* -----------------------------------------------------------------------------------
* Unlike PDOStatement::bindValue(), when using PDOStatement::bindParam() the variable
* is bound as a reference and will only be evaluated at the time that
* PDOStatement::execute() is called.
* -----------------------------------------------------------------------------------
*/
foreach ($bindings as $key => $value) {
// Read the name of the input parameter.
$inputParameterName = is_int($key) ? ($key + 1) : (':' . ltrim($key, ':'));
// Read the data type of the input parameter.
if (is_int($value)) {
$inputParameterDataType = PDO::PARAM_INT;
} elseif (is_bool($value)) {
$inputParameterDataType = PDO::PARAM_BOOL;
} else {
$inputParameterDataType = PDO::PARAM_STR;
}
// Bind the input parameter to the prepared statement.
$bound = $statement->bindValue($inputParameterName, $value, $inputParameterDataType);
// Validate the binding.
if (!$bound) {
throw new UnexpectedValueException('An input parameter could not be bound!');
}
}
// Execute the prepared statement.
$executed = $statement->execute();
// Validate the prepared statement execution.
if (!$executed) {
throw new UnexpectedValueException('The prepared statement could not be executed!');
}
/*
* Get the id of the last inserted row.
*/
$lastInsertId = $this->connection->lastInsertId();
} catch (PDOException $exc) {
echo $exc->getMessage();
// Only in development phase !!!
// echo '<pre>' . print_r($exc, TRUE) . '</pre>';
exit();
} catch (Exception $exc) {
echo $exc->getMessage();
// Only in development phase !!!
// echo '<pre>' . print_r($exc, TRUE) . '</pre>';
exit();
}
return $lastInsertId;
}
}
I think the statements have to be prepared and bound separately for each iteration:
if($stmt = $this->dbh->prepare("INSERT INTO t_virtuemart_categories_en_gb (category_name, virtuemart_category_id) VALUES (:categoryName, :categoryId);")){
foreach($this->values as &$insertData){
$stmt->bindParam(':categoryName', $insertData['categoryName']);
$stmt->bindParam(':categoryId', $insertData['categoryId']);
$stmt->execute();
$stmt->close();
}
}
I would suggest this, using a $dbh = mysqli_connect():
<?php
class Import{
public function __construct($dbh, $values) {
$this->dbh = $dbh;
$this->values = $values;
}
public function importData() {
$stmt = $this->dbh->prepare("INSERT INTO t_virtuemart_categories_en_gb
(category_name, virtuemart_category_id)
VALUES
(?, ?)");
$catetoryName = ''; $categoryId = '';
$stmt->bind_param('ss', $categoryName, $categoryId);
foreach($this->values as $insertData){
$categoryName = $insertData['categoryName'];
$categoryId = $insertData['categoryId'];
$stmt->execute();
}
}
}
In this way you create a reference and bind that variable to the execution of the prepared statement.
You should be careful about it when passing an array, the trick is commented in the php.net page ( http://php.net/manual/it/mysqli.prepare.php )
A code that work is:
$typestring = 'sss'; //as many as required: calc those
$stmt = $dbconni->prepare($ansqlstring);
$refs = [$typestring];
// if $source is an array of array [ [f1,f2,...], [f1,f2,...], ...]
foreach($source as $data) {
if (count($refs)==1) {
foreach ($data as $k => $v) {
$refs[] = &$data[$k];
}
$ref = new \ReflectionClass('mysqli_stmt');
$method = $ref->getMethod("bind_param");
$method->invokeArgs($stmt, $refs);
$r = $stmt->execute();
} else {
// references are maintained: no needs to bind_param again
foreach ($data as $k => $v) {
$refs[$k+1] = $v;
}
$r = $stmt->execute();
}
}
this spare resources and is more performant, but you have to make benchmark to be sure about my words.
this is one of the case where prepared statement make sense, see
https://joshduff.com/2011-05-10-why-you-should-not-be-using-mysqli-prepare.md
Normally PDO emulate prepared statement, see PDO::ATTR_EMULATE_PREPARES
EDIT: specify it is using mysqli, and correct the code.

Search More than 1 value inside 1 input text of HTML search form

I have an usual search form using HTML that will be extract the data from mysql (the connection is using PHP and mysqli). The function works well in searching 1 value, but I want to make user can search more than 1 value that will be separated by comma (,).
For example:
Searching 1 value works well.
But I want to search 2 or more values inside the search box, like: 42-7278954,53-1217544,07-2517487,...
I hope user can input something like in the pic below and the result will have 2 rows -->
CN_no 42-7278954 and 53-1217544:
The query I have so far is:
$sql = "SELECT * FROM mock_data WHERE CN_no IN ('{$CN_no}') OR doc_no IN ('{$doc_no}')";
Notes: CN_no is "Shipment Code" and doc_no is "Reference No"
But well... it's obviously give me an error because of the incorrect syntax.
Please help me to revise it. Thank you.
======== update query based on vp_arth's answer ========
$cn = explode(',', $CN_no);
$incn = str_repeat('?, ', count($cn)-1).'?';
$doc = explode(',', $doc_no);
$indoc = str_repeat('?, ', count($doc)-1).'?';
$query = "SELECT * FROM mock_data WHERE CN_no IN ({$incn}) or doc_no IN ({$indoc})";
$result = $conn->query($query , array_merge($incn, $indoc));
But it give me an error
This is a possible solution to use prepared statements with dynamic input in mysqli. The typing for the parameter binding is static though, in this case the parameters are strings.
/**
* connecting to the database
* defining in how manye columns you want to search (important to create the correct amount of arguments)
*/
$conn = new mysqli('localhost', 'root', '', 'test');
$columnsToSearch = 2;
/**
* the numbers you want to search delimited by ","
*/
$CN_no = '42-7278954,53-1217544,07-2517487';
$cn = explode(',', $CN_no);
/**
* writing the numbers to search into variables
* putting the references of those variables into an array
*
* the references will be used as arguments for the prepared statement
*/
$values = array();
for ($i = 0; $i < $columnsToSearch; $i++) {
foreach ($cn as $k => $value) {
$temp{$k}{$i} = $value;
$values[] = &$temp{$k}{$i};
}
}
/**
* putting together the "types"-part for the binding of the prepared statement
*/
$types = array(str_repeat('s', count($cn) * $columnsToSearch - 1) . 's');
/**
* merging types and references
*/
$argumentsArray = array_merge($types, $values);
/**
* creating placeholder string for the query
*/
$placeholder = str_repeat('?, ', count($cn) - 1) . '?';
$stmt = $conn->prepare('SELECT CN_no, doc_no FROM mock_data WHERE CN_no IN (' . $placeholder . ') or doc_no IN (' . $placeholder . ')');
/**
* check http://us3.php.net/manual/en/mysqli-stmt.bind-param.php#104073 to read what is happening here
*/
$ref = new ReflectionClass('mysqli_stmt');
$method = $ref->getMethod("bind_param");
$method->invokeArgs($stmt, $argumentsArray);
$stmt->execute();
/**
* fetching the result
*/
$stmt->bind_result($CN_no, $doc_no);
$row_set = array();
while ($row = $stmt->fetch()) {
$row_set[] = array('CN_no' => $CN_no, 'doc_no' => $doc_no);
}
var_dump($row_set);
exit;
I adjusted the comment from http://us3.php.net/manual/en/mysqli-stmt.bind-param.php#104073 so it fits to your scenario.
Btw. With PDO as database-API this would be A LOT easier to write and to read. I might add an example for PDO later.
$mysqli = new mysqli("localhost", "my_user", "my_password", "world");
/* check connection */
if (mysqli_connect_errno()) {
printf("Connect failed: %s\n", mysqli_connect_error());
exit();
}
// search input
$input = '42-7278954,53-1217544,07-2517487';
// create and filter your search array
$iArr = array_filter(array_map('trim', explode(",", $input)), 'strlen');
// create your search string from CN_no array
$CN_no = implode("|",$iArr);
/* create a prepared statement */
if ($stmt = $mysqli->prepare("SELECT * FROM `mock_data` WHERE `CN_no` RLIKE ?")) {
/* bind parameters for search */
$stmt->bind_param("s", $CN_no);
/* execute query */
$stmt->execute();
// get all the rows returned
// IMPORTANT
// Use the below syntax in case you do use native mysqlnd driver.
// If you don't have mysqlnd installed/loaded, you will get an error
// that "mysqli_stmt_get_result()" method is undefined.
$result = $stmt->get_result()->fetch_all();
// in case you don't have native mysqlnd loaded uncomment
// and try the below syntax to get the result
/* $meta = $stmt->result_metadata();
while ($field = $meta->fetch_field()) {
$params[] = &$row[$field->name];
}
call_user_func_array(array($stmt, 'bind_result'), $params);
while ($stmt->fetch()) {
foreach($row as $key => $val) {
$c[$key] = $val;
}
$result[] = $c;
} */
/* *********** */
// print output
print_r($result);
/* close statement */
$stmt->close();
}
/* close connection */
$mysqli->close();
Use FIND_IN_SET() function
Syntax : FIND_IN_SET (YOUR_INPUT_STRING_NAME, COLUMN_NAME);
YOUR_INPUT_STRING_NAME it may be 42-7278954,53-1217544,07-2517487,...
Let me know if it helps you.
Some abstract php code:
$cn = explode(',', $input1);
$incn = str_repeat('?, ', count($cn)-1).'?'
$doc = explode(',', $input2);
$indoc = str_repeat('?, ', count($doc)-1).'?'
$sql = "SELECT ... WHERE cn_no IN ({$incn}) or doc_no IN ({$indoc})";
$result = $db->rows($sql, array_merge($cn, $doc));
$input = '42-7278954,53-1217544,07-2517487';
$inputs = explode(',', $input);
$new_inputs = array_map(function ($item){
$item = trim($item);
return "'" . $item . "'";
}, $inputs);
$string = implode(',', $new_inputs);
// string is '42-7278954','53-1217544','07-2517487'
$sql = "SELECT * FROM mock_data WHERE CN_no IN ({$string})";
$input = '42-7278954,53-1217544,07-2517487';
echo $strNew=str_replace(",","','",$input);
$sql = "SELECT * FROM mock_data WHERE CN_no IN ('".$strNew."')";
$row=mysqli_query($conn,$query)or die("not fire");
try this code
It seems that in-clause in not getting generated correctly by your implementation.
To generate the in-clause correctly, you need to split the user input into array and then join back in-list. For example, if user have given 42-7278954,53-1217544,07-2517487 as input, in-list should be look like:
CN_No in ( '42-7278954' , '53-1217544' , '07-2517487');
Given that Shipment code and Reference code are string value, you have to enclose them in quotes ' '.
Following example may help you to generate in-list expression using implode and explode (php is very little known to me)
$in_list_cns = implode( "' , '", explode(",", $CN_no));
$sql = "SELECT * FROM mock_data WHERE CN_no IN ( '{$in_list_cns}')"
Hope this help.
Also, as mentioned by other too, you may need to sanitize user input to prevent SQL-Injection attacks.
try
$cn = explode(',', $CN_no);
$incn = str_repeat('?, ', count($cn)-1).'?';
$doc = explode(',', $doc_no);
$indoc = str_repeat('?, ', count($doc)-1).'?';
$query = "SELECT * FROM mock_data WHERE CN_no IN ('.$incn.') or doc_no IN ({$indoc})";
$result = $conn->query($query , array_merge($incn, $indoc));

Mysqli connection using object

I just started switching my project form the mysql to mysqli. So what i did is i just create a class db and function connect.how can i create objects and re use it
This is my code:
class db:
class db {
//global var session
private $biz;
//constructor
function db (&$b) {
$this->biz = $b;
$this->connect();
}
function connect() {
mysqli_connect(
$this->biz->_db['host']
, $this->biz->_db['username']
, $this->biz->_db['password']
,$this->biz->_db['database']
) or die("Unable to connect to database");
//$cont=mysqli_select_db($con,$this->biz->_db['database']) or die("Unable to select database: {$this->biz->_db['database']}");
//return $connt;
}
/*
This function will perform a simple query and return all the results
#return object Object containing the rows, num_rows and result
#param string $sql The SQL query
*/
function query($sql) {
$result = new fetchQuery($sql);
return $result;
}
/*
This function will perform a query given the sql
#return object Object containing num rows affected and result
#param string $sql The SQL query
*/
function updateQuery($sql) {
return new updateQuery($sql);
}
/*
This function will perform a query given the sql
#return object Object containing num rows affected and result
#param string $sql The SQL query
*/
function insertQuery($sql) {
return new insertQuery($sql);
}
/*
This function will perform a query given the sql
#return object Object containing num rows affected and result
#param string $sql The SQL query
*/
function deleteQuery($sql) {
return new deleteQuery($sql);
}
/*
This function will automatically decide if data is being updated or inserted
#param array $data The post object, key/value pairs , already validated
#param string $table The table to be updated
*/
/*
This function will perform an update query from the post data that has the correct form - fields matching table field names
#return object Object containing num rows affected and result
#param array $data The post object, key/value pairs , already validated
#param string $table The table to be updated
*/
function autoUpdate($data, $table,$wheredata) {
//id is required, return false if not found
if (!isset($wheredata))
{
return false;
}
$sql = "
UPDATE
`$table`
SET
";
foreach ($data as $key => $value) {
$sql .= " `$key` = '$value' ,";
}
//remove extra comma
$sql = substr($sql, 0, (strlen($sql) - 1));
$sql .= " WHERE 0=0 ";
foreach ($wheredata as $key => $value) {
$sql .= " and `$key` = '$value' ";
}
return new updateQuery($sql);
}
/*
This function will perform an insert query from the matching form table
#return object Object containing num rows affected and result
#param array $data The post object, key/value pairs , already validated
#param string $table The table to be updated
*/
function autoInsert($data, $table, $validate = false)
{
$_fields = array();
$_values = array();
foreach ($data as $field => $value) {
$_fields[] = $field;
$_values[] = $value;
}
$sql = "
INSERT INTO
`$table`
(
";
foreach($_fields as $field) {
$sql .= "`$field` ,";
}
//remove extra comma
$sql = substr($sql, 0, (strlen($sql) - 1));
$sql .= "
) VALUES (
";
foreach($_values as $value) {
$sql .= "'$value' ,";
}
//remove extra comma
$sql = substr($sql, 0, (strlen($sql) - 1));
$sql .= "
)
";
return new insertQuery($sql);
}
/*
This function will perform an auto delete query from the matching form table
#return object Object containing num rows affected and result
#param int $id The related id
#param string $table The table to be affected
*/
function autoDelete($sql) {
return new deleteQuery($sql); }
}
fetch query:
class fetchQuery {
public $result;
public $num = 0;
public $rows = array();
public $error = false;
function fetchQuery($sql) {
$this->result = mysql_query($sql);
if ($this->result) {
$this->num = mysql_num_rows($this->result);
if ($this->num) {
while($row = mysql_fetch_assoc($this->result)) {
$this->rows[] = $row;
}
} else {
$this->result = false;
}
} else {
$this->result = false;
$this->error = mysql_error();
}
}
please give me a solution for create fetch query using connect function.
Make a structure like below and try
Database Configuration : dbconfig.php
<?php
$glob['dbhost'] = 'localhost';
$glob['dbusername'] = 'username';
$glob['dbpassword'] = 'password';
$glob['dbdatabase'] = 'database';
?>
Database Class : database.class.php
<?php
class database {
var $_sql = '';
/** #var Internal variable to hold the connector resource */
var $_resource = '';
/** #var Internal variable to hold the query result*/
var $_result = '';
/**
* Database object constructor
* #param string Database host
* #param string Database user name
* #param string Database user password
* #param string Database name
*/
function database() {
global $glob;
$host = $glob['dbhost'];
$user = $glob['dbusername'];
$pass = $glob['dbpassword'];
$db = $glob['dbdatabase'];
$this->_resource = #mysqli_connect( $host, $user, $pass, $db );
if (mysqli_connect_errno()){
echo "Failed to connect to MySQL: " . mysqli_connect_error();
}
else{
echo "Could not connect to the database!";
exit;
}
}
/**
* Execute the query
* #return mixed A database resource if successful, FALSE if not.
*/
function query($sql) {
$_sql = $sql;
return $_result = #mysqli_query($this->_resource, $_sql);
}
function fetchArray($result) {
return #mysqli_fetch_array($result);
}
function fetchAssoc($result) {
return #mysqli_fetch_assoc($result);
}
}
?>
Project Configuration : configuration.php
<?php
session_start();
ob_start("ob_gzhandler");
#ob_gzhandler();
error_reporting(E_ERROR);
require_once('dbconfig.php');
require_once('database.class.php');
$dbclass = new database();
?>
Any project file code :
<?php
require('configuration.php');
$sqltepm="SELECT * FROM tblname";
$restepm=$dbclass->query($sqltepm);
while($row=$dbclass->fetchArray($restepm)){
extract($row);
}
?>
You can create a basic function like strategy to handle this type of Scenario. Hence if you include the function file alone your DB will be connected on the fly and you can use this using the variable that you connect to the DB.
You can have two files and then you can perform the scenario like this.
db.php
<?php
class DB {
function __construct(){
$username='root';
$password='';
$host='localhost';
$db='store';
$this->connection = mysqli_connect($username,$password,$host,$db);
if(mysqli_connect_errno){
echo "Failed to connect to MYSQL: " . mysqli_connect_error();
}
}
function fetchData(){
$get_query = "SELECT * FROM TABLE"
$result = mysqli_query($this->connection, $get_query);
}
}
?>
And you can call the function in the page as follows.
Usage of the class that we have created
listpage.php
<?php
include('db.php'); // Include the DB file over this line
$conn= new DB(); // Initialize the class that we have created
$conn->fetchData();// This query will select all the data and return it.
?>
I hope so my explanation over the code are clear and you can understand it well and continue your project codes on the fly.
Happy Coding :)

PHP file cannot enter some part of code

Here is a function in my php file, which is used to serve the request of my android app.
function checkin($DB, $TechID, $ClientID, $SiteID){
$dbConnection = mysql_connect($DB['server'], $DB['loginName'], $DB['password']);
if(!$dbConnection){
die('Error! ' . mysql_error());
}
mysql_select_db($DB['database'], $dbConnection);
$file2 = "C:/wamp/www/file2.txt";
$data2 = "ClientID:".$ClientID." TechID:".$TechID." SiteID:".$SiteID;
file_put_contents($file2, $data2);
$result1 = mysql_query("SELECT COUNT(*) FROM Log") or die('Error! ' . mysql_error());
$query = "SELECT `Type` FROM `Log` WHERE `TechID` = '".$TechID."' ORDER BY LogTime DESC LIMIT 1";
$file5 = "C:/wamp/www/file5.txt";
file_put_contents($file5, $query);
$result2 = mysql_query($query) or die('Error! ' . mysql_error());
while($row1 = mysql_fetch_array($result1)){
$count = $row1['COUNT(*)'];
$file3 = "C:/wamp/www/file3.txt";
$data3 = "ClientID:".$ClientID." TechID:".$TechID." SiteID:".$SiteID." Count:".$count;
file_put_contents($file3, $data3);
while($row2 = mysql_fetch_array($result2)){
$file4 = "C:/wamp/www/file4.txt";
$data3 = "ClientID:".$ClientID." TechID:".$TechID." SiteID:".$SiteID." Count:".$count;
file_put_contents($file4, $data3);
/*if($row2['Type']!="Checkin"){
$count = $count+1;
$Time = date('Y/m/d H:i');
mysql_query("INSERT INTO Log (LogID, TechID, ClientID, SiteID, LogTime, Type)
VALUES (".$count.", ".$TechID.", ".$ClientID.", ".$SiteID.", ".$Time.", Checkin)");
}else{
$query2 = "SELECT TechEmail FROM Tech WHERE TechID=".$TechID;
$result3 = mysql_query($query2) or die('Error! ' . mysql_error());
$subject = "Please check out";
$message = "You have forgot to logout from the last site, please check out manually";
$from = "devadmin#uniserveit.com";
$header = "Form:".$from;
while($row3 = mysql_fetch_array($result3)){
mail($row3['TechEmail'], $subject, $message, $header);
}
}*/
}
}
}
you can see that I have hidden some codes, since I am debugging it, I create some files just to see which part of codes cannot be executed. I discover that the program cannot enter the region where file4 should be created. I have seeked out probably the problem is coming from the $query, when it executes, somethimes the mysql will response "unknown table status: TABLE_TYPE", which I cannot understand why.
As written in the comment above, you should divide and conquer to make your life easier (especially as you write the code while you play around with it in that large function). That does work as easy as:
function file_put($number, $data)
{
$path = sprintf("C:/temp/wamp/www/file%d.txt", $number);
file_put_contents($path, $data);
}
for example that is just replacing the many duplicate lines where you just need a (numbered) file you put some string in.
But you can also do this with more complex stuff, like the database operation. You probably want to move the error handling out of your sight as well as taking care to connect to the database when needed and a more flexible way to fetch the data. That can be done by moving the (softly deprecated) mysql_* functions into one or two classes of its' own, so that it gets out of your sight. That will make it's usage much more easier (which I show first):
// Create your database object to use it later on:
$config = array(
'server' => 'localhost',
'name' => 'root',
'password' => '',
'db' => 'test',
);
$db = new MySql($config);
I called the database class MySql as it represents the mysql connection and it works with the old mysql extension. You only need to pass that database object then into the function in your question. Combined with the file_put function, it would look like this:
function checkin(MySql $DB, $TechID, $ClientID, $SiteID)
{
$query = sprintf("SELECT `Type` FROM `Log` WHERE `TechID` = '%d' ORDER BY LogTime DESC LIMIT 1", $TechID);
file_put(5, $query);
$result1 = $DB->query("SELECT COUNT(*) FROM Log");
$result2 = $DB->query($query);
foreach ($result1 as $row1) {
list($count) = $row1;
$data = "ClientID:$ClientID TechID:$TechID SiteID:$SiteID Count:$count"
file_put(3, $data);
foreach ($result2 as $row2) {
file_put(4, $data);
}
}
}
Still the checkin function is close to being large (12 lines of code already), but is much shorter than your first version because it delegates the work for writing the files and accessing the database. I hope this demonstration is useful. What follows is the full code example:
/**
* MySql Exception
*/
class MySqlException extends RuntimeException
{
}
/**
* MySql Database Class
*/
class MySql
{
private $server;
private $name;
private $password;
private $db;
private $connection;
public function __construct(array $config)
{
$this->server = $config['server'];
$this->name = $config['name'];
$this->password = $config['password'];
$this->db = $config['db'];
}
private function connect($server, $name, $password)
{
$this->connection = mysql_connect($server, $name, $password);
if (!$this->connection) {
$this->error("Unable to connect to '%s' as user '%s'", $server, $name);
}
}
private function select($db)
{
if (!mysql_select_db($db, $this->connection)) {
$this->error("Unable to select database '%s'", $db);
}
}
private function close()
{
$this->connection && mysql_close($this->connection);
}
private function connectSelect()
{
$this->connect($this->server, $this->name, $this->password);
$this->select($this->db);
}
/**
* #param $query
* #return MySqlResult
*/
public function query($query)
{
$this->connection || $this->connectSelect();
$result = mysql_query($query, $this->connection);
if (!$result) {
$this->error("Unable to execute query '%s'", $query);
}
return new MySqlResult($result);
}
/**
* #param string $format
* #param ...
* #throws MySqlException
*/
private function error($format)
{
$args = func_get_args();
array_shift($args);
$format .= ': %s';
$args[] = $this->connection ? mysql_error($this->connection) : mysql_error();
throw new MySqlException(vsprintf($format, $args));
}
public function __destruct()
{
$this->close();
}
}
/**
* MySql Result Set - Array Based
*/
class MySqlResult implements Iterator, Countable
{
private $result;
private $index = 0;
private $current;
public function __construct($result)
{
$this->result = $result;
}
public function fetch($result_type = MYSQL_BOTH)
{
$this->current = mysql_fetch_array($this->result, $result_type);
return $this->current;
}
/**
* Return the current element
* #link http://php.net/manual/en/iterator.current.php
* #return array
*/
public function current()
{
return $this->current;
}
public function next()
{
$this->current && $this->fetch();
}
/**
* Return the key of the current element
* #link http://php.net/manual/en/iterator.key.php
* #return mixed scalar on success, or null on failure.
*/
public function key()
{
return $this->current ? $this->index : null;
}
/**
* Checks if current position is valid
* #link http://php.net/manual/en/iterator.valid.php
* #return boolean The return value will be casted to boolean and then evaluated.
* Returns true on success or false on failure.
*/
public function valid()
{
return (bool)$this->current;
}
/**
* Rewind the Iterator to the first element
* #link http://php.net/manual/en/iterator.rewind.php
* #return void Any returned value is ignored.
*/
public function rewind()
{
$this->fetch();
}
/**
* Count of rows.
*
* #link http://php.net/manual/en/countable.count.php
* #return int The count of rows as an integer.
*/
public function count()
{
return mysql_num_rows($this->result);
}
}
// Create your database object to use it later on:
$config = array(
'server' => 'localhost',
'name' => 'root',
'password' => '',
'db' => 'test',
);
$db = new MySql($config);
function file_put($number, $data)
{
$path = sprintf("C:/temp/wamp/www/file%d.txt", $number);
file_put_contents($path, $data);
}
function checkin(MySql $DB, $TechID, $ClientID, $SiteID)
{
$query = sprintf("SELECT `Type` FROM `Log` WHERE `TechID` = '%d' ORDER BY LogTime DESC LIMIT 1", $TechID);
file_put(5, $query);
$result1 = $DB->query("SELECT COUNT(*) FROM Log");
$result2 = $DB->query($query);
foreach ($result1 as $row1) {
list($count) = $row1;
$data = "ClientID:$ClientID TechID:$TechID SiteID:$SiteID Count:$count";
file_put(3, $data);
foreach ($result2 as $row2) {
file_put(4, $data);
}
}
}
checkin($db, 1, 2, 3, 4);
The simple answer should be:
mysql_query("INSERT INTO Log (LogID, TechID, ClientID, SiteID, LogTime, Type)
VALUES (".$count.", ".$TechID.", ".$ClientID.", ".$SiteID.", ".$Time.", Checkin)");
remove the quotes from the $vars. or single quote them. Also remove dots. Like:
mysql_query("INSERT INTO Log (LogID, TechID, ClientID, SiteID, LogTime, Type)
VALUES ('$count', '$TechID', '$ClientID', '$SiteID', '$Time', Checkin)");

INSERT into table if not exists and return result

I have a 'users' SQL table structure like this (the ID is randomly generated for certain reasons, it is not auto-incremented):
ID name deleted lastActive
3242 Joe 0 20-6-2012 23:14
2234 Dave 0 20-6-2012 23:13
2342 Simon 1 20-6-2012 23:02
9432 Joe 1 20-6-2012 22:58
In one query (to avoid concurrent queries adding the same name twice), I need to add a new user to the table IF there is not already a record with that name AND deleted = 0. I then need to know the result of the query (if the user was added) so that I can report back saying if the user was added or not. Is this possible using PHP?
I could do this (but as a prepared statement, of course!):
INSERT INTO users (ID, name) VALUES ($id, $name)
WHERE NOT EXISTS (SELECT 1 FROM users WHERE name = $name AND deleted = 0)
But how can I know if the user was added or not?
If you're using mysqli, you can use the mysqli_stmt_affected_rows() function to determine how many rows were inserted.
Similarly, you can use the PDOStatement::rowCount() method to determine how many rows were inserted for PDO.
Both functions will tell you the number of rows that were inserted as a result of the query.
You can know the number of rows affected by your query in mysql using
mysql_affected_rows.
If you are using PDO,
PDOStatement::rowCount.
If MYSQLi, mysqli_affected_rows
Here's a nice insertion method that returns the ID:
/**
* Execute an insert or update in the database.
* #param $table - Table name.
* #param $key_name - Primary key to update. NULL to a insert
* #param $data - Column data array
* #param $call_on_error function name that should called in case of an exception during the
* execution of the statment, the function is expected to take one argument, the exception object
* #return mixed An array containing the key inserted or updated on success, false on failure.
*/
function INSERT($table, $key_name, &$data, $call_on_error = null) {
list($min_cols, $prefix, $suffix, $key_value) = isset($data[$key_name]) ?
array(2, 'UPDATE', " WHERE `$key_name`=:$key_name", $data[$key_name]) :
array(1, 'INSERT', '', null);
if (count($data) < $min_cols) {
return false;
}
$set_clause = '';
foreach ($data as $k => $v) {
if ($k !== $key_name) {
if (($flag_name = strstr($k, "__", true))) {
if (strcmp($k, "{$flag_name}__mask") && isset($data["{$flag_name}__value"]))
$set_clause .= ",`$flag_name`=:{$flag_name}__value | (`$flag_name` & :{$flag_name}__mask)";
} else {
$set_clause .= ",`$k`=:$k";
}
}
}
global $dbo_error_duplicated;
$dbo_error_duplicated = false;
$dbh = DBH();
try {
$sth = $dbh->prepare("$prefix $table SET " . substr($set_clause, 1) . $suffix);
$res = $sth->execute($data);
} catch (PDOException $e) {
$dbo_error_duplicated = $sth->errorCode() === '23000';
echo $e;
if(isset($call_on_error)){
call_user_func($call_on_error, $e);
}
$res = false;
}
if ($res) {
if ($key_value === null && is_numeric($id = $dbh->lastInsertId())) {
$key_value = $id;
}
$res = $key_value === null ? false : array($key_name => $key_value);
}
return $res;
}
And… the DBH config:
/**
* Get Data Base Handler.
* Manual # http://www.php.net/manual/en/pdostatement.fetch.php
* More info # http://net.tutsplus.com/tutorials/php/why-you-should-be-using-phps-pdo-for-database-access/
*
* #return PDO Data Base Handler
*/
function DBH() {
global $DBH;
global $db_config;
if ($DBH === null) {
// throws PDOException on connection error
$DBH = new PDO("mysql:host=$db_config[host];dbname=$db_config[dbname]", $db_config['user'], $db_config['pass'],
array(PDO::ATTR_PERSISTENT => $db_config['persistent'], PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES '$db_config[encoding]'"));
// ask PDO to throw exceptions for any error
$DBH->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
return $DBH;
}
That uses this .ini file:
[db_config]
persistent = true
host = "localhost"
user = "root"
pass = ""
dbname = "theDbName"
# host = "db.production_host.com"
# user = "prod_root"
# pass = "big4nd5tr0ngp4s5word"
# dbname = "theDbName"
encoding = "UTF8"

Categories