Yii2 - update record with child records - yii2

I have a form which allows the user to add many child records, in my case they are called "Items". When updating the master record, the user can add, edit or delete child records. Everything works fine, but I am looking for a better way to do this.
Currently, in my Update action, I first delete any existing child records. I then save all the child records from the form post.
public function actionUpdate($id)
{
$model = $this->findModel($id);
if ($model->load(Yii::$app->request->post()) && $model->save()) {
// first delete all existing child records
Item::deleteAll(['parent_id' => $model->id]);
// get the new set of posted Items
$items = Yii::$app->request->post('Item');
if (!empty($items) && is_array($items)) {
// save each Item
foreach ($items as $index => $values) {
$item = new Item();
$item->attributes = $values;
$item->parent_id = $model->id;
$item->save();
}
}
return $this->redirect(['view', 'id' => $model->id]);
}
return $this->render('update', [
'model' => $model,
]);
}
Form View:
<form method="post" action="">
<?php foreach ($model->items as $index => $item): ?>
<?php echo Html::activeTextInput($item, "[$index]name"); ?>
<!-- Example output -->
<!-- <input id="item-0-name" name="Item[0][name]" value="Test" type="text"> -->
Remove this Item
<?php endforeach; ?>
<button type="submit">Submit</button>
</form>
Add a new Item
In the above $model->items refers to the relation in the parent model:
public function getItems()
{
return $this->hasMany(Item::className(), ['parent_id' => 'id']);
}
When the user clicks "Add a new Item" it simply uses JavaScript to clone the last item and replaces its index with the next value.
Very often, the user does not change any child records. So the process of deleting and re-adding child records in that case is pointless.
What I want to know is, is there a way I can intelligently handle this? For example:
Only delete child records if they do not exist in the POSTed Item array
Only edit the child records if they are different to the ones in the database
Only add new child records that do not currently exist in the database
Otherwise leave everything as it is

You can use indexBy() to define your relation to index related items by their ID:
public function getItems() {
return $this->hasMany(Item::class, [/*...*/])->indexBy('id');
}
Then you can check if record with this ID already exist and do update/delete/create action:
// get the new set of posted Items
$items = Yii::$app->request->post('Item');
$existing = $model->items;
if (!empty($items) && is_array($items)) {
// save each Item
foreach ($items as $index => $values) {
if (!isset($existing[$index])) {
// create new item
$item = new Item();
$item->attributes = $values;
$item->parent_id = $model->id;
$item->save();
} else {
// update existing
$existing[$index]->attributes = $values;
$existing[$index]->save();
// remove from $existing array as already processed
unset($existing[$index]);
}
}
// right now $existing has only existing and not updated items - it means
// that they're not available in POST data so we should remove it
foreach ($existing as $item) {
$item->delete();
}
}

Related

How do i save files in a dyanamic location in PHP Yii2?

I want to save files to directories based on company id like if company has id of 11 then it should create a folder of same name and save/upload the files there?
I have my controller logic here but i dont know how to pass my company id which is in Company Model to saveAs() function.
Scene: I have list of companies from different model and i want to create a directory according to company id/name where the respective layout gets saved.
public function actionCreate()
{
$company=Company::find()->all();
$model = new Layouts();
// if ($model->load(Yii::$app->request->post()) && $model->save()) {
// return $this->redirect(['view', 'id' => $model->layout_id]);
// }
$model->setScenario('create');
if ($model->load(Yii::$app->request->post())) {
$model->layouts=UploadedFile::getInstance($model,'layouts');
if(!empty($model->layouts)){
$filename=time()."_layouts".".". $model->layouts->extension;
$model->layouts->saveAs('#app/web/uploads/layouts/'.$filename); //here i want it to be #app/web/uploads/layouts/company-id/filename
$model->layouts=$filename;
$model->created_by=Yii::$app->user->id;
$model->updated_by=Yii::$app->user->id;
}else{
var_dump($model->errors);die;
}
if($model->save()){
Yii::$app->session->addFlash('success','Record Created Successfully');
return $this->redirect(['view','id'=>$model->id]);
}else{
Yii::$app->session->addFlash('error','Record Can not be Created ');
}
return $this->render('create', [
'model' => $model,
'company'=>$company
]);
}
}
You should create folder first,
\yii\helpers\FileHelper::createDirectory(\Yii::getAlias('#app/web/uploads/layouts/'.$model->id), 0777);

How to force data update on laravel validation custom unique

I'm new in laravel. I have a table with menu_id and title I tried to make this title field unique when have the same menu_id. I found the solution here
But I got problem when update it. Can anyone help please?
My code
Validator::extend('unique_custom', function ($attribute, $value, $parameters)
{
// Get the parameters passed to the rule
list($table, $field, $field2, $field2Value) = $parameters;
// Check the table and return true only if there are no entries matching
// both the first field name and the user input value as well as
// the second field name and the second field value
return \DB::table($table)->where($field, $value)->where($field2, $field2Value)->count() == 0;
});
public function updateSubmenu( Request $request) {
$this->validate( $request, [
'menu_id' => 'required',
'title' => 'required|unique_custom:posts,title,menu_id,'.$request->menu_id,
'order_by' => 'required|integer',
'description' => 'required'
],
[
'title.unique_custom' => 'This title already token'
]
);
}
Can you explain what problem have you got on update? Some exception?
Edit:
If you couldn't update record if title not changes, you need to add one condition to Validator:
Validator::extend('unique_custom', function ($attribute, $value, $parameters)
{
// Get the parameters passed to the rule
list($table, $field, $field2, $field2Value) = $parameters;
// If old value not changed, don't check its unique.
$current = \DB::table($table)->where('title')->first();
if( $current->{$field} == $value) {
return true;
}
return \DB::table($table)->where($field, $value)->where($field2, $field2Value)->count() == 0;
});

Initial Values for an array-type attribute in Yii2 Model

If an attribute in a Model holds array data, say Dates, that are populated in a form with multiple rows for this attribute, how may I assign initial values to the array elements so that they display initially in the form when it appears.
In my example, my array-type attribute holds dates and I want each new date row in the form to have different values when the form loads.
<?= $form->field($model, 'datesToPay[]') ?>
I tried to use the DefaultValueValidator filter of Yii2 to assign initial value to the datesToPay array elements but it does not show the value when the form loads.
['datesToPay', 'each', 'rule' => ['default', 'value' => date('Y-m-d')]]
You can do in the controllerAction before render
public function actionCreate()
{
$model = new MyModel();
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', 'id' => $model->id]);
} else {
$model->datesToPay[0] = 'YourValue';
return $this->render('create', [
'model' => $model,
]);
}
}

Yii2 Dynamic Form update Action is not working

I like to explain my problem clearly,
Am using wbraganca/yii2-dynamicform
Here create action is working perfectly, but in update action
In the code which i marked, i don't know what i need to do, i dont have such field (addresses) in customer table. am stuck on that.
suppose if i create a variable in model like public $addressess, it makes me the reload the table again, and that cause while update the same form, data's getting reload and form viewing as empty without empty,
if create a function on that name, i don't know what to write on that..
Am simply using code like this
public function getaddressess()
{
}
Create Action Code
public function actionCreate()
{
$modelCustomer = new Customer;
$modelsAddress = [new Address];
if ($modelCustomer->load(Yii::$app->request->post())) {
$modelsAddress = Model::createMultiple(Address::classname());
Model::loadMultiple($modelsAddress, Yii::$app->request->post());
// ajax validation
if (Yii::$app->request->isAjax) {
Yii::$app->response->format = Response::FORMAT_JSON;
return ArrayHelper::merge(
ActiveForm::validateMultiple($modelsAddress),
ActiveForm::validate($modelCustomer)
);
}
// validate all models
$valid = $modelCustomer->validate();
$valid = Model::validateMultiple($modelsAddress) && $valid;
if ($valid) {
$transaction = \Yii::$app->db->beginTransaction();
try {
if ($flag = $modelCustomer->save(false)) {
foreach ($modelsAddress as $modelAddress) {
$modelAddress->customer_id = $modelCustomer->id;
if (! ($flag = $modelAddress->save(false))) {
$transaction->rollBack();
break;
}
}
}
if ($flag) {
$transaction->commit();
return $this->redirect(['view', 'id' => $modelCustomer->id]);
}
} catch (Exception $e) {
$transaction->rollBack();
}
}
}
return $this->render('create', [
'modelCustomer' => $modelCustomer,
'modelsAddress' => (empty($modelsAddress)) ? [new Address] : $modelsAddress
]);
}
Help me to sort out this problem
$modelsAddress=$modelCustomer->addresses in that example mean array of related Address() instances
public function actionCreate()
{
$modelCustomer = new Customer;
$modelsAddress = $this->getaddressess($modelCustomer->id);
//...................
}
public function getaddressess($id)
{
$model = Address::find()->where(['id' => $id])->all();
return $model;
}
from
public function getaddressess($id)
{
$model = Address::find()->where(['id' => $id])->all();
return $model;
}
Shared above you will also need to add
on your Update view file :
'model' => $model,
'modelsAddress'=>$modelsAddress,
Hope this helps. It worked for me
It should be getAddresses() instead of getaddresses() (although both could work, I'd go with the first one to meet conventions). Or you could set a public $addresses if you don't need extra encapsulation.
suppose if i create a variable in model like public $addressess, it makes me the reload the table again, and that cause while update the same form, data's getting reload and form viewing as empty without empty,
I think you have a validation issue - no validator to mark the field as safe and you see it as empty after posting.
Add public $addresses to your Customer model.
Add "addresses" to your validation rules as safe (or more appropriate validator). This way after posting the form, it probably won't render empty.
This line code ---> $modelsAddress = $modelCustomer->addresses;
is get from model for customer at line ---> public function getAddresses()
this public function line code is code for get array related table from active record method on yii2.
$modelCustomer->addresses the word addresses should come from the $modelCustomer model you must have a relationship to the other table where you add the multiple values. In my example described in the video I have two tables po table and po_items table po_items table has foreign key of po_id. So when you make the Models using gii you will get a relationship in the model that is what you have to use instead of the addresses.
the relationship according my database should be - poItems you will see this at line 14
Add this to Customer Model
public function getAddresses(){
return $this->hasMany(Address::className(), ['id' => 'id']);
}
enter image description hereIn Po.php models:
public function getPoItems()
{
return $this->hasMany(PoItem::className(), ['po_id' => 'id']);
}
In PoController.php
public function actionUpdate($id)
{
$model = $this->findModel($id);
//$modelsPoItem = [new PoItem];
$modelsPoItem = $model->poItems;
if ($model->load(Yii::$app->request->post()) && $model->save())
{
$oldIDs = ArrayHelper::map($modelsPoItem, 'id', 'id');
$modelsPoItem = Model::createMultiple(PoItem::classname(), $modelsPoItem);
Model::loadMultiple($modelsPoItem, Yii::$app->request->post());
$deletedIDs = array_diff($oldIDs, array_filter(ArrayHelper::map($modelsPoItem, 'id', 'id')));
// validate all models
$valid = $model->validate();
$valid = Model::validateMultiple($modelsPoItem) && $valid;
if ($valid) {
$transaction = \Yii::$app->db->beginTransaction();
try {
if ($flag = $model->save(false)) {
if (! empty($deletedIDs))
{
PoItem::deleteAll(['id' => $deletedIDs]);
}
foreach ($modelsPoItem as $modelPoItem)
{
$modelPoItem->po_id = $model->id;
if (! ($flag = $modelPoItem->save(false))) {
$transaction->rollBack();
break;
}
}
}
if ($flag) {
$transaction->commit();
return $this->redirect(['view', 'id' => $model->id]);
}
} catch (Exception $e) {
$transaction->rollBack();
}
}
}
return $this->render('update', [
'model' => $model,
'modelsPoItem' => (empty($modelsPoItem)) ? [new PoItem] : $modelsPoItem
]);
}

insert if not exists Codeigniter

my controller:
function getFeed()
{
$feed_url = $this->input->get("url");
$content = file_get_contents($feed_url);
$x = new SimpleXmlElement($content);
foreach($x->channel->item as $entry) {
$feeds[] = array(
'title' => (string)$entry->title,
'url'=> (string)$entry->link,
'username' => $this->session->userdata('username')
);
$this->load->model('membership_model');
$this->membership_model->feeds($feeds);
}
Model:
function feeds($feeds_data)
{
$this->db->insert_batch('feeds', $feeds_data);
}
Is there a function to insert if only the row doesn't exists in the table? I have a table with 4 column : id,title,url,username. I have an anchor when i click him it calls geFeed function and insert the info into table. But i want to insert only if not exists.
I had the same challenge, so i eventually come up with a function which might be helpful to you.
function safe_update_batch($table_name,$records,$filter_field)
{
$filters=array();
foreach($records as $record)$filters[]=$record[$filter_field];
$this->db->query("SET SESSION group_concat_max_len=10000000");
$query=$this->db->select("GROUP_CONCAT($filter_field) AS existing_keys",FALSE)->where_in($filter_field, $filters)->get($table_name);
$row=$query->row();
$found_fields=explode(',',$row->existing_keys);
$insert_batch=array();
$update_batch=array();
foreach($records as $record)
{
if(in_array($record[$filter_field],$found_fields))$update_batch[]=$record;
else $insert_batch[]=$record;
}
if(!empty($insert_batch))$this->db->insert_batch($table_name,$insert_batch);
if(!empty($update_batch))$this->db->update_batch($table_name,$update_batch,$filter_field);
}
//sample usage
$this->safe_update_batch('feeds', $feeds_data,'title');
You can try this in your model!!
function insertClient($array)
{
$this->db->from('MyTable');
$this->db->where('Id', $array['Id']);
$query = $this->db->get();
if($query->num_rows() != 0){
$data = array(
'name'=>$array['name'],
'phone'=>$array['phone'],
'email'=>$array['email']
);
$this->db->where('Id', $array['Id']);
$this->db->update('CLIENTS', $data);
}else{
$data = array(
'name'=>$array['name'],
'phone'=>$array['phone'],
'email'=>$array['email']
);
$this->db->insert('CLIENTS',$data);
}
}
In controller:
$this->site_model->insertClient($_POST);
Sadly if you are using the active record class an INSERT IF NOT EXISTS function doesn't exist. You could try
Extending the active record class (easier said than done)
You could set indexes on certain columns as UNIQUE so that MySQL will check to see if it already exists
You could do some kind of SELECT before your INSERT to determine if the record is already there
For the queries where you need to do INSERT IF NOT EXISTS do $this->db->query('INSERT IF NOT EXISTS...')
function getFeed()
{
// Load the model up here - otherwise you are loading it multiple times
$this->load->model('membership_model');
$feed_url = $this->input->get("url");
$content = file_get_contents($feed_url);
$x = new SimpleXmlElement($content);
foreach($x->channel->item as $entry) {
// check if the feed is unique, if true then add to array
if( $this->membership_model->singleFeedIsUnique($entry) == TRUE ){
$feeds[] = array(
'title' => (string)$entry->title,
'url'=> (string)$entry->link,
'username' => $this->session->userdata('username')); }
} //foreach
// check to make sure we got any feeds with isset()
// if yes, then add them
if (isset($feeds)){ $this->membership_model->feeds($feeds); }
}
You can try this in your model and leave you controller without changes
function feeds($feeds_data)
{
$data = array(
title => $feeds_data[0],
url => $feeds_data[1],
username => $feeds_data[2]
);
$this->db->select('*');
$this->db->from('mytable');
$this->db->where('title',$feeds_data[0]);//you can use another field
if ($this->db->count_all_results() == 0) {
$query = $this->db->insert('mytable', $data);//insert data
} else {
$query = $this->db->update('mytable', $data, array('title'=>$feeds_data[0]));//update with the condition where title exist
}
}
you can check the id if you have it, adding in the data array and use it to check if exist