I was wondering how I could make a general function for SELECT mysql queries from my SelectQuery object in PHP. SelectQuery extends Query, which means it inherits the database connection, a realescape method (which is mysqli_real_escape_string()), and a query method which executes the query. Besides that, it also gets a protected variable called _sql, which is the SQL the query() method passes to the database. And it also gets a protected variable called _table, which contains the (escaped) name of the table it's working on.
My code:
public function select($columns = array('*'), $known = null, $limit = null, $offset = null, $orderby = null, $asc = true) {
if (!is_array($columns)) {
new Error('Parameter is not an array.');
return;
}
$select = '';
foreach($columns as $column) {
$select .= (($select != '')?', ':'') . '`' . $this->realescape($column) . '`';
}
$conditions = '';
if (is_array($known)) {
foreach($known as $column => $value) {
$conditions .= (($conditions != '')?' AND ':'WHERE ') . '`' . $this->realescape($column) . '` = ' . ((is_string($value))?'\'':'') . $this->realescape($value) . ((is_string($value))?'\'':'');
}
}
$domain = '';
if ($limit !== null) {
$domain = 'LIMIT ' . $this->realescape($limit);
if ($offset !== null) {
$domain .= ' OFFSET = ' . $this->realescape($offset);
}
}
$order = '';
if ($orderby !== null) {
$order = 'ORDER BY `' . $this->realescape($orderby) . '` ' . (($asc)?'ASC':'DESC');
}
$this->_sql = 'SELECT ' . $select . ' FROM `' . $this->_table . '`';
if ($conditions != '') {
$this->_sql .= ' ' . $conditions;
}
if ($domain != '') {
$this->_sql .= ' ' . $domain;
}
if ($order != '') {
$this->_sql .= ' ' . $order;
}
return $this->query();
}
The $known variable might be set, if set it should be an array which contains all the 'known' elements of the rows we are selecting.
My question: How can I make this so that conditions such as
age < 18, or date > 5120740154 are easily made?
Also, if you think the way I'm making this work is wrong, please say so.
Thanks in advance
Dynamically build WHERE conditions that can use any operators
Create a $where array that holds your where conditions.
$where = array (
'age <' => 18,
'date >' => 5120740154
);
You can build that into your SQL query using a function like this:
private function buildWhereConditions($where) {
$conditions = 'WHERE 1=1';
/*1=1 is just so you can easily append ANDs */
foreach ($where as $key => $value) {
$conditions .= " AND $key $value ";
}
return $conditions;
}
This will return the where conditions to be appended to your query:
WHERE 1=1 AND age < 18 AND date > 5120740154
Related
I want to override batchinsert, because I want to add ON DUPLICATE KEY UPDATE
$result = Yii::$app->db->createCommand()->batchInsert('product', ['asin', 'title', 'image', 'url', 'price'], $results)->execute();
public function batchInsert($table, $columns, $rows)
{
if (empty($rows)) {
return '';
}
$schema = $this->db->getSchema();
if (($tableSchema = $schema->getTableSchema($table)) !== null) {
$columnSchemas = $tableSchema->columns;
} else {
$columnSchemas = [];
}
$values = [];
foreach ($rows as $row) {
$vs = [];
foreach ($row as $i => $value) {
if (isset($columns[$i], $columnSchemas[$columns[$i]]) && !is_array($value)) {
$value = $columnSchemas[$columns[$i]]->dbTypecast($value);
}
if (is_string($value)) {
$value = $schema->quoteValue($value);
} elseif ($value === false) {
$value = 0;
} elseif ($value === null) {
$value = 'NULL';
}
$vs[] = $value;
}
$values[] = '(' . implode(', ', $vs) . ')';
}
$query = 'INSERT INTO ' . $schema->quoteTableName($table);
$duplicate = ' ON DUPLICATE KEY UPDATE ';
$last = end($columns);
reset($columns);
foreach ($columns as $i => $name) {
$columns[$i] = $schema->quoteColumnName($name);
$duplicate .= $schema->quoteColumnName($name) . ' = VALUES(' . $schema->quoteColumnName($name) . ')';
if ($name <> $last) {
$duplicate .= ', ';
}
}
$query .= ' (' . implode(', ', $columns) . ') ';
$query .= ' VALUES ' . implode(', ', $values);
$query .= $duplicate;
return $query;
}
This is working but now I need to override yii\db\QueryBuilder
If found this: Yii::$app->db->commandClass = new common\models\Command();
and I can override the command class, but the code in yii\db\Schema should be updated too
public function createQueryBuilder()
{
return new \common\models\QueryBuilder($this->db);
}
Or I need to do something completely different, how can I fix this?
According to this discussion about non standard sql commands of devs you can do following (not tested):
class MyQueryBuilder extends yii\db\mysql\QueryBuilder
{
public function batchInsert($table, $columns, $rows)
{
$sql = parent::batchInsert($table, $columns, $rows);
$sql .= 'ON DUPLICATE KEY UPDATE';
return $sql;
}
}
or you can do this:
$db = Yii::$app->db;
$sql = $db->queryBuilder->batchInsert($table, $fields, $rows);
$db->createCommand($sql . ' ON DUPLICATE KEY UPDATE')->execute();
<?php
namespace common\models;
use yii\db\mysql\QueryBuilder as baseQueryBuilder;
class QueryBuilder extends baseQueryBuilder
{
public function batchInsert($table, $columns, $rows)
{
$sql = parent::batchInsert($table, $columns, $rows);
$sql .= ' ON DUPLICATE KEY UPDATE ';
$schema = $this->db->getSchema();
$last = end($columns);
reset($columns);
foreach ($columns as $i => $column)
{
$columns[$i] = $schema->quoteColumnName($column);
$sql .= $schema->quoteColumnName($column) . ' = VALUES(' . $schema->quoteColumnName($column) . ')';
if ($column <> $last) {
$sql .= ', ';
}
}
return $sql;
}
}
$db = Yii::$app->db;
$queryBuilder = new \common\models\QueryBuilder(Yii::$app->db);
$query = $queryBuilder->batchInsert('product', ['asin', 'title', 'image', 'url', 'price'], $results);
$db->createCommand($query)->execute();
I am trying to create a class to save time on cleaning up my variables before sending them to the database to prevent sql injections. The basic systems is working now but i cant seem to get a where/or statement implemented. Does anyone know how to add this?
<?php
class Database {
private $db = '';
private $database = '';
function __construct($settings) {
$this->db = new mysqli('127.0.0.1', $settings['mysql_user']['username'], $settings['mysql_user']['password']);
$this->database = $settings['mysql_user']['database'];
print_r('Database Loaded!<br/>');
}
public function query($method, $database, $rows, $params, $where = array(), $or = array()) {
$count = 0;
$amount = count($rows);
$final_rows = '';
$final_data = '';
$bind_names = array();
$bind_names[0] = '';
$param_types = array(
"int" => "i",
"string" => "s",
"double" => "d",
"blob" => "b"
);
switch($method) {
case 'INSERT':
foreach ($rows as $row) {
$count = $count + 1;
$final_rows .= '`' . $row . '`' . ($count != $amount ? ', ' : '');
$final_data .= '?' . ($count != $amount ? ', ' : '');
}
$stmt = $this->db->prepare('INSERT INTO `' . $this->database . '`.`' . $database . '` (' . $final_rows . ') VALUES (' . $final_data . ')');
for ($i = 0; $i < count($params); $i++)
{
$bind_name = 'bind'.$i;
$$bind_name = $params[$i][1];
$bind_names[0] .= $param_types[$params[$i][0]];
$bind_names[] = &$$bind_name;
}
call_user_func_array( array ($stmt, 'bind_param'), $bind_names);
return $stmt->execute();
break;
case 'UPDATE':
foreach ($rows as $row) {
$count = $count + 1;
$final_rows .= '`' . $row . '`' . ($count != $amount ? ', ' : '');
$final_data .= '?' . ($count != $amount ? ', ' : '');
}
$stmt = $this->db->prepare('UPDATE `' . $this->database . '`.`' . $database . '` SET ' . $final_rows . '');
for ($i = 0; $i < count($params); $i++)
{
$bind_name = 'bind'.$i;
$$bind_name = $params[$i][1];
$bind_names[0] .= $param_types[$params[$i][0]];
$bind_names[] = &$$bind_name;
}
call_user_func_array( array ($stmt, 'bind_param'), $bind_names);
return $stmt->execute();
break;
case 'REPLACE':
foreach ($rows as $row) {
$count = $count + 1;
$final_rows .= '`' . $row . '`' . ($count != $amount ? ', ' : '');
$final_data .= '?' . ($count != $amount ? ', ' : '');
}
$stmt = $this->db->prepare('REPLACE INTO `' . $this->database . '`.`' . $database . '` (' . $final_rows . ') VALUES (' . $final_data . ')');
for ($i = 0; $i < count($params); $i++)
{
$bind_name = 'bind'.$i;
$$bind_name = $params[$i][1];
$bind_names[0] .= $param_types[$params[$i][0]];
$bind_names[] = &$$bind_name;
}
call_user_func_array( array ($stmt, 'bind_param'), $bind_names);
return $stmt->execute();
break;
}
}
}
?>
Going to make a few assumptions, but first I'll recommend you use an ORM before whipping up your own solution. Here's a good list of PHP libraries (I've linked to the database sections, which includes some very well done stand-alone ORMs https://github.com/ziadoz/awesome-php#database)
That being said I'm going to assume the $where and $or arrays are both for the WHERE construct and the items in $where are combined via AND and the $or is combined via OR.
Because you didn't describe what kind of output you were looking for I'm also assuming your $where and $or are key/value pairs which translates to "key=value AND key=value AND (key=value OR key=value)".
DISCLAIMER: This example is kind of hacky, but is the shortest/simplest way to get the example across.
$whereQuery = '';
foreach ($where as $key => $value) {
$whereQuery .= "$key = $value AND";
}
if ($or !== array()) {
$whereQuery .= '(';
foreach ($or as $key => $value) {
$whereQuery .= "$key = $value OR";
}
}
if ($whereQuery !== '') {
if (($temp = strlen($whereQuery) - strlen('AND')) >= 0 && strpos($whereQuery, 'AND', $temp) !== false) {
$whereQuery = substr($whereQuery, -4);
} else {
$whereQuery = substr($whereQuery, -3) . ')';
}
$whereQuery = "WHERE $whereQuery";
}
You can then stick the $whereQuery at the end of an UPDATE or SELECT. Even if $where and $or are empty it'll still work.
You could move the loops into functions and make it recursive if the $value was another array so you could create more complex WHERE statements.
how i can make a insert using this fuctions
I m learning php, as using this functions (mysqli abstract) but after update wont work any more.
/** insert data array */
public function insert(array $arr)
{
if ($arr)
{
$q = $this->make_insert_query($arr);
$return = $this->modifying_query($q);
$this->autoreset();
return $return;
}
else
{
$this->autoreset();
return false;
}
}
complement
/** insert query constructor */
protected function make_insert_query($data)
{
$this->get_table_info();
$this->set_field_types();
if (!is_array(reset($data)))
{
$data = array($data);
}
$keys = array();
$values = array();
$keys_set = false;
foreach ($data as $data_key => $data_item)
{
$values[$data_key] = array();
$fdata = $this->parse_field_names($data);
foreach ($fdata as $key => $val)
{
if (!$keys_set)
{
if (isset($this->field_type[$key]))
{
$keys[] = '`' . $val['table'] . '`.`' . $val['field'] . '`';
}
else
{
$keys[] = '`' . $val['field'] . '`';
}
}
$values[$data_key][] = $this->escape($val['value'], $this->is_noquotes($key), $this->field_type($key), $this->is_null($key),
$this->is_bit($key));
}
$keys_set = true;
$values[$data_key] = '(' . implode(',', $values[$data_key]) . ')';
}
$ignore = $this->ignore ? ' IGNORE' : '';
$delayed = $this->delayed ? ' DELAYED' : '';
$query = 'INSERT' . $ignore . $delayed . ' INTO `' . $this->table . '` (' . implode(',', $keys) . ') VALUES ' . implode(',',
$values);
return $query;
}
before update this class i used to insert data like this
$db = Sdba::table('users');
$data = array('name'=>'adam');
$db->insert($data);
this method of insert dont works on new class.
if i try like this i got empty columns and empty values.
thanks for any help
complete class download http://goo.gl/GK3s4E
Try using set instead of insert:
$users = Sdba::table('users');
$user['name'] = 'Alvaro';
$users->set($user);
I am trying to recreate something similar to the CodeIgniter active record. My goal is following:
When this is called:
echo $messages->select()->from()->where('user', 'foo')->where('dateadded', 'today');
it needs to create two where conductions (WHERE user = foo AND dateadded = today). But at the moment I am getting only the value of the second function called (WHERE dateadded = today). How can I do this?
This is the code I have so far:
abstract class DatabaseQuery extends Database
{
protected $_tablename;
protected $_primary_key = 'id';
protected $_dbSelect = '';
protected $_dbFrom = '';
protected $_dbJoin = '';
protected $_dbWhere = '';
function __construct()
{
parent::__construct();
}
public function __toString()
{
$query = '';
$query .= $this->_dbSelect . PHP_EOL;
$query .= $this->_dbFrom . PHP_EOL;
$query .= $this->_dbJoin . PHP_EOL;
$query .= $this->_dbWhere . PHP_EOL;
return $query;
}
public function select($select = null)
{
if($select === null):
$this->_dbSelect = "SELECT *";
else:
$this->_dbSelect = "SELECT {$select}";
endif;
return $this;
}
public function from($from = null)
{
if($from === null):
$this->_dbFrom = "FROM {$this->_tablename}";
else:
$this->_dbFrom = "FROM {$from}";
endif;
return $this;
}
public function join($table, $on)
{
if(is_string($join))
$this->_dbJoin = "JOIN {$table} ON {$on}";
return $this;
}
public function where($field, $value = null)
{
if(!is_array($field)):
$this->_dbWhere = "WHERE " . $field . ' = :' . $value;
else:
$where = 'WHERE ';
foreach($field as $key => $value):
$where .= $key . ' = :' . $key . ' AND ';
endforeach;
$this->_dbWhere = rtrim($where, ' AND ');
endif;
return $this;
}
you need some sort of a trigger to run the query at the end
$messages->select()->from()->where('user', 'foo')->where('dateadded', 'today')->get();
you can smooth things up in the trigger for example
<?php
class QueryBuilder{
private $_dbWhere;
public function where($field, $value = null)
{
if(!is_array($field)):
$this->_dbWhere .= $field . ' = :' . $value . ' AND ';
else:
foreach($field as $key => $value):
$this->_dbWhere .= $key . ' = :' . $key . ' AND ';
endforeach;
endif;
return $this;
}
// your trigger this will get the result for the class user
public function get()
{
// smooth things up (remove last AND )
$this->_dbWhere = " WHERE " . rtrim($this->_dbWhere, ' AND ');
// build the query select . from . where
// and return the result (in this case print _dbWhere)
echo $this->_dbWhere;
}
}
this will work
$db = new QueryBuilder;
$db->where('user', 'foo')->where('dateadded', 'today')->get();
However, your trying to reinvent the wheels there are tons of beautiful query builders out there take a look at Laravel's Query Builder to get some inspiration
Would that fullfill your needs ?
public function where($field, $value = null)
{
if(this->_dbWhere == ''){
$this->_dbWhere .= "WHERE ";
} else {
$this->_dbWhere .= " AND ";
}
if(!is_array($field)){
$this->_dbWhere = . $field . ' = :' . $value;
}else{
foreach($field as $key => $value):
$where .= $key . ' = :' . $key . ' AND ';
endforeach;
$this->_dbWhere = rtrim($where, ' AND ');
}
return $this;
}
I am trying to make a query having multiple select boxes, User can select one or multiple values from the drop down menu, But its not working it works only if user select one select box if user tries to select two or more select box values it is not able to display the where clause properly. I saw this code in another web site but not able to pick it properly, If some one can help in understanding or simplifying...
if ($office != '') {
$where = ($where == '') ? 'WHERE ' : 'AND ';
$where .= "adpno = '$office'";
}
if ($sector!= '') {
$where = ($where == '') ? 'WHERE ' : 'AND ';
$where .= "sector= '$sector'";
}
if ($subsector!= '') {
$where = ($where == '') ? 'WHERE ' : 'AND ';
$where .= "subsector= '$subsector'";
}
mysql_query('SELECT * FROM sometable ' . $where);
Make sure to add a space before and after AND and WHERE!
In your code when more than one condition is used, the query is probably built as: SELECT * FROM sometable WHERE subsector=3AND sector=5 (note the lack of space before AND).
In your code sample, there is a space only after AND/WHERE. Note that MySQL ignores whitespaces in the query, so don't worry if it ends up with a double space sometimes. Just make sure to have at least one space, separating all elements of the query.
EDIT: Also, make sure that each if concatenates to the previous WHERE clause instead of overwriting it. So:
if ($office != '') {
$where .= ($where == '') ? ' WHERE ' : ' AND ';
$where .= "adpno = '$office'";
}
if ($sector!= '') {
$where .= ($where == '') ? ' WHERE ' : ' AND ';
$where .= "sector= '$sector'";
}
if ($subsector!= '') {
$where .= ($where == '') ? ' WHERE ' : ' AND ';
$where .= "subsector= '$subsector'";
}
mysql_query('SELECT * FROM sometable ' . $where);
I'd try something like this:
$conditions = '';
if ($tmp = #$_GET['office'])
$conditions .= ($conditions != '' ? ' AND ' : '') . 'office = \'' . mysql_escape_string($tmp) . '\'';
if ($tmp = #$_GET['sector'])
$conditions .= ($conditions != '' ? ' AND ' : '') . 'sector = \'' . mysql_escape_string($tmp) . '\'';
// ...
mysql_query('SELECT * FROM sometable' . ($conditions != '' ? ' WHERE ' . $conditions : '') . 'M');
If you've got more than just two or three fields, a foreach loop might be better suited:
$conditions = '';
$fields = array('office', 'sector', 'subsector', /*...*/);
foreach ($fields as $field)
if ($tmp = #$_GET[$field])
$conditions .= ($conditions != '' ? ' AND ' : '') . $field . ' = \'' . mysql_escape_string($tmp) . '\'';
mysql_query('SELECT * FROM sometable' . ($conditions != '' ? ' WHERE ' . $conditions : '') . 'M');
You could even simplify code a bit by always having at least one condition (untested, but should work):
$conditions = 'TRUE';
$fields = array('office', 'sector', 'subsector', /*...*/);
foreach ($fields as $field)
if ($tmp = #$_GET[$field])
$conditions .= ' AND ' . $field . ' = \'' . mysql_escape_string($tmp) . '\'';
mysql_query('SELECT * FROM sometable WHERE ' . $conditions . ';'));
$fields = array('sector' , 'subsector' , 'office');
$query = '';
foreach($fields as $field)
{
if($query != '')
$query .= ' AND ';
if( $input[$field] != '')
$query .= $field ." = '". $input[$field] ."'";
}
I would recommend you to use mysqli or pdo instead of mysql.
$input is an array of the input values after you've made "security checks",
for instance: mysql_escape_string and htmlspecialchars