The idea here is to insert in two different tables parameters using SCENARIO, first register the customer with their parameters and then register the order that belongs to a customer, all in just one form.
I'm sending a form with several parameters, some will be used in the insertion of a customer using scenario, and the other parameters I will use in order (I did this so I do not have to create two forms) the parameters are being correctly sent through POST together with the csrf.
public function createOrder()
{
//$customer = Customer::find()->where(['email' => $params->email])->limit(1)->asArray()->all();
$customer = new Customer;
$customer->load(Yii::$app->request->post());
$customer->scenario = 'create';
if($customer->validate()){
$customer->save();
vdp($customer);
} else{
vdpd($customer->getErrors());
}
die;
}
This returns me an array saying that the Name, email, address, cell, phone, city, etc parameters can not be left blank.
In my customer model:
const SCENARIO_CREATE = 'create';
public function scenarios()
{
$scenarios = parent::scenarios();
$scenarios[self::SCENARIO_CREATE] = ['name', 'email', 'public_place', 'cell_phone', 'phone', 'city', 'cep', 'state', 'neighborhood', 'number', 'complement'];
return $scenarios;
}
Controller
public function actionCreateOrder()
{
$customer = new Customer;
$customer->setScenario(Customer::SCENARIO_CREATE);
if($customer->load(Yii::$app->request->post())
if($customer->save()){
vdp($customer);
} else {
vdpd($customer->getErrors());
}
}
die;
}
MODEL
const SCENARIO_CREATE = 'create';
public function rules()
{
return [
[['name', 'email', 'address'], 'required', 'on' => self::SCENARIO_CREATE], // Add more required fields on 'create' scenario.
... // some more rules
];
}
public function scenarios()
{
$scenarios = parent::scenarios();
$scenarios[self::SCENARIO_CREATE] = ['name', 'email', 'public_place', 'cell_phone', 'phone', 'city', 'cep', 'state', 'neighborhood', 'number', 'complement'];
return $scenarios;
}
1) createOrder() should be actionCreateOrder() in controller, not in model.
2)
$customer->load(Yii::$app->request->post());
$customer->scenario = 'create';
if ($customer->validate()) {
...
should be
$customer->scenario = Customer::SCENARIO_CREATE;
if ($customer->load(Yii::$app->request->post()) && $customer->validate()) { ... }
Because load method loads attributes for the current scenario and there is no need to run validation before the model is loaded. See the example.
3) Declare rules for your attributes in Customer model.
You need to set $scenario before load() call. Scenario defines attributes, that can be set by load(), so you're doing it too late and it has no effect. Try this:
$customer = new Customer;
$customer->scenario = 'create';
$customer->load(Yii::$app->request->post());
Related
I'm trying to allow a user to create 2 models in one form. Post has a one to one relationship with Story.
My code looks something like this :
public function actionCreate()
{
post = new post();
story = new story();
if (isset($_POST['post']))
{
$post->attributes = $_POST['post'];
$story->attributes = $_POST['story'];
// force the views to show errors for both $post and $story
$post->validate();
// this one will always fail because the required foreign key field is not set until the post is saved.
$story->validate();
if ($post->save())
{
$story->post_id = $post->id;
$story->save();
}
}
}
I need to call validate so that the view shows errors on all fields, however since the model is just being created, post doesn't have an id yet so I can't assign it to story. This means validate for story will always fail.
Is there a way I can validate the model when it's still new without throwing away the required rule for the foreign key.
you can do it with a "scenario" in which indicate when to use it and what fields does .. for example ..
<?php
class User extends Model
{
public $name;
public $email;
public $password;
public function rules(){
return [
[['name','email','password'],'required'],
['email','email'],
[['name', 'email', 'password'], 'required', 'on' => 'register'],
];
}
public function scenarios()
{
$scenarios = parent::scenarios();
$scenarios['login'] = ['name','password'];//Scenario Values Only Accepted
return $scenarios;
}
}
?>
<?php
...
class UserController extends Controller
{
..
// APPLY SCENARIOS
// scenario is set as a property
............
public function actionLogin(){
$model = new User;
$model->scenario = 'login';
.............
}
// scenario is set through configuration
public function actionRegister(){
$model = new User(['scenario' => 'register']);
..............
}
}
?>
In this sample you ca use two scenario 'login' validate two field 'register' validate three..
see this doc for moore sample and thisi from Yii
need help updating a unique rule in my validation rules. I have a abstract validator that will validate a rules before storing into my database and in the rules array I set the email to be unique when creating or registering a user but when updating the user the enique email should not validate if the email is owned by the user.
abstract class Validator
abstract class Validator {
protected $errors;
protected $attributes;
public function __construct($attributes = null)
{
$this->attributes = $attributes ?: \Input::all();
}
public function passes()
{
$validation = \Validator::make($this->attributes, $this->rules());
if ($validation->passes()) return true;
$this->errors = $validation->messages();
return false;
}
public function getErrors()
{
return $this->errors;
}
}
Validation Rules(UserRule.php)
use MyCustomValidatorNamespaceHere....
class UserRules extends Validator
public function rules()
{
return [
'email' => 'required|email|unique:users,email,id',
...
];
}
and in my UserController I injected the UserRule in the constractor. (UserRule $userRule). Here is the code in the update method.
public function update($id)
{
$if ($this->userRule->passes())
{
$this->user->find($id)->update(Input::all());
return .........
}
}
But the validation always fail and displaying the error that the email is already taken. Please help guys.
The problem is your rule. When you update, you need to use unique that doesn't check record you update. So you should have:
unique:users,email,id
but for example:
unique:users,email,10
if you edit record with id 10.
What you could do is to define this rule:
'email' => 'required|email|unique:users,email,{id}',
and your passes method:
public function passes($id = null)
{
$rules = $this->rules();
$rules['email'] = str_replace('{id}', $id, $rules['email']);
$validation = \Validator::make($this->attributes, $rules);
if ($validation->passes()) return true;
$this->errors = $validation->messages();
return false;
}
and now in update rule use:
if ($this->userRule->passes($id))
By the way you have error in $if ($this->userRule->passes()) - it should be if and not $if
You can use the route method inside your request class to except an id from the validation
public function rules()
{
return [
'email' => 'required|email|unique:users,email,'.$this->route('user'),
...
];
}
I had a problem like that before and it was difficult to find an answer. Here is what I did.
class UserRules extends Validator {
public function __construct($input = NULL) {
$this->input = $input ?: \Input::all();
if(isset($this->input['id'])):
self::$rules['username'] = 'required|unique:users,username,'.$this->input['id'];
self::$rules['email'] = 'required|email|unique:users,email,'.$this->input['id'];
else:
self::$rules['username'] = 'required|unique:users';
self::$rules['email'] = 'required|email|unique:users';
endif;
}
public static $rules = array(
'company_id' => 'required',
'role' => 'required',
'password' => 'sometimes|required|confirmed'
);
}
You need to use self:: because $rules is static.
I have moved this function:
public function passes($id)
{
$rules = static::$rules;
$rules['username'] = str_replace('{id}', $id, $rules['username']);
$rules['email'] = str_replace('{id}', $id, $rules['email']);
$validation = \Validator::make($this->attributes, $rules);
if($validation->passes()) return true;
$this->errors = $validation->messages();
return false;
}
into UserRule.php and commented the same function in abstract class Validator
Now updating is working.
I solved this problem here on stackoverflow in a generic way. It will automatically adapt your rules if you do an update and add exceptions to your :unique if necessary.
in my site if email is registered in my database I would add a error
$this->addError('email' ,'This Email already registered');
but in Update form I do not want see this error
What is a simple way to solve my problem?
this is my users model:
<?php
/**
* This is the model class for table "users".
class Users extends CActiveRecord
{
// public $captcha;
/**
* #return string the associated database table name
*/
public function tableName()
{
return 'users';
}
/**
* #return array validation rules for model attributes.
*/
public function rules()
{
// NOTE: you should only define rules for those attributes that
// will receive user inputs.
return array(
array('username, email,password', 'required'),
array('roles_id', 'numerical', 'integerOnly'=>true),
array('username, password',
'length',
'max'=>255,
'min'=>4
),
array('email', 'comp_email'),
array('username', 'comp_username'),
array('DataCreated, LastUpdated', 'safe'),
// The following rule is used by search().
// #todo Please remove those attributes that should not be searched.
array('id, username, password, DataCreated, LastUpdated, roles_id', 'safe', 'on'=>'search'),
);
}
/**
* #return array relational rules.
*/
/**
* #return array customized attribute labels (name=>label)
*/
public function attributeLabels()
{
return array(
'id' => 'ID',
'email'=>'Email',
'username' => 'Username',
'password' => 'Password',
'DataCreated' => 'Data Created',
'LastUpdated' => 'Last Updated',
'roles_id' => 'Roles',
);
}
public function search()
{
// #todo Please modify the following code to remove attributes that should not be searched.
$criteria=new CDbCriteria;
$criteria->compare('id',$this->id);
$criteria->compare('username',$this->username,true);
$criteria->compare('password',$this->password,true);
$criteria->compare('DataCreated',$this->DataCreated,true);
$criteria->compare('LastUpdated',$this->LastUpdated,true);
$criteria->compare('roles_id',$this->roles_id);
return new CActiveDataProvider($this, array(
'criteria'=>$criteria,
));
}
/**
* Returns the static model of the specified AR class.
* Please note that you should have this exact method in all your CActiveRecord descendants!
* #param string $className active record class name.
* #return Users the static model class
*/
public static function model($className=__CLASS__)
{
return parent::model($className);
}
public function comp_username($attributes , $params)
{
$username = Yii::app()->db->createCommand()
->select('username')
->from('users')
->queryAll();
$y = (count($username));
for ($x=0;$x<$y;$x++)
{
$usernameE[$x] = $username[$x]['username'];
}
foreach ($usernameE as $u)
{
if($this->username == $u)
{
$this->addError('username' ,'This Username already registered');
break;
}
}
}
public function comp_email($attributes , $params)
{
$email = Yii::app()->db->createCommand()
->select('email')
->from('users')
->queryAll();
$y = (count($email));
for ($x=0;$x<$y;$x++)
{
$emailE[$x] = $email[$x]['email'];
}
foreach ($emailE as $u)
{
if($this->email == $u)
{
$this->addError('email' ,'This Email already registered');
break;
}
}
}
public function getUsernameEmail($id)
{
$emailUsername = Yii::app()->db->createCommand()
->select('*')
->from('users')
->where('id=:id', array(':id'=>$id))
->queryAll();
return $emailUsername;
}
}
and this is my action Update in my controller:
public function actionUpdate($id)
{
$model=$this->loadModel($id);
// Uncomment the following line if AJAX validation is needed
$this->performAjaxValidation($model);
if(isset($_POST['Users']))
{
$model->attributes=$_POST['Users'];
$id=$model->id;
$useremail = Users::model()->getUsernameEmail($id);
$useremailX= $useremail[0]['username'];
$model->username=$useremailX;
$useremailX= $useremail[0]['email'];
$model->email=$useremailX;
$model->password= crypt($model->password,'salt');
if($model->save())
$this->redirect(array('view','id'=>$model->id));
}
$this->render('update',array(
'model'=>$model,
));
}
You can achieve this by applying specific scenarios to your rules.
The Yii WIKI topic on the subject is a good reference.
In your rules, you can specify which scenarios to apply the rule to.
array('email', 'unique','message'=>'Email already exists!', 'on'=>'insert')
Please note that Yii automatically injects specific scenarios, depending on how the object is created.
insert
update
search
You can specify your own custom scenario.
$model = Customer::model()->findByPK($customerID);
$model->scenario = 'purchase';
this is how to validate, you can set error message to be empty.
public function rules()
{
// NOTE: you should only define rules for those attributes that
// will receive user inputs.
return array(
//First parameter is your field name of table which has email value
array('email', 'email','message'=>"The email isn't correct"),
array('email', 'unique','message'=>'Email already exists!'),
);
}
https://stackoverflow.com/a/12778419/1727357
or you can make your own validator:
public function uniqueEmail($attribute, $params)
{
// Set $emailExist variable true or false by using your custom query on checking in database table if email exist or not.
// You can user $this->{$attribute} to get attribute value.
$emailExist = true;
if($emailExist)
{
//do what your want
$this->addError('email','Email already exists');
}
}
User this validation method in rules:
array('email', 'uniqueEmail','message'=>'Email already exists!'),
Hey can anyone help me with validating a model in yii?
class MyFormModel extends FormModel
{
public myAttribute1;
public myAttribute2;
public function __construct()
{
$this->myAttribute1 = 'blablabla'
$this->user = new User();
}
public function rules()
{
$rules = parent::rules()
$rules[] = array('myAttribute1', 'required', 'message' => 'this is required');
$rules[] = array(#i need to add validation for $user->firstname here#);
return $rules;
}
}
how do i validate an attribute from another model?
You can write the rules for the firstname attribute in user model itself. And on this page you can validate this particular field by using
$userMDl = new User();
if($userMDl->validate(array('firstname '))
// valid
}
You can add to your MyFormModel attribute
private $firstname;
override init method
public function init(){
...
$this->firstname = $user->firstname;
}
and in your rules
$rules[] = array('firstname', 'required', 'message' => 'firstname is required');
You can write your own validation function in form class. Please see next article: http://www.yiiframework.com/wiki/168/create-your-own-validation-rule/
Let's say I have a product which can have a colour. Depending on the product type, the colour field may or may not be required.
If colour is always required, I would have the following in the product model
public function rules()
{
return array(
array('colour', 'required')
);
}
However, I want this to be dynamic depending on the product type.
Should this be done in the controller? I would imagine having something like the following in the controller:
public function actionOrder() {
// ....
if ($product->HasColour) {
// set the colour validation to be required
} else {
// set the colour validation to be not required
}
}
What is the best way to approach this?
Thanks
You can use scenario. In the model:
class Model extends CActiveRecord {
// ....
public function rules() {
return array(
array('colour', 'required', 'on' => 'hasColour')
);
}
// ....
}
And in the controller:
public function actionOrder() {
// ....
$model = new Product();
if ($product->HasColour) {
$model->setScenario('hasColour');
}
}
So, required colour will be validated when the model's scenario is hasColour
class LoginForm extends CFormModel
{
public $username;
public $password;
}
$form = new LoginForm();
$form->validatorList->add(
CValidator::createValidator('required', $form, 'username, password')
);
Now $form has two required fields.
One approach is to use a custom validation rule. For example, the rule:
array('colour', 'requiredOnHasColour'),
And then the validator method in the same model class:
public function requiredOnHasColour($attribute, $params) {
if ($this->hasColour && $this->$attribute == null)
$this->addError($attribute, 'Colour is required.');
}
More info: Create your own validation rule
If you want to do more complicated logic, then scenarios might not satisfy your needs. Then you can override method init and do all the logic that define validation rules over there, adding results to $validationRules array. And the in rules() method you just return that array. Something like that:
class Person extends CActiveRecord
{
public function init(){
if( TRUE){
$this->validationRules[] = array('first_name','required');
$this->validationRules[] = array('last_name','required');
}
}
public $validationRules = array(
array('email', 'required'),
array('email, email1, email2, email3', 'email', 'message'=>'Email format is invalid'),
array('email, address, email1, email2, email3', 'length', 'max'=>255),
);
public function rules()
{
return $this->validationRules;
}
}
In your ActiveRecord you can add in any place dynamic validator. For example i overwrite parent method setAttributes and add $this->validatorList->add() depends on selected value in $this->type attribute. Official docs: https://www.yiiframework.com/doc/api/1.1/CValidator
public function setAttributes($values, $safeOnly = true)
{
$result = parent::setAttributes($values, $safeOnly);
if (self::TYPE_PHONE == $this->type) {
$this->validatorList->add(CValidator::createValidator('required', $this, ['phone', 'phonePrefix']));
} elseif (self::TYPE_EMAIL == $this->type) {
$this->validatorList->add(CValidator::createValidator('required', $this, ['email']));
}
return $result;
}