I just learned how to create a complex form using multiple models.
public function actionCreate()
{
$model = new Company();
$contact = new Contact();
$address = new Address();
$company_contact = new CompanyContact();
$company_address = new CompanyAddress();
if ($model->load(Yii::$app->request->post()) && $contact->load(Yii::$app->request->post()) && $address->load(Yii::$app->request->post())) {
$model->save();
$address->save();
$contact->save();
// we need to insert the index from each key to the table Company_Contact to associate them
$company_contact->id_company = $model->id_company;
$company_contact->id_contact = $contact->id_contact;
// same procedure for Company_Address
$company_address->id_company = $model->id_company;
$company_address->id_address = $address->id_address;
$company_address->save();
$company_contact->save();
return $this->redirect(['index']);
} else {
return $this->render('create', [
'model' => $model,
'contact' => $contact,
'address' => $address
]);
}
}
The problem now is that I don't know how to call back every table data so i can populate my form and afterwards save the changes. I had the idea of using JOIN, but I don't have necessery knoledge to make this work on yii2 framework.
First of all, you need to make sure the methods declaring the relationship Company has to both Contact and Address are correct.
public function getContact() {
return $this->hasOne(Contact::className(), ['id_contact' => 'id_contact'])
->viaTable('Company_Contact', ['id_company' => 'id_company']);
}
public function getAddress() {
return $this->hasOne(Address::className(), ['id_address' => 'id_address'])
->viaTable('Company_Address', ['id_company' => 'id_company']);
}
Now that we know our relationships are correct, we can make some modifications to actionCreate() inside our controller:
public function actionCreate() {
$model = new Company();
$contact = new Contact();
$address = new Address();
// Check if the request was made using post, otherwise skip and render 'create' view
if(Yii::$app->request->isPost) {
// Begin a transaction, so we only make changes to the Database when we can save all the needed records.
$transaction = Company::getDb()->beginTransaction();
try {
$post = Yii::$app->request->post();
// We try to load $model, $contact and $address. If we can't then we throw an Exception that will be caught.
if(!($model->load(Yii::$app->request->post()) && $contact->load(Yii::$app->request->post()) && $address->load(Yii::$app->request->post()))) {
throw new \Exception('Could not load post data to models');
}
// Now we try to save them, each by itself. If any of them fail to save then we throw an Exception.
if(!$model->save()) {
throw new \Exception('Could not save $model');
}
if(!$address->save()) {
throw new \Exception('Could not save $address');
}
if(!$contact->save()) {
throw new \Exception('Could not save $contact');
}
// Now we populate the relationships.
// First parametter is the name of the relationship, Second is the model we want to link to.
$model->link('address', $address);
$model->link('contact', $contact);
// With the relationships correctly declared, we don't need to populate the juncture table ourselves, just link both models.
// If the 'link' method cannot link the models, then it will throw an Exception that will be caught.
// If we managed to save all the records and link them, now we commit the transaction so the changes made in the database are not reverted.
$transaction->commit();
return $this->redirect(['index']);
}
catch(\Exception $e) {
// If there are any problems then we will do a rollBack to the transaction, reverting the changes made during the transaction.
$transaction->rollBack();
}
}
return $this->render('create', [
'model' => $model,
'contact' => $contact,
'address' => $address,
]);
}
And now for actionUpdate we just need to get $id which will be used as PK to search for the Company.
public function actionUpdate(&id) {
$model = Company::findOne($id);
// If $model is null, then throw a NotFoundHttpException.
if($model === null) {
throw new \yii\web\NotFoundHttpException('The requested page does not exist.');
}
// We can get the $contact and $address models by using the relationships we already declared.
$contact = $model->contact;
$address = $model->address;
// Now we don't need to change much from actionCreate,
// except we don't need to link $model with $contact or $address because they are already linked,
// we just need to save changes made to them.
if(Yii::$app->request->isPost) {
$transaction = Company::getDb()->beginTransaction();
try {
$post = Yii::$app->request->post();
if(!($model->load(Yii::$app->request->post()) && $contact->load(Yii::$app->request->post()) && $address->load(Yii::$app->request->post()))) {
throw new \Exception('Could not load post data to models');
}
if(!$model->save()) {
throw new \Exception('Could not save $model');
}
if(!$address->save()) {
throw new \Exception('Could not save $address');
}
if(!$contact->save()) {
throw new \Exception('Could not save $contact');
}
$transaction->commit();
return $this->redirect(['index']);
}
catch(\Exception $e) {
$transaction->rollBack();
}
}
return $this->render('update', [
'model' => $model,
'contact' => $contact,
'address' => $address,
]);
}
Related
I am new at PHP. We are creating REST API in Phalcon and I've created a put request. It already works, but I would like to check if update has really happened before sending a success response. So I've created a conditional for that ( if (!$product->update()) ), but it always returns 'true'. How can I check if any field has changed in a record?
public function put()
{
$id = $this->getParam('id');
$input = $this->getRawData();
$product = Product::findFirst([
'conditions' => 'id = :id:',
'bind' => ['id' => $id]
]);
if ($product === null){
throw new NotFoundException();
}
$product->assign($input);
$product->update();
if (!$product->update()) {
$this->errorResponse($product->getMessages());
} else {
$this->successResponse($product->toArray($product->update()));
}
}
You can use Model Events, i.e. afterUpdate and notSaved, like:
use Phalcon\Mvc\Model;
use Phalcon\Http\Response;
class ModelBase extends Model
{
public function afterUpdate()
{
$response = new Response();
$response->setJsonContent([
'success' => true,
'message' => "Record updated"
])->send();
}
public function notSaved()
{
$response = new Response();
$response->setJsonContent([
'success' => false,
'message' => 'Record not saved'
])->send();
}
}
The Product and all other models will extend ModelBase. Then your code could be:
public function put()
{
$id = $this->getParam('id');
$input = $this->getRawData();
$product = Product::findFirst([
'conditions' => 'id = :id:',
'bind' => ['id' => $id]
]);
if ($product === null){
throw new NotFoundException();
}
$product->assign($input);
$product->update();
}
And Phalcon event will respond if the model was updated or not. If you prefer, you can also use custom http response codes for update or notSaved. More information about Model Events in the documentation
You are calling $product->update() three times. You do it once after the assign, then again for your if test, which is why it's always returning TRUE there I believe, and once inside the toArray() which may not actually return anything since the second and third updates don't have any data to update (not sure about that though).
I would code this as follows:
$product->assign($input);
$results = $product->update();
if (!results) {
$this->errorResponse($product->getMessages());
} else {
$this->successResponse($results->toArray());
}
I am assuming that the $product->assign($input); statement is working as expected to update the $product data for you. I don't use that. I prefer to do direct assignments for updates so nothing is left to chance, ie. $product->whatever = $input['whatever'];.
Give this a try and hopefully it will work as expected for you.
Theres the action
public function actionEfetuarPedidoReserva($idQuarto){
$modelPedidoReservaQuarto = new PedidoReservaQuarto();
$modelPedidoReserva = new PedidoReserva();
$model = new ComodidadesExtra();
$modelListaComodidades = new ListaComodidadesQuarto();
$modelPedidoReservaQuarto->quartoId = $idQuarto;
if($modelPedidoReservaQuarto->save()){
$modelPedidoReserva->nPessoas = 2;
$modelPedidoReserva->preco = 70.00;
$modelPedidoReserva->reservaQuartoId = $modelPedidoReservaQuarto->id;
$modelPedidoReserva->userInfoId = Yii::$app->user->id;
if($modelPedidoReserva->save()){
$model->pedidoReservaId = $modelPedidoReserva->id;
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['efetuar-pedido-reserva', 'idQuarto' => $idQuarto]);
}
return $this->render('../comodidades-extra/create', [
'model' => $model, 'modelLista' => $modelListaComodidades
]);
}
}
}
When i refresh page, it add the last pedidoreservaid inserted + 1;
i Would like to know if am i doing it the right way.
Your problem is that you create new instance of ComodidadesExtra each time when you call $model = new ComodidadesExtra();. Yii2 consider it as new independent row and you need to tell Yii2 that you should take existing row instead, using:
$model = ComodidadesExtra::find()
->where(['pedidoReservaId' => $modelPedidoReserva->id])
->one();
And your controller should look like:
if($modelPedidoReserva->save()){
if(null===($model = ComodidadesExtra::find()->where(['pedidoReservaId' => $modelPedidoReserva->id])
->one())) {
$model = new ComodidadesExtra();
}
$model->pedidoReservaId = $modelPedidoReserva->id;
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['efetuar-pedido-reserva', 'idQuarto' => $idQuarto]);
}
return $this->render('../comodidades-extra/create', [
'model' => $model, 'modelLista' => $modelListaComodidades
]);
}
I'm completely lost as to why this is happening, and it happens about 50% of the time.
I have a check to see if a user exists by email and last name, and if they do, run some code. If the user doesn't exist, then create the user, and then run some code.
I've done various testing with dummy data, and even if a user doesn't exist, it first creates them, but then runs the code in the "if" block.
Here's what I have.
if (User::existsByEmailAndLastName($params->email, $params->lastName)) {
var_dump('user already exists');
} else {
User::createNew($params);
var_dump("Creating a new user...");
}
And here are the respective methods:
public static function existsByEmailAndLastName($email, $lastName) {
return User::find()->where([
'email' => $email,
])->andWhere([
'last_name' => $lastName
])->one();
}
public static function createNew($params) {
$user = new User;
$user->first_name = $params->firstName;
$user->last_name = $params->lastName;
$user->email = $params->email;
$user->address = $params->address;
$user->address_2 = $params->address_2;
$user->city = $params->city;
$user->province = $params->province;
$user->country = $params->country;
$user->phone = $params->phone;
$user->postal_code = $params->postal_code;
return $user->insert();
}
I've tried flushing the cache. I've tried it with raw SQL queries using Yii::$app->db->createCommand(), but nothing seems to be working. I'm totally stumped.
Does anyone know why it would first create the user, and then do the check in the if statement?
Editing with controller code:
public function actionComplete()
{
if (Yii::$app->basket->isEmpty()) {
return $this->redirect('basket', 302);
}
$guest = Yii::$app->request->get('guest');
$params = new CompletePaymentForm;
$post = Yii::$app->request->post();
if ($this->userInfo || $guest) {
if ($params->load($post) && $params->validate()) {
if (!User::isEmailValid($params->email)) {
throw new UserException('Please provide a valid email.');
}
if (!User::existsByEmailAndLastName($params->email, $params->lastName)) {
User::createNew($params);
echo "creating new user";
} else {
echo "user already exists";
}
}
return $this->render('complete', [
'model' => $completeDonationForm
]);
}
return $this->render('complete-login-or-guest');
}
Here's the answer after multiple tries:
Passing an 'ajaxParam' parameters with the ActiveForm widget to define the name of the GET parameter that will be sent if the request is an ajax request. I named my parameter "ajax".
Here's what the beginning of the ActiveForm looks like:
$form = ActiveForm::begin([
'id' => 'complete-form',
'ajaxParam' => 'ajax'
])
And then I added this check in my controller:
if (Yii::$app->request->get('ajax') || Yii::$app->request->isAjax) {
return false;
}
It was an ajax issue, so thanks a bunch to Yupik for pointing me towards it (accepting his answer since it lead me here).
You can put validation like below in your model:
public function rules() { return [ [['email'], 'functionName'], [['lastname'], 'functionforlastName'], ];}
public function functionName($attribute, $params) {
$usercheck=User::find()->where(['email' => $email])->one();
if($usercheck)
{
$this->addError($attribute, 'Email already exists!');
}
}
and create/apply same function for lastname.
put in form fields email and lastname => ['enableAjaxValidation' => true]
In Create function in controller
use yii\web\Response;
if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) {
Yii::$app->response->format = Response::FORMAT_JSON;
return ActiveForm::validate($model);
}
else if ($model->load(Yii::$app->request->post()))
{
//place your code here
}
Add 'enableAjaxValidation' => false to your ActiveForm params in view. It happens because yii sends request to your action to validate this model, but it's not handled before your if statement.
I have table which have multiple reference to ohter tables like
user
id name email
categories
id title
user_categories
user_id category_id
Here a user will have multiple category associated with him/her
I am able to save these successfully with new records like following
View File:
echo $form->field($package_categories, 'category_id')->dropDownList( ArrayHelper::map(
StudyMaterialCategories::find()->all(), 'id', 'title'),
['multiple' => true]
);
Save New record:
$model = new Packages();
$package_categories = new PackageCategories();
$request = Yii::$app->request;
if ($request->isPost) {
$transaction = Yii::$app->db->beginTransaction();
try {
$post = $request->post();
$model->load($post);
$model->save();
foreach ($post['PackageCategories']['category_id'] as $key => $value) {
$package_categories = new PackageCategories();
$package_categories->category_id = $value;
$package_categories->package_id = $model->id;
$package_categories->save();
}
$transaction->commit();
return $this->redirect(['view', 'id' => $model->id]);
} catch (Exception $ex) {
$transaction->rolback();
Yii::$app->session->setFlash("error", $ex->getMessage());
}
}
Till now It's running successfully.
But I'm stuck when going to update the table. The problem part is dropdown list. How to set multiple selected option as per database if I'm coming with array of object.
Have a look on the following code
$package_categories = PackageCategories::find()
->where('package_id=:package_id', ['package_id' => $id])->all();
if (count($package_categories) < 1) {
$package_categories = new PackageCategories();
}
$request = Yii::$app->request;
if ($request->isPost) {
$transaction = Yii::$app->db->beginTransaction();
try {
$post = $request->post();
$model->load($post);
$model->save();
$package_categories = new PackageCategories();
$package_categories->deleteAll(
"package_id=:package_id",
[':package_id' => $model->id]
);
foreach ($post['PackageCategories']['category_id'] as $key => $value) {
$package_categories = new PackageCategories();
$package_categories->category_id = $value;
$package_categories->package_id = $model->id;
$package_categories->save();
}
$transaction->commit();
return $this->redirect(['view', 'id' => $model->id]);
} catch (Exception $ex) {
$transaction->rolback();
Yii::$app->session->setFlash("error", $ex->getMessage());
}
}
if I try to get first object of the array $package_categories of only able to set selected one option
This is an example code of a model class Permit which has a many to many relationship with Activity through PermitActivity (pivot table model).
Model Class Activity
public class Permit extends \yii\db\ActiveRecord {
public $activities_ids;
...
public function rules() {
return [
...
[['activities_ids'], 'safe'],
...
];
}
...
// Method called after record is saved, be it insert or update.
public function afterSave($insert, $changedAttributes) {
// If this is not a new record, unlink all records related through relationship 'activities'
if(!$this->isNewRecord) {
// We unlink all related records from the 'activities' relationship.
$this->unlinkAll('activities', true);
// NOTE: because this is a many to many relationship, we send 'true' as second parameter
// so the records in the pivot table are deleted. However on a one to many relationship
// if we send true, this method will delete the records on the related table. Because of this,
// send false on one to many relationships if you don't want the related records deleted.
}
foreach($this->activities_ids as $activity_id) {
// Find and link every model from the array of ids we got from the user.
$activity = Activity::findOne($activity_id);
$this->link('activities', $activity);
}
parent::afterSave($insert, $changedAttributes);
}
...
// Declare relationship with Activity through the pivot table permitActivity
public function getActivities(){
return $this->hasMany(Activitiy::className(), ['id' => 'activity_id'])
->viaTable('permitActivity',['permit_id' => 'id']);
}
...
public function afterFind(){
parent::afterFind();
$this->activities_id = ArrayHelper::getColumn($this->activities, 'id');
}
}
This way the model class is the one responsible for creating and updating the relationship using the pivot table.
The most important thing is to have the relationship method declared correctly.
Edit
This is an example of the view using kartikv\widgets\Select2. I don't really know if dropDownList supports multiple select, however Select2 has so many useful features i usually use it over other options.
echo $form->field($model, 'activities')->widget(Select2::classname(), [
'data' => $data,
'options' => [
'placeholder' => '...'
],
'pluginOptions' => [
'allowClear' => true,
'multiple' => true,
],
]);
I have file (ProfileController.php) which contains the following code:
public function editAction() {
if (Zend_Auth::getInstance()->hasIdentity()) {
try {
$form = new Application_Form_NewStory();
$request = $this->getRequest();
$story = new Application_Model_DbTable_Story();
$result = $story->find($request->getParam('id'));
// $values = array(
// 'names' => $result->names,
// 'password' => $result->password,
// );
if ($this->getRequest()->isPost()) {
if ($form->isValid($request->getPost())) {
$data = array(
'names' => $form->getValue("names"),
'password' => $form->getValue("password"),
);
$form->populate($data->toArray());
$where = array(
'id' => $request->getParam('id'),
);
$story->update($data, $where);
}
}
$this->view->form = $form;
$this->view->titleS= $result->title;
$this->view->storyS= $result->story;
} catch (Exception $e) {
echo $e;
}
} else {
$this->_helper->redirector->goToRoute(array(
'controller' => 'auth',
'action' => 'index'
));
}
}
and another file (edit.phtml) with following code:
<?php
try
{
$tmp = $this->form->setAction($this->url());
//$tmp->titleS=$this->title;
//$tmp->storyS=$this->story;
//echo $tmp->title = "aaaaa";
}
catch(Exception $e)
{
echo $e;
}
?>
I would like the users to be able to edit their Username and password. How do I go about it?
First: move the Zend_Auth stuff up to init() or preDispatch(), that way Auth will run against any or all actions in the controller.
The trick in getting more then one submit button to work is to give the buttons different names so that getParam('') has something to work with.
Normally I only do this sort of thing when doing deletes, for edit's or update's I just submit the whole array back to the database. I typically use the Zend_Db_Table_Row save() method instead of Zend_Db_Table's insert() or update() so the mechanism is a little different.
I just use a simple form to perform an update, here is the controller code (the view just echo's the form):
//update album information
public function updatealbumAction()
{ //get page number from session
$session = new Zend_Session_Namespace('page');
//get album id
$id = $this->getRequest()->getParam('id');
$model = new Music_Model_Mapper_Album();
//fetch the album database record
$album = $model->findById($id);
$form = new Admin_Form_Album();
//this form is used elsewhere so set the form action to this action
$form->setAction('/admin/music/updatealbum/');
if ($this->getRequest()->isPost()) {
if ($form->isValid($this->getRequest()->getPost())) {
$data = $form->getValues();//get valid and filtered form values
$newAlbum = new Music_Model_Album($data);//create new entity object
$update = $model->saveAlbum($newAlbum);//save/update album info
$this->message->addMessage("Update of Album '$update->name' complete!");//generate flash message
$this->getHelper('Redirector')->gotoSimple('update', null, null, array('page' => $session->page));//redirect back to the page the request came from
}
} else {
$form->populate($album->toArray());
$this->view->form = $form;
}
}
This is a pretty common update action.
Now here is how you might use different request parameters to perform an action on a record. I use this to delete database records but anything is possible.
public function deleteAction()
{
$session = new Zend_Session_Namespace('page');
$request = $this->getRequest()->getParams();
try {
switch ($request) {
//if
case isset($request['trackId']):
$id = $request['trackId'];
$model = new Music_Model_Mapper_Track();
$model->deleteTrack($id);
$this->message->addMessage("Track Deleted!");
break;
case isset($request['albumId']):
$id = $request['albumId'];
$model = new Music_Model_Mapper_Album();
$model->deletealbum($id);
$this->message->addMessage("Album Deleted!");
break;
case isset($request['artistId']):
$id = $request['artistId'];
$model = new Music_Model_Mapper_Artist();
$model->deleteArtist($id);
$this->message->addMessage("Artist Deleted!");
break;
default:
break;
}
$this->getHelper('Redirector')->gotoSimple('update', null, null, array('page' => $session->page));
} catch (Exception $e) {
$this->message->addMessage($e->getMessage());
$this->getHelper('Redirector')->gotoSimple('update', null, null, array('page' => $session->page));
}
}
you can pass the request parameters as submit button labels or as urls or whatever works for you.
Good Luck!