Codeigniter 3 model function no return variable - php

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?

Related

Database data field check before Data insertion

I have a data coming from the HTML Page. And i want to check whether the date and the place values already exists. If they exists, it should throw an error saying Data is already present, if those date and place data is not there it should allow the user to save it.
Here is the code which i have written to save it,
public function StoreSampling(Request $request)
{
$date = Carbon::createFromFormat('d-m-Y', $request->input('date'))->format('Y-m-d');
$doctorname = Input::get('doctorselected');
$product = Input::get('product');
$product= implode(',', $product);
$quantity = Input::get('qty');
$quantity =implode(',',$quantity);
$representativeid = Input::get('representativeid');
//Store all the parameters.
$samplingOrder = new SamplingOrder();
$samplingOrder->date = $date;
$samplingOrder->doctorselected = $doctorname;
$samplingOrder->products = $product;
$samplingOrder->quantity = $quantity;
$samplingOrder->representativeid = $representativeid;
$samplingOrder->save();
return redirect()->back()->with('success',true);
}
I searched some of the Stack over flow pages. And came across finding the existence through the ID And here is the sample,
$count = DB::table('teammembersall')
->where('TeamId', $teamNameSelectBoxInTeamMembers)
->where('UserId', $userNameSelectBoxInTeamMembers)
->count();
if ($count > 0){
// This user already in a team
//send error message
} else {
DB::table('teammembersall')->insert($data);
}
But i want to compare the date and the place. And if they are not present, i want to let the user to save it. Basically trying to stop the duplicate entries.
Please help me with this.
There are very good helper functions for this called firstOrNew and firstOrCreate, the latter will directly create it, while the first one you will need to explicitly call save. So I would go with the following:
$order = SamplingOrder::firstOrNew([
'date' => $date,
'place' => $place
], [
'doctorname' => Input::get('doctorselected'),
'product' => implode(',', Input::get('product')),
'quantity' => implode(',',Input::get('qty')),
'representativeid' => Input::get('representativeid')
]);
if($order->exists()) {
// throw error
return;
}
$order->save();
// success
You need to modify your query to something like this:
$userAlreadyInTeam = SamplingOrder::where('date', $date)
->where('place', $place) // I'm not sure what the attribute name is for this as not mentioned in question
// any other conditions
->exists();
if (userAlreadyInTeam) {
// Handle error
} else {
// Create
}
You do not need to use count() as your only trying to determine existence.
Also consider adding a multi column unique attribute to your database, to guarantee that you don't have a member with the same data and place.
The best way is to use the laravel unique validation on multiple columns. Take a look at this.
I'm presuming that id is your primary key and in the sampling_orders table. The validation rule looks like this:
'date' => ['unique:sampling_orders,date,'.$date.',NULL,id,place,'.$place]
p.s: I do not see any place input in your StoreSampling()

Compare array to db and add difference to db

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.

How do I reduce repetitive code when inserting the same metadata into identical columns over multiple tables?

My goals:
Follow database-normalization.
Ability to track changes in my tables.
Ability to restore the state of my database from a given
point in time, e.g. last month.
Separate the code that is processing the data so that the
input can come either from a HTML-form or a script in another language (Ruby/perl).
To accomplish this, I've opted for a database design like the one described in this answer:
StackOverflow: Is there a MySQL option/feature to track history of changes to records?
However, when a user updates several fields, the same metadata has to be inserted into multiple tables that contain identical columns, and my code becomes repetitive.
Example:
A user submits data through a HTML-form.
PHP processes the data like below, with the help of Propel ORM.
function insertEvent($name, $venueId, $user, $date, $id = 0) {
//validation and formatting..
//if id == 0, new event,
//else, this is an update, archive the old database row on successful processing and maintain a common id as a parent
//...
$event = new Event();
$event->setName($name);
$event->setVenueId($venueId);
$event->setUser($user); //1
$event->setValidFrom($date); //1
$event->setValidUntil(null); //1
$event->save();
// ...
}
function insertEventPhonenumber($phonenumber, $pid, $user, $date, $id = 0) {
//...
$event_phonenumber = new EventPhonenumber();
$event_phonenumber->setPid($pid); //2
$event_phonenumber->setPhonenumber($phonenumber);
$event_phonenumber->setUser($user); //2
$event_phonenumber->setValidFrom($date); //2
$event_phonenumber->setValidUntil(null); //2
$event_phonenumber->save();
// ...
}
function insertEventArtistId($artistId, $pid, $user, $date, $id = 0) {
//...
$event_artistId = new EventArtistId();
$event_artistId->setPid($pid); //3
$event_artistId->setArtistId($artistId);
$event_artistId->setUser($user); //3
$event_artistId->setValidFrom($date); //3
$event_artistId->setValidUntil(null); //3
$event_artistId->save();
// ...
}
My problem:
In my full code there are more tables affected than the three in the example.
Marked with //1, //2 and //3, you see data input that is often going to be identical.
In my stomach, I don't like this. I've been trying search engines with queries like 'common columns in SQL insert queries over multiple tables' and variations of the wording, without finding anything directly related to my problem.
Is this bad practice like it feels to me?
How can I minimize the repetition in my code?
Are you trying to get rid of all of those functions? If so you can use the call_user_func to eliminate most of your code.
<?php
class MyApp {
static function insert($name, $params) {
$ob = new $name();
foreach($params as $k=>$v) {
$op = array($ob,"set".ucfirst($k));
//if (is_callable($op)) {
call_user_func($op,$v);
//}
}
$ob->save();
}
}
MyApp::insert(
"EventArtistId",
array(
"pid"=>$_REQUEST['pid'],
"artistId"=>$_REQUEST['artistId'],
"user" => $_REQUEST['user'],
"validFrom" => $_REQUEST['date'], // may need to convert date
"validUntil" => isset($_REQUEST['validUntil']) ? $_REQUEST['validUntil'] : null,
)
);
// More cool
// MyApp::insert($_REQUEST['action'],$_REQUEST['params']);
?>

Choose duplication vs. string based variable (object property) creation

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.

Code you're using to insert data into mysql database

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();

Categories