Using custom validators with ActiveForm in Yii2 - php

I want to make custom validation function like built-in validation required. I have example code here:
Model:
use yii\base\Model;
class TestForm extends Model
{
public $age;
public function rules(){
return [
['age', 'my_validation']
];
}
public function my_validation(){
//some code here
}
}
View:
<?php
use yii\helpers\Html;
use yii\widgets\ActiveForm;
$this->title = 'test';
?>
<div style="margin-top: 30px;">
<?php $form = ActiveForm::begin(); ?>
<?= $form->field($model, 'age')->label("age") ?>
<div class="form-group">
<?= Html::submitButton('submit', ['class' => 'btn btn-primary']) ?>
</div>
<?php ActiveForm::end(); ?>
</div>
Controller:
use app\models\form\TestForm;
use yii\web\Controller;
class TestController extends Controller
{
public function actionIndex(){
$model = new TestForm();
if($model->load(\Yii::$app->request->post())){
return $this->render('test', array(
'model'=>$model,
'message'=>'success'
));
}
return $this->render('test', array('model'=>$model));
}
}
in this example I have a field for age and this my_validation function should check if age is over 18 before submit and throw error if age is under 18. This validation should be processed by ajax like it is in case of required rule if you try to submit empty field.

Although you can use Conditional Validators when and whenClient too in your scenario but I would recommend using a more sophisticated way which is to define a custom validator because according to the docs
To create a validator that supports client-side validation, you should
implement the yii\validators\Validator::clientValidateAttribute()
method which returns a piece of JavaScript code that performs the
validation on the client-side. Within the JavaScript code, you may use
the following predefined variables:
attribute: the name of the attribute being validated.
value: the value being validated.
messages: an array used to hold the validation error messages for the attribute.
deferred: an array which deferred objects can be pushed into (explained in the next subsection).
So what you need to do is create a validator and add it to your rules against the field you want.
You need to be careful copying the following code IF you haven't provided the actual model name and update the field names accordingly.
1) First thing to do is to update the ActiveForm widget to the following
$form = ActiveForm::begin([
'id' => 'my-form',
'enableClientValidation' => true,
'validateOnSubmit' => true,
]);
2) Change your model rules() function to the following
public function rules()
{
return [
[['age'], 'required'],
[['age'], \app\components\AgeValidator::className(), 'skipOnEmpty' => false, 'skipOnError' => false],
];
}
3) Remove the custom validation function my_validation() from your model i hope you are checking the age limit in it to be 18+ we will move that logic into the validator.
Now create a file AgeValidator.php inside components directory, if you are using the basic-app add the folder components inside the root directory of the project if it does not exist create a new one, and copy the following code inside.
BUT
I have assumed the name of the Model that is provided by you above so if it not the actual name you have to update the field name inside the javascript statements within clientValidateAttribute function you see below in the validator because the id attribute of the fields in ActiveForm is generated in a format like #modelname-fieldname (all small case) so according to above given model, it will be #testform-age do update it accordingly otherwise the validation wont work. And do update the namespace in the validator below and in the model rules() if you plan to save it somewhere else.
<?php
namespace app\components;
use yii\validators\Validator;
class AgeValidator extends Validator
{
public function init()
{
parent::init();
$this->message = 'You need to be above the required age 18+';
}
public function validateAttribute($model, $attribute)
{
if ($model->$attribute < 18) {
$model->addError($attribute, $this->message);
}
}
public function clientValidateAttribute($model, $attribute, $view)
{
$message = json_encode($this->message, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
return <<<JS
if (parseInt($("#testform-age").val())<18) {
messages.push($message);
}
JS;
}
}

Related

Call to a member function selectID() on null (Concrete CMS 8.5.7)

I need a new form select to allow me to set a different id and name in a select form. So I am trying to extend the form class. I did the following.
concrete\packages\concrete_form_addon\src\Concrete\Form\Service\Form2.php
namespace Concrete\Package\ConcreteFormAddon\Form\Service;
class Form2 extends \Concrete\Core\Form\Service\Form {
public function selectID($key, $optionValues, $valueOrMiscFields = '', $miscFields = []) {
working code for the option
}
}
Then in the class file I have
namespace Concrete\Package\ConcreteFormAddon;
use Concrete\Core\Asset\Asset;
use Concrete\Core\Asset\AssetList;
use Concrete\Core\Package\Package;
use Concrete\Core\Page\Page;
use Concrete\Core\Support\Facade\Route;
use Concrete\Core\Support\Facade\Config;
use Whoops\Exception\ErrorException;
class Controller extends Package {
protected $pkgHandle = 'concrete_form_addon';
protected $appVersionRequired = '8.4';
protected $pkgVersion = '0.8.7';
protected $pkgAutoloaderRegistries = [
'src/Concrete/Form' => 'Concrete\Package\ConcreteFormAddon\Form',
];
public function getPackageDescription() {
return 'Add custom form functions to Concrete CMS';
}
public function getPackageName() {
return 'Concrete Form Add-On';
}
}
Then the line of code that calls it:
<?= $form2->selectID('', $manufacturers, $product->getManufacturer() ? $product->getManufacturer()->getID() : '', ['id' => 'pManufacturer' . $pID, 'name' => 'pManufacturer[]', 'class' => 'selectize pricing-fields']) ?>
If I put the first bit of code in the core file it works fine if I use $form->selectID so there is something wrong with the way I'm setting up the class I believe.

How to pass the entity id to the unique validation in a Form Request on Laravel 5.5?

I have the following route defined:
Route::put('/{organisationId}', 'OrganisationController#update');
And I have the following FormRequest for the update request:
<?php
namespace App\Http\Requests\Organisation;
use Illuminate\Foundation\Http\FormRequest;
class UpdateOrganisationRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'required|min:3|max:60|unique:organisations,name,' . $this->get('organisationId')
];
}
}
And I am trying to use it like this in my controller:
public function update($organisationId, UpdateOrganisationRequest $request)
{
$organisation = $this->organisationRepository->byId($organisationId);
if (!$organisation) {
return $this->error('Organisation not found.', $this::Bad_Request);
}
$this->organisationRepository->update($organisation, $request->validated());
return $this->success(
fractal($organisation, new OrganisationTransformer())
);
}
This appears to trigger the unique validation error, because it doesn't appear to exclude the id I am trying to update.
Any ideas why this isn't working?
Before using FormRequest, this is how I implemented the same functionality above and it was working fine:
https://pastebin.com/raw/CDEg6qLt
I was able to update the same organisation with the same name and the unique validation rule didn't trigger an Validation exception.
All rules defined in a FormRequest's rules method should be in form of a key / value pair that corresponds to an input name and it's validation rule respectively. You missed key here so chances are validator looks for a field named 0 that doesn't exist.
Add name and test result:
return [ 'name' => '....' ];

Custom attribute validation in Yii2

I have model form (SomeForm) and custom validation function in there:
use yii\base\Model;
class SomeForm extends Model
{
public $age;
public function custom_validation($attribute, $params){
if($this->age < 18){
$this->addError($attribute, 'Some error Text');
return true;
}
else{
return false;
}
}
public function rules(){
return [
['age', 'custom_validation']
];
}
}
I use this custom_validation in rules() function but form even submitting whatever value has age attribute.
Here is the form:
age.php
<?php $form = ActiveForm::begin(); ?>
<?= $form->field($model, 'age')->label("Age") ?>
<div class="form-group">
<?= Html::submitButton('Submit', ['class' => 'btn btn-primary']) ?>
</div>
<?php ActiveForm::end(); ?>
and the controller:
use yii\web\Controller;
class SomeController extends Controller
{
//this controller is just for rendering
public function actionIndex(){
return $this->render('age');
}
public function actionSubmit(){
$model = new SomeForm();
if($model->load(Yii::$app->request->post()){
//do something here
}
}
}
You don't need to return anything just adding the error to the attribute is enough.
Since version 2.0.11 you can use yii\validators\InlineValidator::addError() for adding errors instead of using $this. That way the error message can be formatted using yii\i18n\I18N::format() right away.
Use {attribute} and {value} in the error message to refer to an attribute label (no need to get it manually) and attribute value accordingly:
What I suspect is the problem in your case is that you are missing the $formModel->validate() as in the model given above extends the yii\base\Model and not \yii\db\ActiveRecord and you must be saving some other ActiveRecord model and want to validate this FormModel before saving the ActiveRecord model, you have to call the $formModel->validate() to check if valid input is provided and trigger the model validation after loading the post array to the model.
And another thing to notice is by default, inline validators will not be applied if their associated attributes receive empty inputs or if they have already failed some validation rules. If you want to make sure a rule is always applied, you may configure the skipOnEmpty and/or skipOnError properties to be false in the rule declarations.
Your model should look like below you are missing the namespace in your model definition if that is not just intentional or due to sample code. just update you namespace according to the path where it is.
namespace frontend\models;
use yii\base\Model;
class SomeForm extends Model
{
public $age;
const AGE_LIMIT=18;
public function rules(){
return [
['age', 'custom_validation','skipOnEmpty' => false, 'skipOnError' => false]
];
}
public function custom_validation($attribute, $params,$validator){
if($this->$attribute< self::AGE_LIMIT){
$validator->addError($this, $attribute, 'The value "{value}" is not acceptable for {attribute}, should be greater than '.self::AGE_LIMIT.'.');
}
}
}
your controller/action should look like
public function actionTest()
{
//use appropriate namespace
$formModel = new \frontend\models\SomeForm();
$model= new \frontend\models\SomeActiveRecordModel();
if ($formModel->load(Yii::$app->request->post()) && $model->load(Yii::$app->request->post())) {
if ($formModel->validate()) {
// your code after validation to save other ActiveRecord model
if($model->save()){
Yii::$app->session->setFlash('success','Record added succesfully.')
}
}
}
return $this->render('test', ['model' => $model,'formModel'=>$formModel]);
}
The Input field age in the view file should use the $formMoedl object
echo $form->field($formModel, 'age')->textInput();

Yii Validate an un-bound Variable (non-stored)

Classic problem:
verify that a user accepted the contract terms but the value of the acceptance is not stored (bound) in the database...
Extend CFormModel rather than CActiveForm (because CActiveForm binds
values to DB)
Post a CFormModel to a controller action
Validate a CFormModel
I'm asking this question to answer it because the existing questions end in see the documentation...
extend CFormModle, define the rules and got to validate. With bound variables you validated as part of save. Now you validate() by itself but Validate requires a list of attributes which is not defined in CFormModel. So, what do you do? You do this:
$contract->validate($contract->attributeNames())
Here's the full example:
class Contract extends CFormModel
{
...
public $agree = false;
...
public function rules()
{
return array(
array('agree', 'required', 'requiredValue' => 1, 'message' => 'You must accept term to use our service'),
);
}
public function attributeLabels()
{
return array(
'agree'=>' I accept the contract terms'
);
}
}
Then in the controller you do this:
public function actionAgree(){
$contract = new Contract;
if(isset($_POST['Contract'])){
//$contract->attributes=$_POST['Contract']; //contract attributes not defined in CFormModel
...
$contract->agree = $_POST['Contract']['agree'];
...
}
if(!$contract->validate($contract->attributeNames())){
//re-render the form here and it will show up with validation errors marked!
}
The results:

How can I create a custom function for validation rules in yii?

I have created a form with some entities (say name,address,etc.). And I have defined validation rules in model class. Client side validation is working perfectly as desired. Now I need to create custom validation rules. For that,in reference with http://www.yiiframework.com/wiki/168/create-your-own-validation-rule/#hh0 , I have created a method called valid_number() in my model, and defined a simple null checking (I know there are built in rules for validating null,email,password, etc.. Here I have demonstrated a simple method of validation, actually I'm planning to do some custom validations). Please refer the code below. And please let me know what I am doing wrong.
//model
class Employee extends CActiveRecord{
public $number;
public function rules(){
return array(
array('number','valid_number'),
);
}
public function valid_number($attribute,$params){
if($this->$attribute == '' ){
CModel::addError($attribute, 'Number is null');
}
}
//view
</fieldset>
<?php echo $form->textFieldRow($model, 'number'); ?>
</fieldset>
CModel::addError should be $this->addError.
<?php
$this->addError($attribute, 'Your error message');
?>
Why are you calling the static function CModel::addError?
You could just call addError of the object and it works:
public function valid_number($attribute, $params) {
if ($this->$attribute == '' ) {
$this->addError($attribute, 'Number is null');
}
}

Categories