Please, read this question carefully before answer or pressing "close" link.
This question is about sharing experience, exchange of tips and tricks.
What code you are using to insert data into mysql database?
Just a snip of code from your real project. Preferably of CRUD type.
I mean real code. Please, do not copy-paste code examples from manual. It's different from the real life needs. Please, do not answer "you can use these methods...". I know them all. I am asking not for methods but for real coding experience.
I find it very interesting and very enlightening to share your code, to learn from others experience.
Please note that code must be complete, including all data preparations. But no validation if possible. A backstage work can be omitted if there is too much of it (like model initializing and such). I am asking more for food for thought than for code to copy and paste.
Please, do not close this topic too fast.
I an hungry for real world code examples, there is a very little of them here, but dummy code snippets everywhere.
Languages other than PHP are welcome, as well as any ORM or framework usage. But please, remember - not copy-paste from documentation example, but from your own project. There is huge difference.
There's the Redbean ORM. What I have done is basically wrap my own code around its domain object, hence it looks like
class Book extends RedBean_DomainObject
{
public function __construct($id)
{
if ($id!=0)
$this->find($id);
}
public function save_details($author, $title)
{
// insert OR update new entry
$this->author = $author;
$this->title = $title;
$this->save();
}
}
The code will check if the 'bean' exists; if it does, it will load it. You assign properties to the class, and call the save() method to save it to the bean. The RedBean ORM will automatically detect if it is a save or an update.
Note: The RedBean Domain Object has been replaced by something better, though.
I am also using WordPress wp-db, and I like the syntax
$wpdb->insert("books", array('title' => $title, 'author' =>$author));
I found a little wrapper online which allows me to do INSERT...ON DUPLICATE KEY too.
$wpdb->insert_on_duplicate("author_book_relationship",
array('book_id' => $book_id,
'date_published' =>$date_published),
array('book_id' => $book_id));
The first parameter is the table, the second is the insert/update information and the last one is the where clause for the UPDATE part.
Edit
I usually wrap SQL functions in a helper
class BookHelper
{
public function save_relationship($id, $book, $author)
{
global $wpdb;
$wpdb->insert_on_duplicate("author_book_relationship",
array('book_id' => $book_id,
'date_published' =>$date_published),
array('book_id' => $book_id));
}
}
And inside a strategy
class BookSaveStrategy
{
protected $book_helper;
public function save_relationship($id, $book, $title)
{
// validate id, book and title
//.....
// Save if ok
$this->book_helper->save_relationship($id, $book, $title);
}
}
Which can be use in a controller
if (isset($_POST['save_book']))
{
$book_save_strategy->save($_POST['id'], $_POST['author'], $_POST['title']);
}
My framework's ORM:
$User = new User();
$User->name = 'John';
$User->email = 'john#example.com';
$User->homepage = 'http://example.com';
$User->save();
Keeping it simple, lightweight, and effective.
Using ADOdb:
$stmt = $db->Prepare("INSERT INTO `table` (`column1`, `column2`, `column3`) VALUES (?, ? ,?)");
$insert = $db->Execute($stmt, array($value1, $value2, $value3));
if($insert === false) throw new Exception($db->ErrorMsg());
Below are some examples from our company's framework.
Raw queries:
Db::query(
"UPDATE sometable SET city = %s, region = %s WHERE id = %d",
$city, $region, $id);
// or
Db::query(
"UPDATE sometable SET city = %(city)s, region = %(region)s WHERE id = %(id)d",
array('city' => $city, 'region' => $region, 'id' => $id));
There is a number of placeholders supported, such as %s (string), %d (integer), %f (float), %? (auto-detects type of the parameter), %- (no escape, insert as is) and even %as, %ad etc. (arrays of strings/integers/whatever, replaced with comma-separated values). Everything is properly escaped behind the scene.
There is also some kind of ORM with quite limited features:
$city = !empty($id) ? City::fetchOneById($id) : City::create();
$city->setValues(array('city' => $city, 'region' => $region));
$city->save();
Function taken from a recent project's model (using Codeigniter). It's used to
insert a period of vacation in the calendar and in the table that keeps track
of variations (minus for vacations taken, plus is system added every month).
DateIterator is a custom iterator which returns consecutive dates in the format
yyyy-mm-dd, work_days is a custom helper that count the - ugh - number of
working days between two supplied dates. Apologies for italian variable names.
function richiesta_ferie($utente, $inizio, $fine) {
// INSERT INTO Ferie
$data = array(
'ID_Utente' => $utente,
'Richiesta' => date('Y-m-d H:i:s'),
'Inizio' => $inizio,
'Fine' => $fine,
'Stato' => 'P',
);
$this->db->insert('Ferie', $data);
$ID_Ferie = $this->db->insert_id();
// INSERT INTO Variazione
$work_days = -1 * work_days($inizio, $fine);
$data = array(
'ID_Richiesta' => $ID_Ferie,
'ID_Utente' => $utente,
'Giorni' => $work_days,
);
$this->db->insert('Variazione', $data);
// INSERT INTO Giorno
// DateIterator defined in helpers/MY_date_helper.php
$DateIterator = new DateIterator($inizio, $fine);
foreach ( $DateIterator as $date ) {
// skip sundays
if ( date('w', strtotime($date)) == 0 ) {
continue;
}
$data = array(
'ID_Utente' => $utente,
'ID_Richiesta' => $ID_Ferie,
'TipoRichiesta' => 'F',
'Giorno' => $date,
'Stato' => 'P',
);
$this->db->insert('Giorno', $data);
}
}
try {
$pSt = $dbh->prepare('INSERT INTO voucher (field1, field2, field3) VALUES (:field1, :field2,:field3)');
$pSt->execute(array(':field1'=>$field1, ':field2' =>$field2,':field3'=>$field3));
$status=$pSt->errorCode();
if($status=='00000'){
echo "Voucher $voucher created successfully.";
}else{
$error=$pSt->errorInfo();
echo $error[2];
}
} catch (Exception $e) {
echo "Failed: " . $e->getMessage();
}
Changed field names.Otherwise this is the code that I use.
Here is an example from my one of my scripts:
$fields=array('city','region');
if ($id=intval($_POST['id'])) {
$query="UPDATE $table SET ".dbSet($fields)." WHERE id=$id";
} else {
$query="INSERT INTO $table SET ".dbSet($fields);
}
dbget(0,$query);
dbSet() is a helper function to produce an SQL SET statement out of $_POST array and array contains field names, using a consideration which makes form field names equal to table field names.
dbget() is a function to run a query, 0 means raw type of return value, in case it will be needed in the further code.
public static function insertSQL($table, $fields, $values)
{
if (self::$_databaseLogger == null) { self::$_databaseLogger = &LoggerManager::getLogger('databaseAccess'); }
$fieldCount = count($fields);
$valueCount = count($values);
if ($fieldCount != $valueCount) {
self::$_databaseLogger->error('database.insertSQL() Error: field list is different size to value list');
throw new Exception("Database::insertSQL field list is different size to value list");
} else {
$sql = 'INSERT INTO '.$table.' ('.implode(', ',$fields).') VALUES ('.implode(', ', $values).')';
self::$_databaseLogger->databaseQuery('database.insertSQL(): '.$sql);
$statement = database::getDBConnection()->prepare($sql);
$insertedRows = $statement->execute();
if ($insertedRows === False) {
self::$_databaseLogger->error('database.insertSQL(): insertSQL ERROR');
throw new Exception('database::insertSQL ERROR');
}
self::$_databaseLogger->databaseResult('database.insertSQL(): Inserted '.$insertedRows.' rows');
return $insertedRows;
}
} // function insertSQL()
All data values are validated, quoted where appropriate for strings and escaped prior to calling the insertSQL() method. The logger used is log4PHP.
EDIT
Use case:
$fileTreeTableFieldsArray = array ( 'FILE_ID',
'FILE_TYPE',
'FILE_NAME',
'FILE_PARENT_ID',
'FILESIZE',
'CREATED_BY',
'CREATED_ON',
'APPLICATION_CONTEXT' );
$fileTreeTableValuesArray = array ( database::getSQLValueString($this->_fileID,'int'),
database::getSQLValueString($fileType,'text'),
database::getSQLValueString($name,'text'),
database::getSQLValueString($parentID,'int'),
database::getSQLValueString($fileSize,'int'),
database::getSQLValueString($_SESSION["USERID"],'int'),
database::getSQLValueString('sysdate','datetime'),
database::getSQLValueString($_SESSION["APPLICATION"],'int') );
Database::startTransaction();
try {
$result = database::insertSQL('RCD_FILE_TREE',
$fileTreeTableFieldsArray,
$fileTreeTableValuesArray);
} catch (Exception $e) {
Database::rollback();
$error = $this->_setError(baseFileClassInsertException, $this->_fileCategory, $this->_fileName, $sql, $e->getMessage());
$this->_logger->error($this->_ErrorMessage);
return $error;
}
Database::commitTransaction();
Related
I'm making a project where a user can publish/post their own stories and read others' stories. Very simple.
This is my controller method named publish:
public function published()
{
$story = array('author' => $this->session->userdata('username'),
'title' => $this->input->post('title'),
'synopsis' => $this->input->post('synopsis'));
$new_storyid = $this->story_model->new_story($story);
if($new_storyid != NULL)
{
$genre = $this->input->post('genre');
for($temp=0;$temp<count($genre);$temp++)
{
$genres[$temp] = array('story_id' => $new_storyid,
'story_genre_name' => $genre[$temp]);
}
$insert_genre = $this->story_model->new_story_genre($genres);
$tag = $this->input->post('tags');
for($temp=0;$temp<count($tag);$temp++)
{
$tags[$temp] = array('story_id' => $new_storyid,
'story_tag_name' => $tag[$temp]);
}
$content_warning = $this->input->post('content_warning');
for($temp=0;$temp<count($content_warning);$temp++)
{
$content_warnings[$temp] = array('story_id' => $new_storyid,
'story_content_warning_name' => $content_warning[$temp]);
}
//$chapter = array('story_id' => $new_storyid,
//'chapter_number' => 1, 'chapter_title' => $this->input->post('chapter_title'),
//'chapter_content' => $this->input->post('chapter_content'),
//'chapter_number' => 1, 'date_added' => mdate('%Y-%m-%d %h-%i-%s',time()));
//$result = $this->story_model->add_chapter($chapter);
//if($result){
//redirect('account/userprofile_published_stories');}
}
}
This is my model methods for the above controller method:
public function new_story($story)
{
$this->db->select('user_id');
$query = $this->db->get_where('users',array('username' => $story['author']))->result();
foreach($query as $row)
{ $userid = $row->user_id; }
$publish = array('user_id' => $userid,
'story_title' => $story['title'],
'synopsis' => $story['synopsis']);
$this->db->insert('story',$publish);
return $this->db->insert_id();
}
public function new_story_genre($genre)
{
foreach($genre as $row)
{
$this->db->insert('story_genre', $row);}
}
public function add_chapter($chapter){
$this->db->where('story_id', $chapter['story_id']);
return $this->db->insert('chapters', $chapter);
}
I haven't added the other 2 functions for my tags and content warning inserts because i am confused right now. It all works fine, my genre is inserted.
My tables looks like this:
Story tables
In inserting a story in my above method, the first thing i do is insert a new story row in my story table and returns the new_storyid variable.
after that with the new storyid i add the genre,tags,content warning then the chapters.
My question is, what should i return in my methods for inserting the genre,tags,contentwarning?
I forgot this part because every model method ive written so far always returns a variable i needed in my controller. My first thought was to return a TRUE/FALSE variable if insert is successful/fail but barring special circumstances since ive already processed the data its 100% sure to insert successfully. Should i be returning TRUE/FALSE and adding an if statement like:
if($insert_genre){
//insert tags here
if($insert_tags){
//insert content warning here
if($insert_content_warning){
//insert chapters here
//redirect to view here
}
}
}
Or can i just not return anything? and if so, is this a proper/right way?
EDIT: I forgot to mention i haven't yet added form_validation rules before all the inserts. So my function will be nested in multiple if statements.
I just edited my model method:
public function new_story_genre($genre){
$inserted = 0;
foreach($genre as $row){
$this->db->insert('story_genre', $row);
$inserted += $this->db->affected_rows();}
if($inserted == count($genre)){
return TRUE;}else{ return FALSE; }
}
Above compares the number of inserted rows with the number of rows passed into the method. Everytime a row is inserted it adds 1 to the inserted variable. So if my controller passes 3 rows into the method, the inserted variable should also be 3 for a successful insert.
I think you are correct in always returning something. Errors can and do happen for whatever reason, and its a good idea to account for them even if you already validated your data (you never know). Coding practices suggest that more than a couple of nested ifs is bad practice. A personal preference of mine is to check for failure rather than success all the way down the chain until the last lines of the function (if it got that far than everything is good to go).
A scheme like this I usually use:
public function something() {
if (!$insert_genre) {
// add flash error message
// redirect to controller
}
if (!$insert_tags) {
// add flash error message
// redirect to controller
}
if (!$insert_content_warning) {
// add flash error message
// redirect to controller
}
// yay, something went right!
}
In this kindof circumstance it is very procedural. The most important conditions should be first, and if C depends on A, then A should be the first condition.
Unrelated:
It is hard to follow some of your text here, but it also seems like you should look into how you are doing the genres. If the entered genre already exists in the database do you really need to add it? Shouldn't you just use a relationship there storing the id in the main table and joining when displaying?
I'm using codeigniter.
I'm trying to compare some posted values from a form with entries from a database.
Put simply, i want to check to see if the entry is already in the database, if so ignore the posted data, but if there is no database entry then add to the database.
My thinking was that it shouldn't actually be that hard, but having some issues, and now im completely confused. Any pointers in the right direction would be appreciated.
I have an array coming from POST $assigned_ids
And i want to compare that with the data from $assign_data which is being output from the database.
I've been trying foreach loops, looping over the posted data, and then inside that looping through the database and comparing and adding if neccessary.
It works upto a point, but if there is data coming from the database, its adding multiple entires.
Heres my code, surely im over complicating things?
// posted values foreach - loop through all posted values
foreach($assigned_ids as $id) {
if(is_array($assign_data) && count($assign_data) > 0) {
// query all data in assignments table
foreach($assign_data as $key => $value) {
// is the user id from assignments table in posted id's
if(in_array($value->user_id, $id)){
// if it is, then do the course id's match as well? if so, do nothing, already an entry
if($value->course_id == $course_id) {
echo "match id and course, do nothing";
} else {
// else if there isnt an entry for this user for this course, add the entry
$add_data = array(
'user_id' => $value->user_id,
'course_id' => $course_id,
'org_id' => $org_id
);
$this->assignment_model->save_org_assignments($add_data);
}
} else {
// the user id was not in the array from the db, but was in the posted vars, so log in db
$add_data = array(
'user_id' => $id,
'course_id' => $course_id,
'org_id' => $org_id
);
$this->assignment_model->save_org_assignments($add_data);
}
}
} else {
$add_data = array(
'user_id' => $id,
'course_id' => $course_id,
'org_id' => $org_id
);
$this->assignment_model->save_org_assignments($add_data);
}
}
I think your main issue is your array is not properly structured that's why your having a hard time.
My opinion is to predefined your db result after fetching it.
function getAssignedData(){
// its better to get the only field you'll need than to fetch everything
$result = $this->db->select($field)->get();
if($result->num_rows()){
$existing_ids = [];
foreach($result->result() as $row){
$existing_ids[] = $row->$field;
}
return array_flip($existing_ids);
}
return FALSE;
}
And you can already compare the values like this
foreach($assigned_ids as $id)
{
if(!isset($existing_ids[$id])) {
// do something
}
}
Hope that helps.
I have a draggable div in which the position is being saved to database with user_id set to unique. How would I check if user_id exists.. and if it does, update the other 3 columns.. If it does not exist, insert new row. I have read to use "ON DUPLICATE KEY UPDATE", but having a hard time implementing it. Any thoughts?
As explained by #N.B. - Working solution
global $wpdb;
$_POST['user_id'];
$_POST['div_id'];
$_POST['x_pos'];
$_POST['y_pos'];
$user_id = $_POST['user_id'];
$div_id = $_POST['div_id'];
$x_pos = $_POST['x_pos'];
$y_pos = $_POST['y_pos'];
$wpdb->query($wpdb->prepare(" INSERT INTO coords
(user_id, div_id, x_pos, y_pos)
VALUES (%d, %s, %d, %d)
ON DUPLICATE KEY UPDATE
x_pos = VALUES(x_pos), y_pos = VALUES(y_pos)",
$user_id,
$div_id,
$x_pos,
$y_pos
));
As #N.B. pointed out in the comments, while the first method I submitted works, it is open to race conditions. I'd like to thank him for pointing that out. Here is that first solution with race conditions:
$user_exists = $wpdb->get_var(
$wpdb->prepare("SELECT user_id FROM coords WHERE user_id = %d", $user_id)
);
if($user_exists) {
// Do Update
}
else {
// Do Insert
}
These race conditions are astronomical, as an insert must finish executing in the time between the first query returning and the next insert query. But the race condition exists none-the-less, and therefore could happen at some point in time.
However your database is still safe. When it occurs it won't duplicate the data, but rather it will throw a wpdb error that a unique key already exists and the insert will silently fail (it won't terminate the script and it won't output the error unless error reporting is turned on).
WordPress database error: [Duplicate entry '3' for key 'PRIMARY']
Amazingly, the above technique is used in the Wordpress core and by countless plugin and theme authors, and I could only find two instances of 'ON DUPLICATE' being used correctly in the Wordpress core. So a large chunk of the internet runs with multiple instances of this race condition seemingly just fine, just to give you an idea of the astronomical chance we're talking about.
Regardless of the chance, to use it is bad practice. As N.B. commented, the database should worry about the data and not PHP.
The Real Solution
Wordpress, for whatever reason, does not have an 'INSERT ON DUPLICATE UPDATE' function, which means you have to either write up a query each time with $wpdb->query or build your own function to handle it. I went with writing a function because writing wpdb->query each time is a pain and brings the user one layer closer to accidental mysql injection. Also development speed.
/**
* Insert on Duplicate Key Update.
*
* Wordpress does not have an 'insert on duplicate key update' function, which
* forces user's to create their own or write standard queries. As writing
* queries is annoying and open to mysql injection via human error, this function
* automates this custom query in an indentical fashion to the core wpdb functions.
* Source: http://stackoverflow.com/a/31150317/4248167
*
* #global wpdb $wpdb
* #param string $table The table you wish to update.
* #param array $data The row you wish to update or insert, using a field => value associative array.
* #param array $where The unique keys that you want to update at or have inserted, also a field => value array.
* #param array $data_formats Wordpress formatting array for the data. Will default to %s for every field.
* #param array $where_formats Wordpress formatting array for the where. Will default to %s for every field.
* #return boolean True if successfully inserted or updated the row.
*/
function insertOrUpdate($table, $data, $where, $data_formats = array(), $where_formats = array())
{
if(!empty($data) && !empty($where)) {
global $wpdb;
// Data Formats - making sure they match up with the data.
$default_data_format = (isset($data_formats[0])) ? $data_formats[0] : '%s';
$data_formats = array_pad($data_formats, count($data), $default_data_format);
$data_formats = array_splice($data_formats, 0, count($data));
// Where Formats - making sure they match up with the where data.
$default_where_format = (isset($where_formats[0])) ? $where_formats[0] : '%s';
$where_formats = array_pad($where_formats, count($where), $default_where_format);
$where_formats = array_splice($where_formats, 0, count($where));
// Get Fields
$data_fields = array_keys($data);
$where_fields = array_keys($where);
// Create Query
$query =
"INSERT INTO $table" .
" (" . implode(', ', array_merge($data_fields, $where_fields)) . ")" .
" VALUES(" . implode(', ', array_merge($data_formats, $where_formats)) . ")" .
" ON DUPLICATE KEY UPDATE";
// Compile update fields and add to query
$field_strings = array();
foreach($data_fields as $index => $data_field) {
$field_strings[] = " $data_field = " . $data_formats[$index];
}
$query .= implode(', ', $field_strings);
// Put it all together - returns true on successful update or insert
// and false on failure or if the row already matches the data.
return !!$wpdb->query(
$wpdb->prepare(
$query,
array_merge(
array_merge(
array_values($data),
array_values($where)
),
array_values($data)
)
)
);
}
return false;
}
To use it, you simply enter parameters just like you would with a $wpdb->update function call.
insertOrUpdate(
'testing_table',
array('column_a' => 'hello', 'column_b' => 'world'),
array('id' => 3),
array('%s', '%s'),
array('%d')
);
In your case, it would be:
insertOrUpdate(
'coords',
array('div_id' => $div_id, 'x_pos' => $x_pos, 'y_pos' => $y_pos),
array('user_id' => $user_id),
array('%d', '%d', '%d'),
array('%d')
);
Or you can just use a default formatting:
insertOrUpdate(
'coords',
array('div_id' => $div_id, 'x_pos' => $x_pos, 'y_pos' => $y_pos),
array('user_id' => $user_id),
array('%d')
);
Or you can ignore the formatting which will default to formatting as strings:
insertOrUpdate(
'coords',
array('div_id' => $div_id, 'x_pos' => $x_pos, 'y_pos' => $y_pos),
array('user_id' => $user_id)
);
If you find any issues just let me know.
I have code like this:
$something_type = $this->db
->where('something_type_id', $something->something_type_id)
->get('something_types')
->row();
if(!$something_type) {
$something->type = lang('data_not_specified');
} else {
$something->type = $something_type->something_type_name;
}
// everything is exactly the same here except for one word
$something_category = $this->db
->where('something_category_id', $something->something_category_id)
->get('something_categories')
->row();
if(!$something_category) {
$something->category = lang('data_not_specified');
} else {
$something->category = $something_category->something_category_name;
}
...
// and so on, about four times
One solution I thought of was:
$classfications = array('type', 'category');
foreach ($classifications as $classification) {
$id_property = "something_{$classificiation}_id";
$something_classification = $this->db
->where("something_{$classification}_id", $something->$id_property)
->get("something_{$classification}s")
->row();
if(!$something_classification) {
$something->$classification = lang('data_not_specified');
} else {
$name_property = "something_{$classificiation}_name";
$something->$classification = $something_classification->$name_property;
}
}
Of course, reading that will probably result in someone having the fits, so what do I do about this? This is probably a very common problem but I can't name it so having trouble Googling it.
Are you looking for Inflection?
The biggest challenge with the code snippet in the question is that the classifications you have provided have different pluralized forms (e.g., "type" becomes "types", yet "category" becomes "categories"). In order to structure this data without inflection, you could create a nested array hash, e.g.,
$classifications = array(
'type' => array(
'plural' => 'something_types',
'id' => 'something_type_id',
),
// etc.
);
foreach ($classifications as $singular => $data) {
/*
* Produces:
* $singluar = 'type';
* $data['plural'] = 'something_types';
* $data['id'] = 'something_type_id';
*/
}
However, most of the PHP frameworks I have used include an Inflector class (or similar) to handle the nuances in language that make using singular and plural names together problematic (and would avoid the need for a nested data structure, as described above).
Have a look at CodeIgniter's Inflector Helper to get an idea of what this entails. If you are already using a framework (your use of a $db helper suggests you might be) then also be sure to see if it supports ORM, which handles this kind of scenario automatically.
I'm starting out using the Zend Framework and have created a suitable model which saves data back to a database table. The issue I am having is that the sql statement is trying to insert '?' as the value for each column in the database. I have created the following save function which passes an array of data to the DBtable adapter functions:
public function save() {
$data = $this->getData();
if ($data['pageId']==0) {
$this->getDbTable()->insert($data);
} else {
$this->getDbTable()->update($data, array('pageId = ?' => $data['pageId']));
}
}
This seems to go through the appropriate motions but the item is not added to the database and the sql statement within MySql logs looks something like:
insert into DB_Table ('pageId','title','body') values ('?', '?', '?');
Not quite sure where this is falling down, any pointers would be gratefully received.
Thanks
Data should be in next format:
$data = array(
'pageId' => 1,
'title' => 'title',
'body' => 'body'
);
Are you sure that $this->getDbTable() returns your db adapter?
Try to use:
$db = new Zend_Db_Table('table_name');
$db->insert($data);