New record and many to many insert - mysql

I am working in a project where the users have a rate plan associated. When a new user is created, a valid rate plan must be specified.
I have the following MySQL schema and Eloquent models:
/**
* User Eloquent model file ...
*
*/
public function ratePlans() {
return $this->belongsToMany(
'App\Models\RatePlan',
'users_rate_plans',
'users_id',
'rate_plans_id'
);
}
So, to create a new user with your selected rate plan i do:
try {
\DB::beginTransaction();
$model->create($data);
$model->ratePlans()->attach($data['rate_plan'], ['active' => 1]);
\DB::commit();
return $model;
} catch(\Exception $e) {
\DB::rollback();
return false;
}
But, i am getting the next exception:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column
'users_id' cannot be null (SQL: insert into users_rate_plans
(active, rate_plans_id, users_id) values (1, 43, ))
Why te transaction didn't work ? How i can do that task ?
UPDATE 1
I changed the transaction code but, the result is the same.
try {
\DB::beginTransaction();
$ratePlan = \App\Models\RatePlan::find($data['rate_plan']);
$user->ratePlans()->attach($ratePlan, ['active' => 1]);
$user->create($data);
\DB::commit();
return $user;
} catch(\Exception $e) {
\DB::rollback();
die($e->getMessage());
return false;
}
UPDATE 2
I changed the transaction code again and its works:
\DB::beginTransaction();
$user = \App\Models\User::create($data);
$ratePlan = \App\Models\RatePlan::find($data['rate_plan']);
$user->ratePlans()->attach($ratePlan, ['active' => 1]); \DB::commit();
return $user;

I think you're not loading the rate_plans entity.
$model->ratePlans()->attach($data['rate_plan'], ['active' => 1]);
In this line $data['rate_plan'] shoud be an instance of your model "rate_plans", and I'm assuming that $model in this situation stand for an entity of your User model
Also have tried a test without the transaction if the result's is the same ?
I give you an example of one of my code which is similar:
try {
DB::beginTransaction();
$group = Group::create($data);
$employees = User::whereIn('reference', $references)->get();
$group->employees()->attach($employees);
$group->save();
DB::commit();
} catch(Exception $e) { [...] }
Good luck

Related

how to add data from Employee table to user table in YII2 advanced

I am working on my collage project i.e. Employee Management. I have Employee table in sql(crud is also generated from gii). only Admin is having rights to create Employee (there is no Signup).
My Problem: when I am creating employee then I am not able to save data in user table also, please help me to save data in both Employee and user table.
Thanks in advance
Update:
Below is the code:
public function actionCreate() {
$model1=new Employee;
$model2=new User;
if(isset($_POST['Employee']) && isset($_POST['User']))
{
$model1->attributes=$_POST['Emoloyee'];
$model2->attributes=$_POST['User'];
$model1->save();
$model2->save();
echo 'data is saved in both tables';
}
$this->render('create',array('model1'=>$model1,model2'=>$mod‌​‌​el2));
}
could be you have some validation problem
try check this way
......
$model1->attributes=$_POST['Emoloyee'];
$model2->attributes=$_POST['User'];
if ($model1->validate() && $model2->validate() ) {
$model1->save();
$model2->save();
} else {
$errors1 = $model1->errors;
$errors2 = $model2->errors;
var_dump($errors1);
var_dump($errors2);
exit();
}
then just for debug try using
$model1->attributes=$_POST['Emoloyee'];
$model2->attributes=$_POST['User'];
$model1->save(false);
$model2->save(false);
and check in db if the value are saved ..
You can try this example,
public function actionCreate()
{
$model = new Employee();
$user = new User();
if ($model->load(Yii::$app->request->post()) && $user->load(Yii::$app->request->post())) {
if($model->save() && $user->save()) {
Yii::$app->session->setFlash('success', 'Record saved successfully.');
} else {
//var_dump($model->getErrors());
//var_dump($user->getErrors());
Yii::$app->session->setFlash('error', 'Record not saved.');
}
return $this->redirect(['index']);
} else {
var_dump($model->getErrors());
var_dump($user->getErrors());
die();
}
return $this->render('create', [
'model' => $model,
'user' => $user,
]);
}
Follow the instruction given in below link . This should work
how to insert data to 2 tables i.e Employee and User(migrated) from single form(Employee Create) and controller in yii2

Base Table not found on unique value validation in MongoDB with laravel

I'm using laravel 5.3 with jenssegers/laravel-mongodb package for managing mongodb connections.
I want to check every time a user send a request to register a website in my controller if it's unique then let the user to register his/her website domain.
I wrote below code for validation but What I get in result is :
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'iranad.seat' doesn't exist (SQL: select count(*) as aggregate from `seat` where `domain` = order.org)
my controller code :
public function store(Request $request) {
$seat = new Seat();
$validator = Validator::make($request->all(), [
'domain' => 'required|regex:/^([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/|unique:seat', //validating user is entering correct url like : iranad.ir
'category' => 'required',
]);
if ($validator->fails()) {
return response()->json($validator->messages(), 400);
} else {
try {
$statusCode = 200;
$seat->user_id = Auth::user()->id;
$seat->url = $request->input('domain');
$seat->cats = $request->input('category');
$seat->filter = [];
if($request->input('category') == 'all') {
$obj['cats'] = 'false';
$seat->target = $obj;
} else {
$obj['cats'] = 'true';
$seat->target = $obj;
}
$seat->status = 'Waiting';
$seat->save();
} catch (\Exception $e) {
$statusCode = 400;
} finally {
$response = \Response::json($seat, $statusCode);
return $response;
}
}
}
My Seat Model :
namespace App;
use Moloquent;
use Carbon\Carbon;
class Seat extends Moloquent {
public function getCreatedAtAttribute($value) {
return Carbon::createFromTimestamp(strtotime($value))
->timezone('Asia/Tehran')
->toDateTimeString();
}
}
Obviously The validator is checking if domain is unique in mysql tables which causes this error, How can I change my validation process to check mongodb instead of mysql ?
I solved the problem, The solution is that you should add Moloquent to your model and define database connection :
namespace App\Models;
use Moloquent;
use Carbon\Carbon;
class Seat extends Moloquent
{
protected $collection = 'seat';
protected $connection = 'mongodb';
}

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
]);
}

Total rollback on nested transactions

I really like this NestedPDO solution for Yii but I need some different transaction handling.
I want to commit my nested transactions only if all nested transactions could be commited and if ONE transaction does a rollback all transactions should be rolled back.
How can I do that?
My try of changing the rollBack function which didn't work:
public function rollBack() {
$this->transLevel--;
if($this->transLevel == 0 || !$this->nestable()) {
parent::rollBack();
} else {
$level = $this->transLevel;
for($level; $level>1; $level--){
$this->exec("ROLLBACK TO SAVEPOINT LEVEL{$this->constantlevel}");
}
//parent::rollBack();
}
}
I was thinking of adapting the NestedPDO: In function commit() do a commit only on the outermost transaction and in function rollBack() do a rollback to the outermost transaction no matter which sub transaction caused the rollback. But I could not get it done...
I'm using MySQL and InnoDB tables and I'm not sure about autocommit but when echoing the value of autocommit within a transaction I always get the value 1 which should mean autocommit is on but within a transaction autocommit should be set to 0. I'm not sure whether this is the cause why a whole rollback does not work for me?
If you want the whole transaction be rolled back automatically as soon as an error occurs, you could just re-throw the exception from B's exception handler when called from some specific locations (eg. from A()):
function A(){
...
$this->B(true);
...
}
/*
* #param B boolean Throw an exception if the transaction is rolled back
*/
function B($rethrow) {
$transaction=Yii::app()->db->beginTransaction();
try {
//do something
$transaction->commit();
} catch(Exception $e) {
$transaction->rollBack();
if ($rethrow) throw $e;
}
}
Now I understand you actually just want your wrapper to detect if a transaction is already in progress, and in this case not start the transaction.
Therefore you do not really need the NestedPDO class. You could create a class like this instead:
class SingleTransactionManager extends PDO {
private $nestingDepth = 0;
public function beginTransaction() {
if(!$this->nestingDepth++ == 0) {
parent::beginTransaction();
} // else do nothing
}
public function commit() {
$this->nestingDepth--;
if (--$this->nestingDepth == 0) {
parent::commit();
} // else do nothing
}
public function rollback() {
parent::rollback();
if (--$this->nestingDepth > 0) {
$this->nestingDepth = 0;
throw new Exception(); // so as to interrupt outer the transaction ASAP, which has become pointless
}
}
}
Based on the answer of #RandomSeed I've created a 'drop in' for default Yii transaction handling:
$connection = Yii::app()->db;
$transaction=$connection->beginTransaction();
try
{
$connection->createCommand($sql1)->execute();
$connection->createCommand($sql2)->execute();
//.... other SQL executions
$transaction->commit();
}
catch(Exception $e)
{
$transaction->rollback();
}
This is my SingleTransactionManager class:
class SingleTransactionManager extends CComponent
{
// The current transaction level.
private $transLevel = 0;
// The CDbConnection object that should be wrapped
public $dbConnection;
public function init()
{
if($this->dbConnection===null)
throw new Exception('Property `dbConnection` must be set.');
$this->dbConnection=$this->evaluateExpression($this->dbConnection);
}
// We only start a transaction if we're the first doing so
public function beginTransaction() {
if($this->transLevel == 0) {
$transaction = parent::beginTransaction();
} else {
$transaction = new SingleTransactionManager_Transaction($this->dbConnection, false);
}
// always increase transaction level:
$this->transLevel++;
return $transaction;
}
public function __call($name, $parameters)
{
return call_user_func_array(array($this->dbConnection, $name), $parameters);
}
}
class SingleTransactionManager_Transaction extends CDbTransaction
{
// boolean, whether this instance 'really' started the transaction
private $_startedTransaction;
public function __construct(CDbConnection $connection, $startedTransaction = false)
{
$this->_startedTransaction = $startedTransaction;
parent::__construct($connection);
$this->setActive($startedTransaction);
}
// We only commit a transaction if we've started the transaction
public function commit() {
if($this->_startedTransaction)
parent::commit();
}
// We only rollback a transaction if we've started the transaction
// else throw an Exception to revert parent transactions/take adquate action
public function rollback() {
if($this->_startedTransaction)
parent::rollback();
else
throw new Exception('Child transaction rolled back!');
}
}
This class 'wraps' the main database connection, you should declare it as component like this in your config:
'components'=>array(
// database
'db'=>array(
'class' => 'CDbConnection',
// using mysql
'connectionString'=>'....',
'username'=>'...',
'password'=>'....',
),
// database
'singleTransaction'=>array(
'class' => 'pathToComponents.db.SingleTransactionManager',
'dbConnection' => 'Yii::app()->db'
)
Note that the dbConnection property should be an expression to the master database connection.
Now, when nesting transactions in nested try catch blocks, you can create an error in for example nested transaction 3, and the ones on 1 and 2 are rolled back also.
Test code:
$connection = Yii::app()->singleTransaction;
$connection->createCommand('CREATE TABLE IF NOT EXISTS `test_transactions` (
`number` int(10) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;')->execute();
$connection->createCommand('TRUNCATE TABLE `test_transactions`;')->execute();
testNesting(4, 3, 1);
echo '<br>';
echo 'Rows:';
echo '<br>';
$rows = $connection->createCommand('SELECT * FROM `test_transactions`')->queryAll();
if($rows)
{
foreach($rows as $row)
{
print_r($row);
}
}
else
echo 'Table is empty!';
function testNesting(int $total, int $createErrorIn = null, int $current = 1)
{
if($current>=$total)
return;
$connection = Yii::app()->singleTransaction;
$indent = str_repeat(' ', ($current*4));
echo $indent.'Transaction '.$current;
echo '<br>';
$transaction=$connection->beginTransaction();
try
{
// create nonexisting columnname when we need to create an error in this nested transaction
$columnname = 'number'.($createErrorIn===$current ? 'rr' : '');
$connection->createCommand('INSERT INTO `test_transactions` (`'.$columnname.'`) VALUES ('.$current.')')->execute();
testNesting($total, $createErrorIn, ($current+1));
$transaction->commit();
}
catch(Exception $e)
{
echo $indent.'Exception';
echo '<br>';
echo $indent.$e->getMessage();
echo '<br>';
$transaction->rollback();
}
}
Results in the following output:
Transaction 1
Transaction 2
Transaction 3
Exception
CDbCommand failed to execute the SQL statement: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'numberrr' in 'field list'. The SQL statement executed was: INSERT INTO `test_transactions` (`numberrr`) VALUES (3)
Exception
Child transaction rolled back!
Exception
Child transaction rolled back!
Rows:
Table is empty!
IMHO, the idea of simulating "nested transactions" in application code is an anti-pattern. There are numerous anomaly cases that are impossible to solve in the application (see my answer to https://stackoverflow.com/a/319939/20860).
In PHP, it's better to keep it simple. Work is organized naturally into requests, so use the request as the transaction scope.
Start a transaction at the controller level, before you call any model classes.
Let models throw exceptions if anything goes wrong.
Catch the exception at the controller level, and rollback if necessary.
Commit if no exception is caught.
Forget about all the nonsense about transaction levels. Models should not be starting, committing, or rolling back any transactions.

Zend framework complains that "There is already an active transaction"

we are using zend_db_table and we are having some issues since Zend Framework is complaining about two transactions being active:
[message:protected] => There is already an active transaction
[string:Exception:private] =>
[code:protected] => 0
[file:protected] => /var/www/vhosts/test.local/private/library/Zend/Db/Adapter/Pdo/Abstract.php
[line:protected] => 305
[trace:Exception:private] => Array
This is the code in the Controller:
public function convertAction()
{
$this->setNoRender();
// If the quote is a copy of a previous one, fetch all the datas
$quoteId = Zend_Filter::filterStatic($this->getRequest()->getParam('qte_id'), 'int');
$quoteTable = new Model_QuotesTable();
$quoteRow = $quoteTable->findById($quoteId);
if (count($quoteRow)) {
$clonedId = $quoteRow->convertToJob();
$this->flashMessageRedirect('Quotation successfully converted', '/jobs/edit/job_id/' . $clonedId);
} else {
$this->flashMessageRedirect('Unable to find the quote to be converted', '/quotes');
}
}
which is recalling this function in QuotesTableRow which extends zend_db_table_abstract:
public function convertToJob()
{
$db = $this->_getTable()->getAdapter();
$db->beginTransaction();
$jobsTable = new Model_JobsTable();
try {
/*
* Update the status of the old row to match the $status passed into this function
*/
$this->qte_status = "Accepted";
$this->save();
/*
* Create new row with the same details as above
*/
$newRow = $jobsTable->createRow();
$newRow->job_title = $this->qte_title;
$newRow->job_cus_id = $this->qte_cus_id;
$newRow->job_enq_id = $this->qte_enq_id;
$newRow->job_qte_id = $this->qte_id;
$newRow->job_title = $this->qte_title;
$newRow->job_description = $this->qte_description;
$newRow->job_work_location_id = $this->qte_work_location_id;
$newRow->job_work_category_id = $this->qte_work_category_id;
$newRow->job_work_type_id = $this->qte_work_type_id;
$newRow->job_cus_code = $this->qte_cus_code;
$newRow->job_cus_name = $this->qte_cus_name;
$newRow->job_wt_ref_code = $this->qte_wt_ref_code;
$newRow->job_wt_description = $this->qte_wt_description;
$newRow->job_wl_code = $this->qte_wl_code;
$newRow->job_wl_description = $this->qte_wl_description;
$newRow->job_wc_ref_code = $this->qte_wc_ref_code;
$newRow->job_wc_description = $this->qte_wc_description;
$newRow->job_qte_title = $this->qte_title;
$newRow->job_datetime_created = date('Y-m-d H:i:s');
$newRowId = $newRow->save();
$db->commit();
return $newRowId;
}
catch (Exception $e) {
$db->rollback();
echo('<pre>');
print_r($e);
echo('</pre>');
exit();
throw new Exception($e->getMessage());
return false;
}
}
in addition, it seems to be related to the model we are not in since if we comment the row with the save() function related to the Model_JobsTable() the script is working, while it returns the same error when we comment the other save().
This error is being returned from MySQL and ZF is only telling you the error message.
Are you starting two transactions in the same request? That can explain why you got this error message, or you could have had an aborted connection that was in the middle of a transaction and it didn't get rolled back or auto-committed.
You should only start one transaction per database connection. If you need two models to have an active transaction in a single request, then you need to get 2 separate database connections.
See this (great) answer by Bill Karwin in regards to this issue.
You can run the query SHOW ENGINE InnoDB STATUS; to get a list of active transactions. If you have one that is open and you have no active transactions from PHP/ZF, then try closing that transaction, otherwise you'll have to look into your code and see how two transactions are getting started in the same request.
Thanks for your answer, we found a solution.
The problem was that, we were using the save() function twice; changing the first save() with an insert(), solved the problem:
public function convertToJob()
{
$db = $this->_getTable()->getAdapter();
$db->beginTransaction();
$jobsTable = new Model_JobsTable();
try {
/*
* Create new row with the same details as above
*/
$data = array(
'job_cus_id' => $this->qte_cus_id,
'job_enq_id' => $this->qte_enq_id,
'job_qte_id' => $this->qte_id,
'job_title' => $this->qte_title,
'job_description' => $this->qte_description,
'job_work_location_id' => $this->qte_work_location_id,
'job_work_category_id' => $this->qte_work_category_id,
'job_work_type_id' => $this->qte_work_type_id,
'job_cus_code' => $this->qte_cus_code,
'job_cus_name' => $this->qte_cus_name,
'job_wt_ref_code' => $this->qte_wt_ref_code,
'job_wt_description' => $this->qte_wt_description,
'job_wl_code' => $this->qte_wl_code,
'job_wl_description' => $this->qte_wl_description,
'job_wc_ref_code' => $this->qte_wc_ref_code,
'job_wc_description' => $this->qte_wc_description,
'job_qte_title' => $this->qte_title,
'job_datetime_created' => date('Y-m-d H:i:s')
);
$newRowId = $jobsTable->insert($data);
/*
* Update the status of the old row to match the $status passed into this function
*/
$this->qte_status = "Accepted";
$this->save();
$db->commit();
return $newRowId;
}
catch (Exception $e) {
throw new Exception($e->getMessage());
return false;
}
}