I have 2 models: ReceivedGoodsDetail and StockInventory.
When model ReceivedGoodsDetail do actionCreate then StockInventory automatically also will be inserted into table StockInventory.
I use this stored procedure, this what I've tried:
public function actionCreate($id) {
$model = new ReceivedGoodsDetail();
$connection = \Yii::$app->db;
$transaction = $connection->beginTransaction();
$model->ID_Received_Goods = $id;
if ($model->load(Yii::$app->request->post()) && $model->validate()) {
$connection = Yii::$app->db;
$command = $connection->createCommand('{call usp_T_Received_Goods_Detail#InsertData(:ID_Received_Goods,:ID_Item, :Qty, :User)}');
$ID_Received_Goods = $model->ID_Received_Goods;
$ID_Item = $model->ID_Item;
$Qty = $model->Qty;
$User = Yii::$app->user->identity->username;
$command->bindParam(":ID_Received_Goods",$ID_Received_Goods,PDO::PARAM_STR);
$command->bindParam(":ID_Item", $ID_Item, PDO::PARAM_STR);
$command->bindParam(":Qty", $Qty, PDO::PARAM_INT);
$command->bindParam(":User", $User, PDO::PARAM_STR);
if ($command->execute() == 0) {
$transaction->commit();
} else {
$transaction->rollBack();
foreach ($model->getErrors() as $key => $message) {
Yii::$app->session->setFlash('error', $message);
}
}
return $this->redirect(['receivedgoodsheader/view', 'id' => $model->ID_Received_Goods]);
} else {
return $this->render('create', [
'model' => $model,
]);
}
}
But I'm confused if use 2 models like the case above
Do not be afraid to do such things, here is nothing bad to use stored procedures. But in general your code is not clean and pretty confusing.
First of all, if you are using stored procedure, then why do not make a trigger for ReceivedGoodsDetail (on INSTERT)? IMHO everything will be much simpler with trigger.
Here are some remarks for your implementation.
Why do you open transaction before first if? If validation fails then your transaction will not be closed
manually.
I cant't see here using of 2 models, just one - ReceivedGoodsDetail, and StockInventory
as i can understand will be created in stored procedure usp_T_Received_Goods_Detail#InsertData?
Why do you redirect user to the item view even if transaction fails?
Using ActiveRecord. Then here is no need to start transaction manually. Just define what operations
you wish to be transactional for this model in transactions() method.
Using ActiveRecord. It is better practice to get db connection from your model class, not application.
It will be Yii::$app->db by default, but later you can easily change connection for this particular model.
It will be better for you (for example) to extend ActiveRecord (if not yet) and overload insertInternal() method
for ReceivedGoodsDetail.
In class ReceivedGoodsDetail:
public function transactions() {
return [
'default' => self::OP_INSERT
];
}
protected function insertInternal($attributeNames = null) {
if (!$this->beforeSave(true)) {
return false;
}
$values = $this->getDirtyAttributes($attributes);
/* overrided part of code */
$connection = static::getDb();
$command = $connection->createCommand('{call usp_T_Received_Goods_Detail#InsertData(:ID_Received_Goods,:ID_Item, :Qty, :User)}');
$ID_Received_Goods = $model->ID_Received_Goods;
$ID_Item = $model->ID_Item;
$Qty = $model->Qty;
$User = Yii::$app->user->identity->username;
$command->bindParam(":ID_Received_Goods",$ID_Received_Goods,PDO::PARAM_STR);
$command->bindParam(":ID_Item", $ID_Item, PDO::PARAM_STR);
$command->bindParam(":Qty", $Qty, PDO::PARAM_INT);
$command->bindParam(":User", $User, PDO::PARAM_STR);
if($command->execute() != 0) {
return false;
}
/* end of overrided part */
$changedAttributes = array_fill_keys(array_keys($values), null);
$this->setOldAttributes($values);
$this->afterSave(true, $changedAttributes);
return true;
}
In your action:
public function actionCreate($id) {
$model = new ReceivedGoodsDetail();
$model->ID_Received_Goods = $id;
if ($model->load(Yii::$app->request->post()) && $model->save(true)) {
return $this->redirect(['receivedgoodsheader/view', 'id' => $model->ID_Received_Goods]);
} else {
foreach ($model->getErrors() as $key => $message) {
Yii::$app->session->setFlash('error', $message);
}
return $this->render('create', [
'model' => $model,
]);
}
}
And then catch your flash messages on create form.
P.S. One more moment. It is strange practice to use path/to/model/{id} endpoint
with predefined ID to create new instance. Usually this looks like POST path/to/model. But this can be subject of your business logic, so i don't know if it can be improved or not
P.P.S. This example was not tested (obviously) so here can be some mistakes
Related
I am trying to update some users info. Here is my code :
public function update(Request $request, $id)
{
$user = Auth::user();
$user_id = Auth::user()->id;
$arrRequest = $request->all();
$contact = Contact::findOrFail($id)->where('user_id', $user_id);
$validator = Validator::make($arrRequest, Contact::$rules);
$content = null;
if ($validator->fails()) {
$status = 400;
$content = $validator->errors();
} else {
$contact->update($arrRequest)->save();
$status = 200;
}
return response($content, $status);
}
The main problem is that the request is applied to all rows in the table though I'm specifying the $id of the row for the request to be applied to. I'm struggling to see where is my mistake. The second problem (that just popped up) is that when I perform the request I'm now getting a message that says : Call to a member function save() on integer. But it was working just fine earlier (except it was updating all rows..) ! And Im retrieving an object ($contact) and not just an integer...
Thanks !
You are using findOrFail() method, which returns a Model or Collection.
After that you actually convert $contact into a Builder object by appending the where() method on the findOrFail() result. findOrFail() expects either an $id or array of $ids and will return a Model or Collection, not a Builder.
If you just want to make sure that the requested id is owned by the user, you can either check that after fetching the object, or use something other than findOrFail().
$contact = Contact::findOrFail($id);
if ($contact->user_id != $user->id) {
abort(403);
}
or
$contact = Contact::where('id', $id)
->where('user_id', $user->id)
->first();
if (! $contact) {
abort(404);
}
Although I would not recommend the last one, just the $id should be enough to fetch the item you want.
The second error is caused by calling save() after update(), update() will return a boolean.
I've finally managed to make it work.. some weirds things happening though.
Here is my solution :
public function update(Request $request, $id)
{
$user = Auth::user();
$user_id = Auth::user()->id;
$arrRequest = $request->all();
$contact = Contact::where('user_id', $user_id)->find($id);
$validator = Validator::make($arrRequest, Contact::$rules);
$content = null;
if ($validator->fails()) {
$status = 400;
$content = $validator->errors();
} else {
$contact->update($arrRequest);
$contact->save();
$status = 201;
}
return response($content, $status);
}
Thanks to Robert I can understand some parts of it. I guess if I make update() first then save() (not in a row), my save method is applying to the object and not the returned boolean? and thats why it works? still learning ahah !
my login function in users controller
public function login() {
if ($this->request->is('post')) {
$user = $this->Auth->identify();
// print_r($user);
// die();
if ($user['role'] === 'student') {
$this->Auth->setUser($user);
$session = $this->request->session();
$session->write('user', $user);
return $this->redirect(['controller' => 'Useracountinfo/addinfo']);
} elseif .....
and my add info function in the Useracountinfo
public function addinfo()
{
$this->loadModel('Users');
$userinfo= $this->Users->find('all');
$session = $this->request->session();
$userinfo = $session->read('user');
//print_r($userinfo);
//die();
$this->set($userinfo);
$user = $this->Useracountinfo->newEntity();
if ($this->request->is('post')) {
$user = $this->Useracountinfo->patchEntity($userinfo, $this->request->data);
print_r($user);
die();
if ($this->Useracountinfo->save($user)) {
$this->Flash->success(__('The user has been saved.'));
return $this->redirect(['action' => 'index']);
} else {
$this->Flash->error(__('The user could not be saved. Please, try again.'));
}
}
$this->set(compact('useracountinfo'));
$this->set('_serialize', ['useracountinfo']);
}`
and in my view i have something like this
<?php echo '<strong>'.$userinfo['email'].'</strong>'; ?>
the correct syntax is (see the manual)
$this->set('variable_name', somevalue);
so in your case
$this->set('userinfo', $userinfo);
mind that this is not a session variable but just a php variable that cake share from the controller to the view
if you want this to be available in every view you can do it in AppController
but you don't even need to do that because $this->request->session() is already always available in views too
This said I don't fully understand what you are trying to achieve so maybe there are better solutions
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
]);
}
this is a user.php controller
public function verifyLogin() {
if (isset($_POST["email"])) {
$e = $this->input->post("email");
$p = $this->input->post("pass");
$this->form_validation->set_rules("email", "email", "required|valid_email|xss_clean");
$this->form_validation->set_rules("pass", "password", "required|xss_clean");
if ($this->form_validation->run()) {
$data = array(
'select' => '*',
'table' => 'users',
'where' => "email = '$e' AND activated = '1'"
);
$checklogin = $this->query2->selectData($data);
if ($checklogin === FALSE) {
echo "quering userInfo fails. email is wrong or activation not done";
exit();
} else {
foreach ($checklogin as $row) {
$dbid = $row->id;
$dbusername = $row->username;
$dbpassword = $row->password;
$dbemail = $row->email;
if ($p === $dbpassword) {
$login_data = array(
'name' => $dbusername,
'email' => $dbemail,
'password' => $dbpassword,
'id' => $dbid,
'expire' => '86500',
'secure' => TRUE,
'logged_in' => TRUE
);
$this->input->set_cookie($login_data);
$this->session->set_userdata($login_data);
if ($this->session->userdata("logged_in")) {
$time = time();
$now = unix_to_human($time, TRUE, 'us');
$updateLogin = $this->query1->updateLogin($e, $now);
if ($updateLogin) {
echo "success";
} else {
echo 'update failed';
}
} else {
echo "session failed";
}
}else{
echo 'password incorrect';
}
}
}
} else {
echo "form validation fails";
}
} else {
$this->load->view('header');
$this->load->view('login');
$this->load->view('modal');
$this->load->view('footer');
}
}
this is model.php
public function selectData($data){
if(isset($data['direction'])){
$dir = $data['direction'];
}else{
$dir = "ASC";
}
if(isset($data['offset'])){
$off = $data['offset'];
}else{
$off = '0';
}
if(isset($data['select']) && isset($data['table'])){
$this->db->select($data['select'])->from($data['table']);
}
if(isset($data['where'])){
$this->db->where($data['where']);
}
if(isset($data['order_by_name'])){
$this->db->order_by($data['order_by_name'], $dir);
}
if(isset($data['limit'])){
$this->db->limit($data['limit'], $off);
}
$query = $this->db->get();
if($query){
$d = $query->result();
return $d;
}else{
return FALSE;
}
}
is this a good way of quering database?
i am new to mvc and i am reading everywhere about "fat models and this controllers"
what can be done to make it a good mvc architecture?
its only acceptable to echo out from the controller when you are developing:
if ($checklogin === FALSE) {
echo "quering userInfo fails.
if checking login is false then either show a view or go to a new method like
if ($checklogin === FALSE) {
$this->showLoginFailed($errorMessage) ;
the check login code in the controller is a great example of something that could be refactored to a model. then if you need to check login from another controller its much easier. putting the form validation code in a model would be another choice. often times when you are validating form code you are also inserting/updating to a database table -- so having all those details together in a model can make things easier long term.
"fat model" does not mean one method in a model that does a hundred things. it means the controller says -- did this customer form validate and insert to the database? yes or no? 3 lines of code.
the model has the code that is looking into the "fat" details of the form, validation, database, etc etc. say 50 or more lines compared to the 3 in the controller. but the methods in the model should still be clean: small and specific.
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