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();
Related
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);
This is a bit confusing for me, so I will try to explain it the best I can.
I am running update but nothing is happens.
This is the query which I get:
"UPDATE users SET name = :name, surname = :surname WHERE name = :name AND surname = :surname"
I start the query like this:
$data = ['name' => 'Sasha', 'surname' => 'M'];
$user = $users->where(['name' => 'TestName', 'surname' => 'TestSurname'])->update($data);
This is the update function:
public function update($data)
{
$fields = explode(',', $this->prepareFields($data));
$values = explode(',', $this->prepareValues($data));
$i = 0;
$count = count($fields);
$query = "UPDATE {$this->_tablename} SET ";
for($i; $i < $count; $i++):
$query .= $fields[$i] . " = " . $values[$i] . ',';
endfor;
$query = rtrim($query, ',');
$query .= " WHERE " . rtrim($this->_dbWhere, ' AND ');
$this->query($query);
$this->bindData($data);
$this->_dbBind = call_user_func_array('array_merge', $this->_dbBind);
$this->bindData($this->_dbBind);
$this->execute();
return $this->lastInsertId();
}
Where function:
public function where($field, $value = null)
{
if(!is_array($field)):
$this->_dbWhere .= $field . ' = :' . $field . ' AND ';
$this->_dbBind[] = [$field => $value];
else:
foreach($field as $key => $value):
$this->_dbWhere .= $key . ' = :' . $key . ' AND ';
$this->_dbBind[] = [$key => $value];
endforeach;
endif;
return $this;
}
Bind data function:
public function bindData($data)
{
foreach ($data as $key => $value) :
$this->bind(':' . $key, $value);
endforeach;
}
public function bind($param, $value, $type = null){
if (is_null($type)) {
switch (true) {
case is_int($value):
$type = \PDO::PARAM_INT;
break;
case is_bool($value):
$type = \PDO::PARAM_BOOL;
break;
case is_null($value):
$type = \PDO::PARAM_NULL;
break;
default:
$type = \PDO::PARAM_STR;
}
}
$this->stmt->bindValue($param, $value, $type);
}
Prepare fields and prepare values:
public function prepareFields($data)
{
return $fields = implode(', ', array_keys($data));
}
public function prepareValues($data)
{
$values = implode(', :', array_keys($data));
return ':' . $values;
}
Query function:
public function query($query){
$this->stmt = $this->handler->prepare($query);
}
The crux of this is that you use the same placeholder :fieldname in the WHERE clause and in the SET portion of the statement. You do need to correct other small issues raised here, but a simple solution is to make this change in the where() function:
if(!is_array($field)):
// make up a placeholder name distinct from the one used in SET clause
$field_placeholder = ":where_".$field
$this->_dbWhere .= $field . ' = ' . $field_placeholder . ' AND ';
$this->_dbBind[] = [$field_placeholder => $value];
else:
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 have this query:
UPDATE `terms`
SET id = '15',
taxonomy_id = '1',
parent_id = NULL,
level = 0,
position = 1,
active = '1',
time_created = '2012-05-24 09:31:12',
time_updated = '0000-00-00 00:00:00'
WHERE id = 15
And I want to set the parent_id to NULL with php, but how do I do this?
These are wrong:
$term->parent_id = 'NULL'; -> this is a string
$term->parent_id = NULL; -> this is empty
$term->parent_id = 0; -> this is an integer
there was a problem in my database class, that's why my parent_id value was always empty
this is how I fixed it:
old code:
public function set($data)
{
$this->query .= ' SET ';
if(is_object($data))
$data = get_object_vars($data);
if(is_array($data))
{
$l = count($data);
$t = 1;
foreach($data as $k => $v)
{
$this->query .= $k . ' = ' . ((is_string($v)) ? '\'' . $this->_escape($v) . '\'' : $this->_escape($v));
if($t < $l)
$this->query .= ',';
$t++;
}
}
else
{
$this->query .= $data;
}
return $this;
}
new code:
public function set($data)
{
$this->query .= ' SET ';
if(is_object($data))
$data = get_object_vars($data);
if(is_array($data))
{
$l = count($data);
$t = 1;
foreach($data as $k => $v)
{
$this->query .= $k . ' = ';
if(is_string($v))
{
$this->query .= '\'' . $this->_escape($v) . '\'';
} elseif (is_null($v)) {
$this->query .= 'NULL';
} else {
$this->query .= $this->_escape($v);
}
if($t < $l)
$this->query .= ',';
$t++;
}
}
else
{
$this->query .= $data;
}
return $this;
}
Query should be as :
some query editors requires NULL as null
UPDATE `terms` SET
id = '15',
taxonomy_id = '1',
parent_id = null,
level = 0,
position = 1,
active = '1',
time_created = '2012-05-24 09:31:12',
time_updated = '0000-00-00 00:00:00'
WHERE
id = 15