I am trying to save a record of another model in model's beforeSave() method. I expected the save() method to call the other's model beforeSave(),but it doesn't.Is it a bug or I am just missed something?Here is some code.
Operations model
public function beforeSave(){
if(parent::beforeSave()){
if($this->isNewRecord){
$this->creation_date=$this->modification_date=time();
$cur=Currencies::model()->find('LOWER(short)=?',array('usd'));
if($cur->id!=$this->currency_id){
$conv_cours=Currencies::model()->findByPk($this->currency_id);
$this->ammount_usd=round(($this->ammount*$conv_cours->buy)/$cur->sell,4);
}else{
$this->ammount_usd=$this->ammount;
}
}else{
$this->modification_date=time();
}
$opType=OperationType::model()->findByPk($this->operation_type);
$log=new ActionLogs;
$log->comment=Yii::app()->user->name.' добавил '.$opType->name;
/*$log->logtime=time();
$log->user_id=Yii::app()->user->id;*/
$v=1;
$log->save ();
return true;
}else{
return false;
}
}
And another models beforeSave()
public function beforeSave(){
if(parent::beforeSave()){
if($this->isNewRecord){
$this->logtime=time();
$this->user_id=Yii::app()->user->id;
}
return true;
}else{
return false;
}
}
Thanks.
The only reason I can think of for Yii not calling beforeSave when you try to save an activerecord is when its validation fails. Either try to do a dump of $object->errors after your save call or try to save it with $object->save(FALSE) to skip validation. Like that we can eliminate this as a cause.
Related
I have a problem with UploadImageBehavior.
I need to create simple button on frontend part that allow me to remove image (user avatar) after model save.
Actually i know how to do it with unlink, but i absolutely don't understand how to do that via behaviour.
I have next code in rules:
[['avatar'], 'file', 'skipOnEmpty' => true, 'extensions' => 'png, jpg, jpeg'],
So Yii just ignore me, if i try to pass null to my avatar property.
Thx )
Just create a method to your model class that does the unlinking, sets the file attribute in your model to null and saves the model.
public function removeAvatar() {
$transaction = $this->getDb()->beginTransaction();
try {
// If this is a new record throw an Exception because no file has been uploaded yet
if($this->isNewRecord) {
throw new \Exception("Can't delete file of new record");
}
// Set the attribute avatar to null
$this->avatar = null;
// Try to save the record. If we can't then throw an Exception
if(!$this->save()) {
throw new \Exception("Couldn't save the model");
}
// Try to delete the file. If we can't then throw an Exception
if(!unlink(Yii::getAlias('#app/path/to/your/file.something')) {
throw new \Exception("Couldn't delete the file");
}
$transaction->commit();
return true;
}
catch(\Exception $e) {
$transaction->rollback();
return false;
}
}
Below works for me:
unlink(getcwd().'/uploads/'.$model->file_id.'/'.$fileModel->file_name.$fileModel->extension);
getcwd() gets the current working directory. The docs for it are here
A user has a sponsor:
public function sponsor()
{
return $this->belongsTo(User::class, 'sponsor_id');
}
A user has referrals:
public function referrals()
{
return $this->hasMany(User::class, 'sponsor_id');
}
A user is considered capped when they have 2 or more referrals:
public function activeReferrals()
{
return $this->referrals()->whereActive(true);
}
public function isCapped()
{
return $this->activeReferrals()->count() >= 2;
}
A user can give points. By default, the sponsor will receive them, but if the sponsor is capped, I want the points to go to a sponsor's referral that is NOT capped. If all the referrals are capped, then it does the same thing with the level below (the referral's referrals).
If I go user by user making database calls for each one, it's gonna take a long time. How can I write a scope that makes recursive calls until it finds the first active referral in the tree that's not capped?
This is what I'm trying to do:
Please give this a try... I believe this will work for you :)
public function scopeNotCappedActiveReferrals($query, $count) {
return $query->withCount(['referrals' => function($q) {
$q->where('active', true);
}])->where('referrals_count', '<', $count);
}
For the second part...
// Finally you can call it with
public function allReferrals() {
$users = User::notCappedActiveReferrals(2)->get();
$allUsers = $this->findNotCappedActiveReferralsRecurrsively($users);
}
// Do not place this function in the model,
// place it in your Controller or Service or Repo or blahblah...
// Also, not tested... but should work :)
protected function findNotCappedActiveReferralsRecurrsively($users) {
if(!count($user)) {
return $users;
}
foreach($users as $user) {
$moreUsers = $user->notCappedActiveReferrals(2)->get();
return $users->merge($this->findNotCappedActiveReferralsRecurrsively($moreUsers));
}
}
Hope this is what you need :)
I know, how to use transactions in pure DAO or in ActiveModel, where transaction is initiated before call to $model->save() and rolled back upon any exception.
But how to use transactions, if the only place of code I have access to (no matter, why) is Yii event?
public function beforeDelete()
{
foreach($this->menuItems as $menuItem) $menuItem->delete();
return parent::beforeDelete();
}
If I initiate transaction there, capture possible exception and rollback entire transaction upon it, then only deletion of relational models (here: menu items) will be rolled back. It will not prevent (roll back) deletion of master record.
Does preventing deletion of master record, by returning FALSE in my own beforeDelete in case of exception, is all I need to take care here? Or should I avoid transactions at all in Yii events?
What about override save method:
public function save($runValidation=true,$attributes=null)
{
$transaction=$this->getDbConnection()->beginTransaction();
try
{
$result = parent::save($runValidation,$attributes);
if($result)
$transaction->commit();
else
$transaction->rollback();
}
catch(Exception $e)
{
$transaction->rollback();
$result = false;
}
return $result;
}
Answering my own question with example piece of code to further extend my comment given to Alex's answer:
public function beforeDelete()
{
$transaction = $this->getDbConnection()->beginTransaction();
try
{
foreach($this->menuItems as $menuItem) $menuItem->delete();
$transaction->commit();
return parent::beforeDelete();
}
catch(Exception $ex)
{
$transaction->rollback();
return FALSE;
}
}
Both answers seems correct, both are alternative to each other. Though, I accept Alex answer, as better.
I want to make a TransactionFilter in Yii to be applied over an action to wrap it in a transaction so I don´t have to write the same code over and over every time I want to use transactions, at least that's the idea. I have
class TransactionFilter extends CFilter
{
public function filter($filterChain)
{
if(Yii::app()->getRequest()->getIsPostRequest())
{
$transaction= Yii::app()->db->beginTransaction();
try {
$filterChain->run();
$transaction->commit();
}catch(Exception $e) {
$transaction->rollback();
}
}
else
$filterChain->run();
}
}
This is my filters method in my User class:
public function filters()
{
return array(
'accessControl',
'postOnly + delete',
array('application.components.TransactionFilter + create'),
);
}
I'm assuming $filterChain->run() will eventually execute the action but the problem arises when there's a redirect in the action, it never made it after the $filterChain->run() sentence in the filter
I don't know if this approach would be advisable and posible in Yii, if not I would appreciate the help if there is another approach or I have to stick with the traditional one.
Thank you.
You have to begin transaction on:
protected function preFilter($filterChain)
And commit, rollback on :
protected function postFilter($filterChain)
I am using PHP Yii Framework with MongoDB(yiimongodbsuite). I have created a Model which extends from EMongoDocument.
<?php
class MyModel extends EMongoDocument
{
public $attr1;
public $attr2;
// rules, custom validations and other functions....
public function setAttributes($values, $safeOnly=true)
{
if(!is_array($values))
return;
if($this->hasEmbeddedDocuments())
{
$attributes=array_flip($safeOnly ? $this->getSafeAttributeNames() : $this->attributeNames());
foreach($this->embeddedDocuments() as $fieldName => $className)
if(isset($values[$fieldName]) && isset($attributes[$fieldName]))
{
$this->$fieldName->setAttributes($values[$fieldName], $safeOnly);
unset($values[$fieldName]);
}
}
parent::setAttributes($values, $safeOnly);
}
}
In Controller,
$dataModel = new MyModel();
$dataModel->setAttributes($_POST['MyModel']);
if($dataModel->validate()){
$dataModel->save();
}
the above code is not setting the attribute value.
Please let me know if there is any mistake.
You need to make sure that the 'safe' validation rules is used on each level.
To understand more read this http://www.yiiframework.com/wiki/161/understanding-safe-validation-rules/
Try to determine which valdation errors you have:
if(!$model->validate()) {
die( print_r($model->getErrors()) );
}