I'm in the middle of creating a custom Database Class to suit the requirements of the company i'm developing for. I currently have this:
class DBC {
protected $Link;
protected $Results;
public function __construct($Host = null,$User = null ,$Pass = null,$Database = null){
if ($Host === null OR $User === null OR $Pass === null OR $Database === null){
trigger_error("Incorrect Parameters Passed In The Database Link", E_USER_WARNING);
}
if (is_string($Host) AND is_string($User) AND is_string($Pass) AND is_string($Database)){
$this->Link = new mysqli($Host,$User,$Pass,$Database);
}else{
trigger_error("Expecting String(s), Array passed in one or more connection parameters",E_USER_ERROR);
}
}
public function Query ($Query,$Params){
$Query = $this->Link->prepare($Query);
$Query->bind_param();
}
}
Now.. I'm having a problem with how to sucessfully bind the parameters to the prepared statement.. For example, A Query will be submitted with this:
$DB = new DBC("Host","User","pass","database");
$DB->Query("SELECT * FROM Test WHERE Col=?",array("SearchCriteria"));
I've hit a block with figuring out how to bind_param and bind_result based on the results. A more clear insight is the normal procedure of MySQLi:
$SearchCriteria = "String";
$Query = $Database->prepare("SELECT * FROM Test WHERE Col=?");
$Query->bind_param('s',$SearchCriteria);
$Query->execute();
$Query->bind_results(/* Variables to match the column set */);
$Query->fetch();
$Query->close();
How can I bind the results and params to the prepared statement?
Below are copies of functions I use in a class that extends the mysqli class which do what you are asking.
function bind_placeholder_vars(&$stmt,$params,$debug=0) {
// Credit to: Dave Morgan
// Code ripped from: http://www.devmorgan.com/blog/2009/03/27/dydl-part-3-dynamic-binding-with-mysqli-php/
if ($params != null) {
$types = ''; //initial sting with types
foreach ($params as $param) { //for each element, determine type and add
if (is_int($param)) {
$types .= 'i'; //integer
} elseif (is_float($param)) {
$types .= 'd'; //double
} elseif (is_string($param)) {
$types .= 's'; //string
} else {
$types .= 'b'; //blob and unknown
}
}
$bind_names = array();
$bind_names[] = $types; //first param needed is the type string
// eg: 'issss'
for ($i=0; $i<count($params);$i++) { //go through incoming params and added em to array
$bind_name = 'bind' . $i; //give them an arbitrary name
$$bind_name = $params[$i]; //add the parameter to the variable variable
$bind_names[] = &$$bind_name; //now associate the variable as an element in an array
}
if ($debug) {
echo "\$bind_names:<br />\n";
var_dump($bind_names);
echo "<br />\n";
}
//error_log("better_mysqli has params ".print_r($bind_names, 1));
//call the function bind_param with dynamic params
call_user_func_array(array($stmt,'bind_param'),$bind_names);
return true;
}else{
return false;
}
}
function bind_result_array($stmt, &$row) {
// Credit to: Dave Morgan
// Code ripped from: http://www.devmorgan.com/blog/2009/03/27/dydl-part-3-dynamic-binding-with-mysqli-php/
$meta = $stmt->result_metadata();
while ($field = $meta->fetch_field()) {
$params[] = &$row[$field->name];
}
call_user_func_array(array($stmt, 'bind_result'), $params);
return true;
}
However, it sounds like you are doing something similar to what I have already done and have been using in many projects for a while now. Copy the contents of this pastebin (better_mysqli.php) into a new file and name it 'better_mysqli.php'
Then use it in your php program like so:
// include the class
include_once('better_mysqli.php');
// instantiate the object and open the database connection
$mysqli = new better_mysqli('yourserver.com', 'username', 'password', 'db_name');
if (mysqli_connect_errno()) {
die("Can't connect to MySQL Server. Errorcode: %s\n", mysqli_connect_error()), 'error');
}
// do a select query
$sth = $mysqli->select('select somecol, othercol from sometable where col1=? and col2=?', $row, array('col1_placeholder_value', 'col2_placeholder_value'));
while ($sth->fetch()) {
echo "somecol: ". $row['somecol'] ."<br />\n";
echo "othercol: ". $row['othercol'] ."<br />\n";
}
// the nice thing about this class is that the statement is only prepared once so if you use it again the already prepared statement is automatically used:
// do another select query with different placeholder values
$sth = $mysqli->select('select somecol, othercol from sometable where col1=? and col2=?', $row, array('other_col1_placeholder_value', 'other_col2_placeholder_value'));
while ($sth->fetch()) {
echo "somecol: ". $row['somecol'] ."<br />\n";
echo "othercol: ". $row['othercol'] ."<br />\n";
}
// the class supports the following methods: select, update, insert, and delete
// example delete:
$mysqli->delete('delete from sometable where col1=?', array('placeholder_val1'));
Related
I've seen this question before but all the solutions do not work for me. The main solution is to store the result which I already do. Most people say I am running 2 simultaneous queries which I don't understand. I may have used this function below more than once but I always free and close the statement so not sure why this is happening.
You can most likely ignore the top half of the function which just generates a string to represent the types. This is the full DB class I made:
(I edited it since I originally posted it and replaced self::$connection with self::$db)
class DB {
private static $dbhost = "localhost";
private static $dbuser = "some_user"; // placeholder
private static $dbpass = "some_assword"; // placeholder
private static $dbname = "database_name"; // placeholder
private static $db;
public static function connect() {
self::$db = new mysqli(self::$dbhost, self::$dbuser, self::$dbpass, self::$dbname);
if (self::$db->connect_errno) {
die("Database mysqli failed: " .
self::$db->connect_error . " (" .
self::$db->connect_errno . ")"
);
}
}
// IGNORE THIS! It just formats the parameters for the
// call_user_func_array function to work correctly.
private static function getBindings(&$params) {
$types = "";
$bindings = array();
foreach ($params as $value) {
switch (gettype($value)) {
case "integer":
$types .= "i";
break;
case "double":
$types .= "d";
break;
case "string":
$types .= "s";
break;
default:
$types .= "s";
break;
}
}
foreach($params as $key => $value)
$bindings[$key] = &$params[$key]; // assign to references (because bind_param requires references)
// to add a string of variable types to the start of the $bindings array (such as 'sss')
array_unshift($bindings, $types);
return $bindings;
}
public static function query($query, $params) {
if (!isset(self::$db)) { self::connect(); }
if (empty($params)) {
// prepared statement not needed
return self::$db->query($query);
}
$successful = false;
$bindings = self::getBindings($params);
// MySQL prepared statement execution:
$statement = self::$db->prepare($query);
call_user_func_array(array($statement, 'bind_param'), $bindings);
$statement->execute();
$statement->store_result();
if ($statement->num_rows > 0) {
// for select queries
$successful = $statement->get_result(); // does not work! (returns boolean)
echo self::$db->errno; // 2014
echo "<br />";
echo self::$db->error; // Commands out of sync; you can't run this command now
echo "<br />";
// this method works fine (but I need to return the result set!):
/*$name = false; $link = false;
$statement->bind_result($name, $link);
while ($statement->fetch()) {
echo 'name: '.$name.'<br>';
echo 'link: '.$link.'<br>';
}*/
} else if ($statement->affected_rows > 0) {
// for insert queries
$successful = true;
}
$statement->free_result();
$statement->close();
return $successful;
}
public static function close() {
if (isset(self::$db)) self::$db->close();
}
}
EDIT: This is what one of my requests looks like (I have queried 2 requests on this same page using my DB class and DB::query(...) function):
$result = DB::query("SELECT * FROM table_name;");
if ($result) {
while ($row = $result->fetch_assoc()) {
// do stuff
}
$result->close();
}
For the love of what is sane, change your driver to PDO and make all this code into
public static function query($query, $params = NULL)
{
if (!$params) {
return self::$connection->query($query);
}
$statement = self::$connection->prepare($query);
$statement->execute($params);
return $statement;
}
to be used like this
$result = DB::query("SELECT * FROM table_name");
foreach($result as $row) {
// do stuff
}
The MySQL documentation regarding Commands out of sync suggest two possibilities:
You have used a result and are trying to execute a new query before freeing the last result.
You are running two queries (not necessarily simultaneously) without using or storing the result between each one.
The logic in your code shows the only situation where you are not freeing results is when no prepared statement is required. The answer to your problem may be to deal with this particular result and free or store it.
I can see from your code self::$connection is a static reference, therefore it could be possible that any query executed in that scope is using the same connection. It is hard to tell without being able to see your full class.
I would like to write a database connection class and I dont understand how I have to write the select method with bind_param-s. Here is the full code. And here the part of the code where I need the help:
public function select($sql){
$db = $this->connect(); //This methos connect to the DB
$stmt = $db->prepare($sql);
if($stmt === false){ //If the prepare faild
trigger_error("Wrong SQL", E_USER_ERROR);
}
$error = $stmt->bind_param("i", $id);
if($error){
return "Error: ".$stmt->error, $stmt->errno;
}
$err = $stmt->execute();
if($error){
return "Error: ".$stmt->error, $stmt->errno;
}
$result = $stmt->bind_result($id);
$stmt->close();
$dbConnection->closeConnection($db);
return $result;
}
I need to got it parameters or how can I slove it?
You need to pass your values into this function too. And eventually bind them into prepared statement.
Optionally you can pass string with types, but by default all "s" will do.
Also remember that you should connect only ONCE per script execution. and then use one single connection all the way throughout your code.
And get rid of all these error checks. Set mysqli in exception mode instead.
public function q($sql, $values = array(), $types = NULL)
{
$stm = $this->mysql->prepare($sql);
if (!$types)
{
$types = str_repeat("s", count($values));
}
if (strnatcmp(phpversion(),'5.3') >= 0)
{
$bind = array();
foreach($values as $key => $val)
{
$bind[$key] = &$values[$key];
}
} else {
$bind = $values;
}
array_unshift($bind, $types);
call_user_func_array(array($stm, 'bind_param'), $bind);
$stm->execute();
return $stm->get_result();
}
so it can be used like this
$res = $db->q("SELECT name FROM users WHERE id=?", [$id]);
or
$res = $db->q("SELECT name FROM users WHERE id=?", [$id], "i");
your other functions have to be changed as well.
class DB{
public $con;
function __construct()
{
$this->con = new mysqli("localhost", "root", "", "proba_fferenc");
}
public function select(...)
{
// as shown above
}
}
I have found a question recently (stackoverflow.com/questions/30556100/how-to-uncheck-all-checkboxes-if-one-checkbox-checked). Now, I would like to create a template for the HTML generation, as it seems a good idea to avoid code duplication. This is how my html is generated:
// ---------------------------------------
// --- Populate "Body Type" dropdown list:
// ---------------------------------------
// for populate body types:
$bodytype = array();
// Select exsiting body type from database:
$prep_stmt = " SELECT id, name FROM body_type";
$stmt = $mysqli->prepare($prep_stmt);
if ($stmt) {
// Bind "$userId" to parameter.
//$stmt->bind_param('i', $userId);
// Execute the prepared query.
$stmt->execute();
$stmt->store_result();
// get variables from result.
$stmt->bind_result($id, $name);
// Fetch all the records:
while ($stmt->fetch()) {
// XSS protection as we might print these values
$name = preg_replace("/[^a-zA-Z0-9_\-]+/", "", $name);
$id = filter_var($id,FILTER_SANITIZE_NUMBER_INT);
$type = "<label class='checkbox-inline'>\n";
if ($name == 'Any'){
$type .= "<input type='checkbox' id='bodyTypeAny' name='bodyTypeCheck[]' value='{$id}'> {$name}\n";
} else {
$type .= "<input type='checkbox' id='' name='bodyTypeCheck[]' value='{$id}'> {$name}\n";
}
$type .= "</label>\n";
//Add body types to array
$bodytype[] = $type;
}
} else {
echo 'Database error';
}
// Close the statement:
$stmt->close();
unset($stmt);
All PHP Code for checkbox groups : http://pastebin.com/NXN6ZzcK
How can I implement a function which would generate a checkbox group, so I would simply call it as a function.
Hope somebody may help me out.
Thank you.
Before you follow the steps below, save your working version somewhere to make sure you can roll back if you need to.
You could create a class for templates, like this:
class TemplateEngine {
public static function getCheckboxTemplate($input, $templateName) {
$template = "";
foreach ($input as $element) {
$template .= "<label class='checkbox-inline'>\n";
$template .= "<input type='checkbox' ".(($element["name"] === "Any") ? ("id='".$templateName."Any'") : (""))." name='".$templateName."Check' value='".$element["id"]."'>".$element["name"]."\n";
$template .= "</label>\n";
}
return $template;
}
}
Make sure you create that class in a separate file where you will implement later templates as well and you require/include that file. And then you can use it like this:
// ---------------------------------------
// --- Populate "Body Type" dropdown list:
// ---------------------------------------
$template = "";
// Select exsiting body type from database:
$prep_stmt = " SELECT id, name FROM body_type";
$stmt = $mysqli->prepare($prep_stmt);
if ($stmt) {
// Bind "$userId" to parameter.
//$stmt->bind_param('i', $userId);
// Execute the prepared query.
$stmt->execute();
$stmt->store_result();
// get variables from result.
$stmt->bind_result($id, $name);
$input = array();
// Fetch all the records:
while ($stmt->fetch()) {
$input[]= array("name" => preg_replace("/[^a-zA-Z0-9_\-]+/", "", $name), "id" => filter_var($id,FILTER_SANITIZE_NUMBER_INT));
}
$template = TemplateEngine::getCheckboxTemplate($input, "bodyType");
} else {
echo 'Database error';
}
// Close the statement:
$stmt->close();
unset($stmt);
And instead of using a $bodytype array, you can simply:
echo $template;
The code is not tested. If there are typos, let me know :)
EDIT: Further templating:
class TemplateEngine {
public static function getCheckboxTemplate($input, $templateName) {
$template = "";
foreach ($input as $element) {
$template .= "<label class='checkbox-inline'>\n";
$template .= "<input type='checkbox' ".(($element["name"] === "Any") ? ("id='".$templateName."Any'") : (""))." name='".$templateName."Check' value='".$element["id"]."'>".$element["name"]."\n";
$template .= "</label>\n";
}
return $template;
}
public static function getCheckboxDatabaseWrapperTemplate($tableName, $templateName, $mysqli) {
$template = "";
// Select exsiting body type from database:
$prep_stmt = " SELECT id, name FROM ".$tableName;
$stmt = $mysqli->prepare($prep_stmt);
if ($stmt) {
// Bind "$userId" to parameter.
//$stmt->bind_param('i', $userId);
// Execute the prepared query.
$stmt->execute();
$stmt->store_result();
// get variables from result.
$stmt->bind_result($id, $name);
$input = array();
// Fetch all the records:
while ($stmt->fetch()) {
$input[]= array("name" => preg_replace("/[^a-zA-Z0-9_\-]+/", "", $name), "id" => filter_var($id,FILTER_SANITIZE_NUMBER_INT));
}
$template = TemplateEngine::getCheckboxTemplate($input, $templateName);
} else {
echo 'Database error';
}
// Close the statement:
$stmt->close();
unset($stmt);
return $template;
}
}
I'm having trouble finding good documentation on pdo update prepared statements and even more trouble finding documentation on dynamically updating the database with pdo prepared statements. I've gotten my dynamic insert to work but am having trouble with the update. The error I'm getting is:
Warning: PDOStatement::execute() [pdostatement.execute]:
SQLSTATE[HY093]: Invalid parameter number: parameter was not defined
in
/Users/scottmcpherson/Sites/phpsites/projectx/application/models/db.php
on line 91 error
Here is the class I created minus a couple of methods that are irrelevant to this problem:
<?php
require_once("../config/main.php");
class Database{
protected static $dbFields = array('username', 'password');
public $db;
public $tableName = 'users';
public $id = 1;
public $username = "Jonny";
public $password = "Appleseed";
public function __construct() {
$this->connect();
}
public function connect(){
try {
$this->db = new PDO("mysql:host=".DB_SERVER."; dbname=".DB_NAME, DB_USER, DB_PASS);
} catch (PDOException $e) {
echo 'Connection failed: ' . $e->getMessage();
}
}
public function properties() {
$properties = array();
foreach (self::$dbFields as $field) {
if (isset($this->field) || property_exists($this, $field)) {
$properties[$field] = $this->$field;
}
}
return $properties;
}
public function propertyValues() {
$property = $this->properties();
$propertyValues = array();
foreach ($property as $key => $value) {
$propertyValues = ":" . implode(", :", array_keys($property));
}
return $propertyValues;
}
public function polishedVals(){
// The end result of this function is:
// username=:username, password=:password
$props = $this->properties();
$phaseOne = array();
foreach ($props as $key => $value) {
$phaseOne[$key] = ":".$key;
}
$phaseTwo = array();
foreach ($phaseOne as $key => $value) {
$phaseTwo[] = "{$key}={$value}";
}
$polishedVals = implode(", ", $phaseTwo);
return $polishedVals;
}
public function update(){
$stmt = "UPDATE ". $this->tableName." SET ";
$stmt .= $this->polishedVals();
$stmt .= "WHERE id=" . $this->id;
$stmt = $this->db->prepare($stmt);
if($stmt->execute($this->properties())) {
echo "yes";
} else {
echo "error ";
}
}
}
$database = new Database();
echo$database->update();
?>
With all the variables replaced with the actual values, the result I'm going for with the update() method would look like this:
public function update(){
$stmt = "UPDATE users SET ";
$stmt .= "username=:username, password=:password ";
$stmt .= "WHERE id=1";
$stmt = $this->db->prepare($stmt);
if($stmt->execute($this->properties())) {
echo "yes";
} else {
echo "error ";
}
}
In addition to spotting this problem, please let me know if you see any other issues with this code. I'm still kind of new to PHP.
Edit: I've now created a new method that adds a : to the beginning of each key in the properties array:
public function colProperties(){
$properties = $this->properties();
$withCols = array();
foreach($properties as $key => $value){
$withCols[":".$key] = $value;
}
return $withCols;
}
So my update() method now looks like:
public function update(){
$stmt = "UPDATE ". $this->tableName." SET ";
$stmt .= $this->polishedVals();
$stmt .= "WHERE id=" . $this->id;
$stmt = $this->db->prepare($stmt);
if($stmt->execute($this->colProperties())) {
echo "yes";
} else {
echo "error ";
}
}
and if I var_dump($this->colProperties) I get:
array(2) { [":username"]=> string(5) "Jonny" [":password"]=> string(9) "Appleseed" }
And still getting the same error.
I don't think that passing parameters to an UPDATE query requires a different method than a SELECT one. The information in the PDOStatement->execute() manual page should apply:
<?php
/* Execute a prepared statement by passing an array of insert values */
$calories = 150;
$colour = 'red';
$sth = $dbh->prepare('SELECT name, colour, calories
FROM fruit
WHERE calories < :calories AND colour = :colour');
$sth->execute(array(':calories' => $calories, ':colour' => $colour));
?>
You are using named parameters so execute() expects an associative array. Use var_dump() to display $this->properties() right before execute():
var_dump($this->properties())
Make sure you keys match exactly.
The error is that in between
$stmt .= $this->polishedVals();
$stmt .= "WHERE id=" . $this->id;
There needs to be a space in between the WHERE clause as the polishedVals() method does not add a space after the implode. So, you'll have something like
UPDATE User SET city=:city, location=:locationWHERE User.id=28
Which causes the error.
Simple bug.
I have been using the old mysql api in PHP for a long time and want to start using mysqli for both speed and security with a new project I'm working on. I've looked through the manual and read several tutorials, but I'm finding a lot of conflicting and somewhat confusing information on how to do prepared statements in mysql.
Is there anything in this code that doesn't need to be there, and is there anything that is missing? Also, is this the easiest way to do something simple like this (seems somewhat involved for such a simple task)?
Procedural:
// build prepared statement
$query = mysqli_prepare($link, "SELECT email FROM users WHERE id = ?");
// bind parameters to statement
mysqli_stmt_bind_param($query, 's', $_GET['id']);
// execute statement
mysqli_stmt_execute($query);
// bind the variables to the result
mysqli_stmt_bind_result($query, $email);
// print the results
while (mysqli_stmt_fetch($query)) {
echo $email;
}
// close the statement
mysqli_stmt_close($query);
// close connection
mysqli_close($link);
Object-Oriented:
// build prepared statement
$query = $link->prepare("SELECT email FROM users WHERE id = ?");
// bind parameters to statement
$query->bind_param('s', $_GET['id']);
// execute statement
$query->execute();
// bind the variables to the result
$query->bind_result($email);
// print the results
while ($query->fetch()) {
echo $email;
}
// close the statement
$query->close();
// close connection
$link->close();
Here's the guts of a semi-self-explanatory class that encapsulates mysqli, including prepared statements, which are quite tricky. It's pretty well tested - I've been using it for a year now without change.
It only implements prepared statements to Execute SQL commands because they change data and often require nasty encoding tricks otherwise. If you want SELECTs, it's left as an exercise for the reader - it's easier. :)
<?php
class Db
{
var $_mysqli;
var $_result;
var $_error_msg;
public function __construct($server, $user, $password, $name)
{
$this->_mysqli = new mysqli("p:".$server, $user,
$password, $name);
if($this->_mysqli->connect_errno)
{
$this->_error_msg = $this->_mysqli->connect_error;
}
}
public function __destruct()
{
}
private function sql_select($sql)
{
$this->_mysqli->query("SET NAMES 'utf8'"); // a little help for UTF8 io
$this->_result = $this->_mysqli->query($sql);
}
private function sql_close()
{
$this->_mysqli->close();
}
public function ErrorMessage()
{
return $this->_error_msg;
}
public function SqlRows($sql)
{
$rows = array();
$result = $this->sql_select($sql);
if($this->IsError())
{
return $rows;
}
while($row = $result->fetch_array())
{
$rows[] = $row;
}
$result->free();
return $rows;
}
public function SqlObjects($sql)
{
$objects = array();
$result = $this->sql_select($sql);
while($object = $this->_result->fetch_object())
{
$objects[] = $object;
}
$result->free();
return $objects;
}
public function SqlOneObject($sql)
{
$result = $this->sql_select($sql);
$obj = $result->fetch_object();
$result->free();
return $obj;
}
public function SqlOneRow($sql)
{
$result = $this->sql_select($sql);
if(! is_object($result))
return null;
if($result->num_rows > 0)
$row = $result->fetch_array();
else
$row = null;
$result->free();
return $row;
}
public function SqlOneValue($sql)
{
$result = $this->sql_select($sql);
if(!empty($result))
{
$row = $result->fetch_array();
}
$result->free();
return empty($row) ? null : $row[0] ;
}
// returns number of affected rows
public function SqlExecute($sql)
{
$this->_result = $this->_mysqli->query($sql);
return $this->affected_rows();
}
private function affected_rows()
{
return $this->_mysqli->affected_rows;
}
private function IsError()
{
if(empty($this->_mysqli))
return false;
return !empty($this->_mysqli->error);
}
// arguments are sql and an array of
// argument references (not values).
public function SqlExecutePS($sql, $args)
{
$stmt = $this->_mysqli->prepare($sql);
// make the type-string
$typestr = make_typestring($args);
$params = array($typestr);
$params = array_merge($params, $args);
call_user_func_array(array($stmt, 'bind_param'), $params);
$stmt->execute();
$ret = $this->affected_rows();
$stmt->close();
return $ret;
}
public function SqlExists($sql)
{
$result = $this->SqlOneRow($sql);
return !empty($result[0]);
}
function make_typestring($args)
{
assert(is_array($args));
$ret = "";
foreach($args as $arg)
{
switch(gettype($arg))
{
case "boolean":
case "integer":
$ret .= "i";
break;
case "double":
$ret .= "d";
break;
case "string":
$ret .= "s";
break;
case "array":
case "object":
case "resource":
case "NULL":
default:
// call it a blob and hope
// you know what you're doing.
$ret .= "b";
break;
}
}
return $ret;
}
}
?>