Yii 1.x how to change model name in POST (CActiveForm) - yii

Its posible change model name in post/get?
I have model with large names, like "VerLargeModelName" and many parameters.
It does not fit in GET (query string limit).
Update:
i need just change generated inputs from CActiveForm (change LongModelName[a] to short[a])

You can just change the name. You can do this like this echo $form->textFieldBlock($model,'name',array('name' => 'x["name"]') or whatever you want. You could also create a class (widget) with does this for your.
class MyActiveForm extends CActiveForm {
public function hiddenField($model, $attribute, $htmlOptions = array()) {
if(isset($htmlOptions['shortName'])) {
$htmlOptions['name'] = $htmlOptions['shortName'] . "[".$attribute."]";
unset($htmlOptions['shortName']);
}
return parent::hiddenField($model, $attribute, $htmlOptions);
}
}
You change CActiveFrom from the widget to MyActiveForm. Then use $form->textFieldBlock($model,'name',array('shortName' => 'x'). You could also change the above code to always change to a shortname without the htmlOptions. So that it is always x. However you could not have two form at once in this case. Benifit is that you would not need to add array('shortName' => 'x') to all of them, but just change CActiveFrom to MyActiveForm. So that would save you time, but cost your flexibility (with you might need later on maybe).
You have to create a function offcourse for every input field you want to use from ActiveRecord. The name of the element would become x['name']
In the controller you could simply do $model->attributes = $_POST['x'].

Related

Yii2 REST API relational data return

I've set up Yii2 REST API with custom actions and everything is working just fine. However, what I'm trying to do is return some data from the API which would include database relations set by foreign keys. The relations are there and they are actually working correctly. Here's an example query in one of the controllers:
$result = \app\models\Person::find()->joinWith('fKCountry', true)
->where(..some condition..)->one();
Still in the controller, I can, for example, call something like this:
$result->fKCountry->name
and it would display the appropriate name as the relation is working. So far so good, but as soon as I return the result return $result; which is received from the API clients, the fkCountry is gone and I have no way to access the name mentioned above. The only thing that remains is the value of the foreign key that points to the country table.
I can provide more code and information but I think that's enough to describe the issue. How can I encode the information from the joined data in the return so that the API clients have access to it as well?
Set it up like this
public function actionYourAction() {
return new ActiveDataProvider([
'query' => Person::find()->with('fKCountry'), // and the where() part, etc.
]);
}
Make sure that in your Person model the extraFields function includes fKCountry. If you haven't implemented the extraFields function yet, add it:
public function extraFields() {
return ['fKCountry'];
}
and then when you call the url make sure you add the expand param to tell the action you want to include the fkCountry data. So something like:
/yourcontroller/your-action?expand=fKCountry
I managed to solve the above problem.
Using ActiveDataProvider, I have 3 changes in my code to make it work.
This goes to the controller:
Model::find()
->leftJoin('table_to_join', 'table1.id = table_to_join.table1_id')
->select('table1.*, table_to_join.field_name as field_alias');
In the model, I introduced a new property with the same name as the above alias:
public $field_alias;
Still in the model class, I modified the fields() method:
public function fields()
{
$fields = array_merge(parent::fields(), ['field_alias']);
return $fields;
}
This way my API provides me the data from the joined field.
use with for Eager loading
$result = \app\models\Person::find()->with('fKCountry')
->where(..some condition..)->all();
and then add the attribute 'fkCountry' to fields array
public function fields()
{
$fields= parent::fields();
$fields[]='fkCountry';
return $fields;
}
So $result now will return a json array of person, and each person will have attribute fkCountry:{...}

Handling uuid pk column in yii

I'm using UUID's as PK in my tables. They're stored in a BINARY(16) MySQL column. I find that they're being mapped to string type in YII. The CRUD code I generate breaks down though, because these binary column types are being HTML encoded in the views. Example:
<?php echo
CHtml::link(CHtml::encode($data->usr_uuid), /* This is my binary uuid field */
array('view', 'id'=>$data->usr_uuid)); ?>
To work around this problem, I overrode afterFind() and beforeSave() in my model where I convert the values to/from hex using bin2hex and hex2bin respectively. See this for more details.
This takes care of the view problems.
However, now the search on PK when accessing a url of the form:
http://myhost.com/mysite/user/ec12ef8ebf90460487abd77b3f534404
results in User::loadModel($id) being called which in turn calls:
User::model()->findByPk($id);
This doesn't work since the SQL is being generated (on account of it being mapped to php string type) is
select ... where usr_uuid='EC12EF8EBF90460487ABD77B3F534404'
What would have worked is if I could, for these uuid fields change the condition to:
select ... where usr_uuid=unhex('EC12EF8EBF90460487ABD77B3F534404')
I was wondering how I take care of this problem cleanly. I see one possiblity - extend CMysqlColumnSchema and override the necessary methods to special case and handle binary(16) columns as uuid type.
This doesn't seem neat as there's no support for uuid natively either in php (where it is treated as string) or in mysql (where I have it as binary(16) column).
Does anyone have any recommendation?
If you plan using it within your own code then I'd create my own base AR class:
class ActiveRecord extends CActiveRecord
{
// ...
public function findByUUID($uuid)
{
return $this->find('usr_uuid=unhex(:uuid)', array('uuid' => $uuid));
}
}
If it's about using generated code etc. then customizing schema a bit may be a good idea.
I used the following method to make working with uuid (binary(16)) columns using Yii/MySQL possible and efficient. I mention efficient, because I could have just made the column a CHAR(32) or (36) with dashes, but that would really chuck efficient out of the window.
I extended CActiveRecord and added a virtual attribute uuid to it. Also overloaded two of the base class methods getPrimaryKey and setPrimaryKey. With these changes most of Yii is happy.
class UUIDActiveRecord extends CActiveRecord
{
public function getUuid()
{
$pkColumn = $this->primaryKeyColumn;
return UUIDUtils::bin2hex($this->$pkColumn);
}
public function setUuid($value)
{
$pkColumn = $this->primaryKeyColumn;
$this->$pkColumn = UUIDUtils::hex2bin($value);
}
public function getPrimaryKey()
{
return $this->uuid;
}
public function setPrimaryKey($value)
{
$this->uuid = $value;
}
abstract public function getPrimaryKeyColumn();
}
Now I get/set UUID using this virtual attribute:
$model->uuid = generateUUID(); // return UUID as 32 char string without the dashes (-)
The last bit, is about how I search. That is accomplished using:
$criteria = new CDbCriteria();
$criteria->addCondition('bkt_user = unhex(:value)');
$criteria->params = array(':value'=>Yii::app()->user->getId()); //Yii::app()->user->getId() returns id as hex string
$buckets = Bucket::model()->findAll($criteria);
A final note though, parameter logging i.e. the following line in main.php:
'db'=>array(
...
'enableParamLogging' => true,
);
Still doesn't work, as once again, Yii will try to html encode binary data (not a good idea). I haven't found a workaround for it so I have disabled it in my config file.

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

What is main purpose of filter in YII?

As as title clarifies what is main purpose of filters in Yii? I am newbie to Yii and little confuse about filters and validators? Any one can explain it for me?
A validator will be validating that an attribute in a model is as it should be: an, integer, a date, less than a given size, ...
Example:
public function rules()
{
return array(
//username and password are required
array('username, password', 'required'),
//myInt is a number between 0 and 255
array('myInt', 'numerical', 'min'=>0, 'max'=> 255),
);
}
The validation rules will be tested when calling $model->validate() or $model->save(). If one of the validator did not pass, then an error will be thrown to the user.
You can know the errors thrown by calling $model->getErrors()
Source: Model Rules Validation
The filter definition is :
A filter can be applied before and after an action is executed. It can
modify the context that the action is to run or decorate the result
that the action generates.
So basiclly it will execute some work before calling the controller method (so before rendering anything on the screen) or after the controller is done (so it could be after the datas are validated and added in the db).
As example we can say:
Check user authorizations
Implements HTTP caching
...
To apply filters to actions, we need to override the CController::filters() method. The method should return an array of filter configurations. For example,
public function filters()
{
return array(
'postOnly + edit, create',
array(
'application.filters.PerformanceFilter - edit, create',
'unit'=>'second',
),
);
}
Using the plus and the minus operators, we can specify which actions the filter should and should not be applied to. In the above, the postOnly filter will be applied to the edit and create actions, while PerformanceFilter filter will be applied to all actions EXCEPT edit and create. If neither plus nor minus appears in the filter configuration, the filter will be applied to all actions.
Source: Yii API about CFilter and Yii Guide
So if you want to validate some datas, then use the validators and if what you want to do is not depending on the model (ie check a user is logged in, ...) then you should implements a filter.
In general the difference between a filter and a validator is pretty obvious.
Validators are used for preventing inserting or updating wrong data in db. Filters could be used to make some preparation before or after validating
class LoginForm extends CFormModel
{
public $mail;
public $password;
public $rememberMe;
public function rules()
{
return array(
array('mail, password', 'filter'=>'trim'),
array('mail', 'filter'=>'mb_strtolower'),
array('mail, password', 'required'),
array('mail', 'email'),
array('rememberMe', 'boolean'),
array('password', 'authenticate'),
);
}
}
Filters is mainly used to filter your URL. Here also included accessControl. That's means when you set URL to access any action. Then those filter checked that action is permitted for those User or not. Also if you set that this controller delete action only can be accessible by POST method Then you will not be able to delete by using GET method. Those all type of access .... Controlled by FILTERS.
On the other hand, Validator is used to validate your any input filed AS your wishes. As like Minimum , Maximum, Integer or not, is it will be Unique or not, is this field required or not. is this field will be email type or not ...... and many more type of validation of INPUT FIELD.....
In Single Word it can be say " Validator is used for Input Validation and Filter is Used for Output Validation "

add a common scope to all models in cakephp

I am building an online accounts application where each business has its own data and therefore each table has a field business_id.
Is there a way to automatically add to each Model the condition
Model.business_id => x
For example if a user did a search for all Transactions containing Project the conditions added would be:
Transaction.business_id => x
Project.business_id => x
I am guessing that this would be best placed in a behavior as it applies to all but two models
Thanks
you can modify query data in beforeFind(), which can be within a behavior as well
in your models:
$actsAs = array('BusinessCondition');
The behavior would be something like:
<?php
App::uses('AuthComponent', 'Controller/Component');
class BusinessCondition extends ModelBehavior {
public function beforeFind(Model $Model, $query) {
$query['conditions'][$Model->alias . '.business_id'] = AuthComponent::user('business_id');
return $query;
}
}