What is the purpose of parent::beforeSave in Yii Framework? - yii

protected function beforeSave()
{
if ( parent :: beforeSave())
{
if ( $this -> isNewRecord )
$this -> create_time =time () ;
return true ;
}
else
return false;
}
This is code from the demo blog within Yii framework,I do not know the purpose of parent :: beforeSave(),why we should use it ?I am new to PHP thanks !

beforeSave() is an internal CActiveRecord function that normally fires onBeforeSave event. You should call parent::beforeSave() in your overriden function in a subclassed record if you want to fire this event. Or do it manually.

beforeSave() is very useful. You can assign variable with different values on add or update record by checking isNewRecord. And this is a commonn place to assing some variables like create_time, generating uuid. You can do this at anywhere but it's better if they are at common place.
Also you should consider it as a better way programming that has been proved by Yii.

Related

How do I make a construct to have beforeAuth only apply to certain views/functions in Laravel 4

I have a resource in Laravel I have called artists with an ArtistsController. I would like to add filters to some of the pages, but not all. I know I can add a filter to all of the functions/views in the resource controller like so:
public function __construct()
{
$this->beforeFilter('auth', array('except' => array()));
}
How do I add the beforeAuth filter to only a certain view/function? I would like a user to be logged in in order to go the "index" view, but I would like a user to be able to go to the "show" pages without necessarily being logged in:
public function index()
{
$artists = Artist::all();
return View::make('artists.index', compact('artists'))
->with('artists', Artist::all())
->with('artists_new', Artist::artists_new());
}
public function show($id)
{
$artist = Artist::find($id);
return View::make('artists.show', compact('artist'))
->with('fans', Fan::all());
}
Is there a way to do this? Thank you.
Not sure if this helps but you could use the only key instead of the except (if I understand your question correctly).
$this->beforeFilter('auth', array('only' => array('login', 'foo', 'bar')));
Although that would still go in the constructor.

Yii-rights params/data for bizrule

Scenerio:
Using Yii-rights + Yii-user module in my project. In Rights, I generated operations based on my controller action, under update I added a child UpdateOwn.
For UpdateOwn, the bizrule is suppose to be a simple comparison that the logged in user's ID is equal to $model->user_id field.
Problem:
I understand yii checkaccess allow you to pass in variables as parameters and comparing with your defined bizrule. But how does it work for Yii-rights module? How or what are the data/params passed in to be used in bizrule? How can I define or pass my own data/params?
Yii-rights is a wrapper for standart yii-rbac. In rights module you have web-interface for your RBAC. When you creating AuthItem (Operation in rights web interface) you can define your own bizrule.
Here is code for creating AuthItem:
$item = $this->_authorizer->createAuthItem($formModel->name, $type, $formModel->description, $formModel->bizRule, $formModel->data);
$item = $this->_authorizer->attachAuthItemBehavior($item);
_authorizer here is an example of RAuthorizer class. Then we go to RDbAuthManager, which extends CDbAuthManager, where we createAuthItem function:
public function createAuthItem($name,$type,$description='',$bizRule=null,$data=null)
{
$this->db->createCommand()
->insert($this->itemTable, array(
'name'=>$name,
'type'=>$type,
'description'=>$description,
'bizrule'=>$bizRule,
'data'=>serialize($data)
));
return new CAuthItem($this,$name,$type,$description,$bizRule,$data);
}
This is how created AuthItem, in rights. Personally i prefer to use web interface. It have alot of great fetures and much easier to handle then go to code each time.
Then when we perform checkAccess() on AuthItem we call execute bizRule:
public function executeBizRule($bizRule,$params,$data)
{
return $bizRule==='' || $bizRule===null || ($this->showErrors ? eval($bizRule)!=0 : #eval($bizRule)!=0);
}
This is how RBAC in yii work, and rights is just a cool wrapper for it. Rights doesn't change logic of how things must be done.
So in basic yii-rbac if you want to allow update only Own records you do:
$bizRule='return Yii::app()->user->id==$params["user"]->username;';
$task=$auth->createTask('updateOwnUser','update a your own account',$bizRule);
$task->addChild('updateUser');
Then you call it like this:
$user=$this->loadUser();
$params = array('user' => $user);
if(Yii::app()->user->checkAccess('updateOwnUser', $params){
..................
}
In rights it's already implemented with filters. Only thing what you need to do is add to your controller:
class MyController extends RController{
.............
public function filters()
{
return array(
'rights',
............
);
}
.............
}
So define your bizrule for item in web interface, change your controller code, and actually thats it. To know what variables to use in bizrule you can watch on RightsFilter.php code, where checkAccess() performed.
And on top of all of this i'll say about how checkAccess() does :
For each assigned auth item of the user, it first checks if the bizRule for the assignment returns true.
If true, it calls the item's checkAccess method. If the item's bizRule returns true,
2.1. If the item name is the same as the name passed in the original checkAccess() method, it returns true;
2.2. Otherwise, for every child item, it calls its checkAccess.
Hope this will clarify some aspects of RBAC and help in your task.
The yii-rights module has the following properties:
/**
* #property boolean whether to enable business rules.
*/
public $enableBizRule = true;
/**
* #property boolean whether to enable data for business rules.
*/
public $enableBizRuleData = false;
To set bizrule data via the web interface you have to set $enableBizRuleData = true in your application configuration.
Please note that the UI is limited and you can set data only for Auth-Items not for Auth-Assignments. Also the value for data has to be a serialized PHP variable.
As mentioned by #ineersa you can access $data in unserialized form in your bizRule.
It's also worth noting, that Yii checks first the bizRule for the Auth-Item and then additionally for the Auth-Assignment.
[edit] added example
Auth Item
bizRule
Check if the assignment has all the keys specified in the item data
return BizRule::compareKeys($params, $data, 'Editor');
data
a:1:{s:8:"language";b:1;}
Auth Assignment
Check if the application language matches the assignment data
bizRule
return BizRule::compareApplicationLanguage($params, $data);
data
a:1:{s:8:"language";s:5:"de_de";}
[edit] added code link
Here is the full Helper Code

Yii Behaviors and scenario

i have a behavior for my models, the behavior has beforeFind, beforeSave, in methods i override user_id, something like:
...
public functio beforeSave() {
$this->owner->user_id = Yii::app()->user->id
}
I have model User, how can i disable behavior for registration new user?
Saving code:
$user = new User();
$user->id = 1332;
$user->field1 = 'data';
$user->save();
but on save i have null in $user->id (because work behavior).
i tried
$user->disableBehaviors();
$user->detachBehavior();
Without result.
Maybe its not right way? I create behaviors for identify users in system (find only user something, save only with user id...), but that if i have new user with full previegies, i should again detach behaviors?
If condition can be changed in future I just pass it as callback parameter into behavior from model.
This give you a bit more control over the condition. Hence, behavior becomes more reusable - if it is used by several models this condition can be unique for each.
Example below is a bit simplified, but you should get the idea.
Behavior:
class SomeBehavior extends CActiveRecordBehavior
{
public $trigger;
public function beforeSave($event)
{
if(!call_user_func($this->trigger))
return;
// do what you need
$this->owner->user_id = Yii::app()->user->id;
}
}
Model:
class SomeModel extends CActiveRecord
{
public function behaviors()
{
$me=$this;
return array(
'some'=>array(
'class'=>'SomeBehavior',
'trigger'=>function() use($me){
return $me->scenario=='some-scenario';
}
)
);
}
}
Also I use PHP 5.3. So, I use closure for trigger callback.
If your PHP version is less than 5.3 - anything callable can be used instead. Check here http://www.php.net/manual/en/function.is-callable.php
Because of behavior is a method, you can declare your own logic inside.
The model knows about its scenario, so there is no problem to return different arrays for different conditions:)
Hope it be helpful for somebody.
You can check Yii::app()-user->isGuest to determine if the user is logged in or not. or you can just try looking for the null. Like this:
if (!Yii::app()->user->isGuest)
$this->owner->user_id = Yii::app()->user->id;
or
if (null !== Yii::app()->user->id)
$this->owner->user_id = Yii::app()->user->id;

Yii form and model for key-value table

I have a table which has only two column key-value. I want to create a form which allow user insert 3 pair of key-value settings.
Do I need pass 3 different models to the view? Or is there any possible way to do this?
Check out this link:
http://www.yiiframework.com/doc/guide/1.1/en/form.table
This is considered best form in Yii for updating for creating multiple models.
In essence, for creation you can create a for loop generate as many inputs a you wish to have visible, and in your controller loop over the inputs to create new models.
View File:
for ( $settings as $i=>$setting ) //Settings would be an array of Models (new or otherwise)
{
echo CHtml::activeLabelEx($setting, "[$i]key");
echo CHtml::activeLabelEx($setting, "[$i]key");
echo CHtml::error($setting, "[$i]key");
echo CHtml::activeTextField($setting, "[$i]value");
echo CHtml::activeTextField($setting, "[$i]value");
echo CHtml::error($setting, "[$i]value");
}
Controller actionCreate:
$settings = array(new Setting, new Setting, new Setting);
if ( isset( $_POST['Settings'] ) )
foreach ( $settings as $i=>$setting )
if ( isset( $_POST['Setttings'][$i] ) )
{
$setting->attributes = $_POST['Settings'][$i];
$setting->save();
}
//Render View
To update existing models you can use the same method but instead of creating new models you can load models based on the keys in the $_POST['Settings'] array.
To answer your question about passing 3 models to the view, it can be done without passing them, but to validate data and have the correct error messages sent to the view you should pass the three models placed in the array to the view in the array.
Note: The example above should work as is, but does not provide any verification that the models are valid or that they saved correctly
I'm going to give you a heads up and let you know you could potentially make your life very complicated with this.
I'm currently using an EAV patterned table similar to this key-value and here's a list of things you may find difficult or impossible:
use CDbCriteria mergeWith() to filter related elements on "value"s in the event of a search() (or other)
Filtering CGridView or CListView
If this is just very straight forward key-value with no related entity aspect ( which I'm guessing it is since it looks like settings) then one way of doing it would be:
create a normal "Setting" CActiveRecord for your settings table (you will use this to save entries to your settings table)
create a Form model by extending CFormModel and use this as the $model in your form.
Add a save() method to your Form model that would individually insert key-value pairs using the "Setting" model. Preferably using a transaction incase a key-value pair doesn't pass Settings->validate() (if applicable)
optionally you may want to override the Form model's getAttributes() to return db data in the event of a user wanting to edit an entry.
I hope that was clear enough.
Let me give you some basic code setup. Please note that I have not tested this. It should give you a rough idea though.:
Setting Model:
class Setting extends CActiveRecord
{
public function tableName()
{
return 'settings';
}
}
SettingsForm Model:
class SettingsForm extends CFormModel
{
/**
* Load attributes from DB
*/
public function loadAttributes()
{
$settings = Setting::model()->findAll();
$this->setAttributes(CHtml::listData($settings,'key','value'));
}
/*
* Save to database
*/
public function save()
{
foreach($this->attributes as $key => $value)
{
$setting = Setting::model()->find(array('condition'=>'key = :key',
'params'=>array(':key'=>$key)));
if($setting==null)
{
$setting = new Setting;
$setting->key = $key;
}
$setting->value = $value;
if(!$setting->save(false))
return false;
}
return true;
}
}
Controller:
public function actionSettingsForm()
{
$model = new Setting;
$model->loadAttributes();
if(isset($_POST['SettingsForm']))
{
$model->attributes = $_POST['SettingsForm'];
if($model->validate() && $model->save())
{
//success code here, with redirect etc..
}
}
$this->render('form',array('model'=>$model));
}
form view :
$form=$this->beginWidget('CActiveForm', array(
'id'=>'SettingsForm'));
//all your form element here + submit
//(you could loop on model attributes but lets set it up static for now)
//ex:
echo $form->textField($model,'fieldName'); //fieldName = db key
$this->endWidget($form);
If you want further clarification on a point (code etc.) let me know.
PS: for posterity, if other people are wondering about this and EAV they can check the EAV behavior extention or choose a more appropriate DB system such as MongoDb (there are a few extentions out there) or HyperDex

Yii model is validating but data could not be saved

I have a yii application. Data is validated properly. the $model->validate() returns true but data is not being saved. Is there any way that I know about the error. It does nothing. neither prints error nor any warning.
if (isset($_POST['Invoice'])) {
$model->validate();
$model->attributes = $_POST['Invoice'];
if (!$model->validate()) {
die(CVarDumper::dump($model->errors,10,true));
}
if ($model->save()) {
die("Data saved");
$this->redirect(array('view', 'id' => $model->id));
} else {
CVarDumper::dump($model->attributes,10,true);
CVarDumper::dump($model->errors,10,true);
}
}
if you override beforeSave or afterFind method in your model,
public function beforeSave() {
return true; //don't forget this
}
public function afterFind() {
return true; //don't forget this
}
make sure you return true for those function
If save() is returning true and there are no errors as such in your database and queries. Only thing, thats possible is you haven't marked some of the column safe for mass assignment via "$model->attributes".
Make sure the column you are trying to save are marked safe in the "rules" function in your model. You can mark columns safe via adding the following rule in "rules" function in the model.
array ( "column_name1, column_name2 ....." , "safe" )
I've just ran into something similar to this. Everything was validating correctly, and $model->save() was returning true, but no data was saved in the database.
The problem and solution was that I was creating the $model object like so:
$model = ClassName::model();
but you need to create the object like so:
$model = new ClassName;
If you have this problem, you replace this:
$model->save(false)
This solves your problem.
If you use $model->save(); the filters is running that is not good for you.
Fire up some logging and see what going on...
I got the same error when I was using reCaptcha. I just did this and it worked:
$model->scenario = NULL;
Make sure you do this AFTER validation.
I had the same issue, my mistake was with the post name in the controller, where I used $model->save. I had given wrong - if(isset($_POST['postname']))
If I am not wrong, you are doing an AR save() in the $model->save() method. You do not get any error, but the data is not saved as well.
If this is the case you would like to do a:
die(CVarDumper::dump($arObj->errors,10,true));
after the $arObj->save(); call. Most of the time this happens because of the Database rejecting the values provided for insert or update.
Also do not override your model constructor:
function __construct() { } // don't do this
The issue for me was that I had a property for the column name in the ActiveRecord class, so it wasn't saving.
You should not declare properties for column names as I guess the magic methods __get() and __set() are used to save data, I guess by checking if there are column changes when you click the save() method to avoid useless SQL queries. In my case, because the column was a user-declared property, it wasn't in the columns list and therefore changes to it were not detected.
Hope this helps other people