yii2 custom validation addError message doesn't show - yii2

when i use custom validations on yii2 dynamic forms it doesn't show any error messages below the input field.Below I have posted my model.
It doesn't show any error messges when qty field gets validated
namespace frontend\models;
use Yii;
class OrderD extends \yii\db\ActiveRecord
{
public static function tableName()
{
return 'order_d';
}
public function rules()
{
return [
[['item_id', 'qty', 'price', 'value'], 'required'],
[['item_id'], 'integer'],
[['price', 'value'], 'number'],
[['order_code'], 'string', 'max' => 10],
[['item_id'], 'exist', 'skipOnError' => true, 'targetClass' => Item::className(), 'targetAttribute' => ['item_id' => 'id']],
[['order_code'], 'exist', 'skipOnError' => true, 'targetClass' => OrderH::className(), 'targetAttribute' => ['order_code' => 'code']],
['qty', 'validateQty']
];
}
public function validateQty($attribute)
{
$qty = $this->$attribute;
if ($qty >= 5)
{
$this->addError('qty', "qty validation successful");
}
}
/**
* #inheritdoc
*/
public function attributeLabels()
{
return [
'id' => 'ID',
'item_id' => 'Item ID',
'order_code' => 'Order Code',
'qty' => 'Qty',
'price' => 'Price',
'value' => 'Value',
];
}
/**
* #return \yii\db\ActiveQuery
*/
public function getItem()
{
return $this->hasOne(Item::className(), ['id' => 'item_id']);
}
/**
* #return \yii\db\ActiveQuery
*/
public function getOrderCode()
{
return $this->hasOne(OrderH::className(), ['code' => 'order_code']);
}
}

Be sure to know custom validations are php functions and not convert as javascript to validate in runtime .. those will work after the page has submit and send to controller ..
here is simple sample you want :
['qty','custom_function_validation'],
];
}
public function custom_function_validation($attribute, $params){
if($this->$attribute>5){
$this->addError($attribute,'it\'s more than 5');
}
}

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.
public function clientValidateAttribute($model, $attribute, $view)
{
return <<<JS
// your validation
JS;
}
See the documentation here: http://www.yiiframework.com/doc-2.0/guide-input-validation.html#implementing-client-side-validation

Use method addError() in a controller and then just render your view file
Example
if ($promo_code) {
if ($promo_code->status == '1') {
$message = 'This combination is incorrect.';
$model->addError('promo_code', $message);
return $this->render('index', compact('model'));
}
$promo_code->status = '1';
$promo_code->save();
$model->save();
}

Related

How I can validate response with Form Request and ajax json for laravel 8

I have been using laravel for a short time, and I have been reading about good practices, I want to make an ajax json request with a validation in the form, I have seen that it is better to create a class "extend FormRequest" to better manage the information, in my case I am using validator but I don't know how to manage it in such a way to simplify the controller code as much as possible.
Currently I have:
Controller:
public function store(Request $request)
{
if($request->ajax()) {
$messages = [
'nombre.required' => 'El campo nombre es necesario',
'descripcion.required' => 'El campo descripción es necesario',
];
$rules = [
'nombre' => 'required',
'descripcion' => 'required',
'url' => 'required|alpha_dash'
];
$validator = Validator::make($request->all(), $rules, $messages);
if ($validator->fails()) {
return response()->json([
'status' => 400,
'errors' => $validator->messages()//$validator->errors()
]);
} else {
$crear = new Proyecto([
'nombre' => $request->nombre,
'descripcion' => $request->descripcion,
'url' => $request->url
]);
$crear->save();
return response()->json([
'status' => 200,
'message' => 'Registro insertado con éxito'
]);
}
}else{
return view("404");
}
}
Model:
class Proyecto extends Model
{
use HasFactory;
protected $table = 'proyecto';
protected $primaryKey = 'id_proyecto';
protected $fillable = ['nombre', 'descripcion', 'url'];
public function getRouteKeyName()
{
return 'url';
}
}
js:
$('#crear_proyecto').submit(function(e){
e.preventDefault();
var formu = $("#crear_proyecto").serialize();
$.ajax({
type: "POST",
url: "proyecto/crearproyecto",
data: formu,
dataType: "json",
beforeSend:function(){
$(".loader").show();
},
success: function (data) {
$('#crear_proyecto').find('span.text-danger').remove();
if (data.status == 400) {
$.each(data.errors, function (key, error_value) {
$('#'+key).after("<span class='text-danger'>"+error_value+"</span>");
});
$(".loader").fadeOut("slow");
}else{
$(".loader").fadeOut("slow");
alert(data.message);
$('#crear_proyecto').trigger("reset");
$("#nombre").focus();
$('#modalformu').modal('toggle');
}
}
});
I do not know if the code I have previously put is a bad practice or to leave it like that, what I have been seeing is to leave the controller as light as possible by creating a RequestForm, but I do not know how to adapt it with json and ajax, the idea was to do something like that:
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Validation\ValidationException;
class PostProyectoRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'nombre' => 'required',
'descripcion' => 'required',
'url' => 'required|alpha_dash',
];
}
public function messages()
{
return [
'nombre.required' => 'El campo nombre es necesario',
'descripcion.required' => 'El campo descripción es necesario'
];
}
}
EDIT
Now I have this code in the controller:
public function store(PostProyectoRequest $request)
{
$crear = new Proyecto([
'nombre' => $request->nombre,
'descripcion' => $request->descripcion,
'url' => $request->url
]);
$crear->save();
return response()->json([
'status' => 200,
'message' => 'Registro insertado con éxito'
]);
}
And this y RequestForm
namespace App\Http\Requests;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Validator;
class PostProyectoRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'nombre' => 'required',
'descripcion' => 'required',
'url' => 'required|alpha_dash'
];
}
public function messages()
{
return [
'nombre.required' => 'El campo nombre es necesario',
'descripcion.required' => 'El campo descripción es necesario'
];
}
protected function failedValidation(Validator $validator)
{
if($this->wantsJson())
{
$response = response()->json([
'status' => 400,
'errors' => $validator->errors()//$validator->errors()
]);
}else{
$response = redirect()
->route('guest.login')
->with('message', 'Ops! Some errors occurred')
->withErrors($validator);
}
throw (new ValidationException($validator, $response))
->errorBag($this->errorBag)
->redirectTo($this->getRedirectUrl());
}
}
So you are actually right on track and you don't need to keep the old validation in the controller anymore. You want to do something like this.
//old
//public function store(Request $request)
//new
public function store(PostProyectoRequest $request)
{
$crear = new Proyecto([
'nombre' => $request->nombre,
'descripcion' => $request->descripcion,
'url' => $request->url
]);
$crear->save();
//you can't have two responses because the first one would run only because
return response()->json([
'status' => 200,
'message' => 'Registro insertado con éxito'
]);
//or
$response = [
'status' => 200,
'message' => 'Registro insertado con éxito'
];
return json_encode($response);
}
So is instead of using the normal Request facade, you would use the form request PostProyectoRequest you created. By doing so, you are telling the controller to pass the request through the form request which would validate it and if any of the validation fails it, it would return a validation error and wouldn't run the code in the controller. Read more about form requests here also read about early returns and returns in general

How to use relations using joinWith in yii2 search model

I have two models with related data fbl_leagues and fbl_country tables. fbl_leagues have a column country_id which is related to fbl_country, now in yii2 gridView i was able to do something like [
'attribute' => 'country_id',
'value' => 'country.name',
], which gives the name of the country rather than the countries id, then i also want to enable searching by country name rather than country_id but i get the below error
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'country.name' in 'where clause'
The SQL being executed was: SELECT COUNT(*) FROM `fbl_leagues` LEFT JOIN `fbl_country` ON `fbl_leagues`.`country_id` = `fbl_country`.`id` WHERE `country`.`name` LIKE '%s%'
Below is my LeagueSearch model
class LeagueSearch extends League
{
/**
* {#inheritdoc}
*/
public function rules()
{
return [
[['id', 'created_at', 'updated_at'], 'integer'],
[['name','country_id'], 'safe'],
];
}
/**
* {#inheritdoc}
*/
public function scenarios()
{
// bypass scenarios() implementation in the parent class
return Model::scenarios();
}
/**
* Creates data provider instance with search query applied
*
* #param array $params
*
* #return ActiveDataProvider
*/
public function search($params)
{
$query = League::find();
$query->joinWith('country');
// add conditions that should always apply here
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
$this->load($params);
if (!$this->validate()) {
// uncomment the following line if you do not want to return any records when validation fails
// $query->where('0=1');
return $dataProvider;
}
// grid filtering conditions
$query->andFilterWhere([
'id' => $this->id,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
]);
$query->andFilterWhere(['like', 'name', $this->name])
->andFilterWhere(['like', 'country.name',$this->country_id]);
return $dataProvider;
}
}
and my league model
{
return [
[['country_id', 'created_at', 'updated_at'], 'integer'],
[['name','country_id'], 'required'],
[['name'], 'string', 'max' => 100],
[['country_id'], 'exist', 'skipOnError' => true, 'targetClass' => Country::className(), 'targetAttribute' => ['country_id' => 'id']],
];
}
/**
* {#inheritdoc}
*/
public function attributeLabels()
{
return [
'id' => Yii::t('app', 'ID'),
'country_id' => Yii::t('app', 'Country Name'),
'name' => Yii::t('app', 'Name'),
'created_at' => Yii::t('app', 'Created At'),
'updated_at' => Yii::t('app', 'Updated At'),
];
}
/**
* #return \yii\db\ActiveQuery
*/
public function getCountry()
{
return $this->hasOne(Country::className(), ['id' => 'country_id']);
}
/**
* #return \yii\db\ActiveQuery
*/
public function getPerfectSelections()
{
return $this->hasMany(PerfectSelection::className(), ['league_id' => 'id']);
}
Now i noticed that when i comment out $query->joinWith('country'); then there will be no error but searching not working as expected
Hmm silly me i guess i later figure out the problem the table name is fbl_country so i suppose write fbl_country.name but wrote country.name in my search model, it's being a long day though, thanks for all

YII2 Call to a member function isAttributeRequired() on a non-object

I am new with Yii2, im trying to make sales transaction, but i have problem for dropdownlist in detail section.
for this module, i have 3 tables :
1. for header, table salesheader
2. for detail, table salesdetail
3. for item, table item
this is my code :
on controller :
public function actionUpdate($id)
{
$model = $this->findModel($id); //find appropriate record in salesheader
//$modeldetail = salesdetail::findModel($id);
$modeldetail = new salesdetail();
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['index']);
} else {
$item = item::find()->select(["concat('[',code,'] - ', name) as item", "id"])->indexBy('id')->column();
$detail = salesdetail::find()->select(["*"])->all(); //find appropriate record in salesdetail
return $this->render('update', [
'model' => $model,
'modeldetail' => $modeldetail,
'item' => $item,
'detail' => $detail
]);
}
}
on salesheader model
namespace app\models;
use Yii;
class salesheader extends \yii\db\ActiveRecord
{
public static function tableName()
{
return 'salesheader';
}
public function rules()
{
return [
[['partnerId', 'date', 'term', 'name'], 'required'],
[['name', 'invNumber'], 'string'],
[['partnerId', 'term'], 'integer']
];
}
public function attributeLabels()
{
return [
'partnerId' => 'Customer',
'date' => 'Date',
'invNumber' => 'Inv. Number',
'term' => 'Term',
'name' => 'Name'
];
}
on salesdetail model
namespace app\models;
use Yii;
class salesdetail extends \yii\db\ActiveRecord
{
public static function tableName()
{
return 'salesdetail';
}
public function rules()
{
return [
[['itemId', 'qty', 'price'], 'required'],
[['unit'], 'string']
];
}
/**
* #inheritdoc
*/
public function attributeLabels()
{
return [
'itemId' => 'Item',
];
}
}
on my view
<?= $form->field($detail, 'itemId')->dropDownList($item)->label(false) ?>
and when open the page in browser, i got this error "Call to a member function isAttributeRequired() on a non-object"
and if i change this line on controller
//$modeldetail = salesdetail::findModel($id);
$modeldetail = new salesdetail();
to this one
$modeldetail = salesdetail::findModel($id);
//$modeldetail = new salesdetail();
then the error would be "Call to undefined method app\models\salesdetail::findModel()"
can somebody tell me what am i doing wrong ?
and how to get appropriate data from salesdetail based on salesheader and put it on dropdownlist ?
Thank you for ur help

Yii2 remove unique validator

I have an AR model, it has the following rules:
/**
* #inheritdoc
*/
public function rules() {
return [
[['category_id', 'source', 'url', 'title', 'thumbs', 'duration', 'status', 'created_at'], 'required'],
[['category_id', 'status', 'views', 'ratings', 'created_at'], 'integer'],
[['rating'], 'double'],
[['source', 'url', 'title', 'slug'], 'string', 'max' => 255],
[['url'], 'unique', 'on' => 'create'],
[['category_id'], 'exist', 'skipOnError' => true, 'targetClass' => Category::className(), 'targetAttribute' => ['category_id' => 'id']],
];
}
I want to do a soft delete so I have the following.
/**
* Deletes an existing Video model.
* If deletion is successful, the browser will be redirected to the 'index' page.
* #param integer $id
* #return mixed
*/
public function actionDelete($id)
{
$model = $this->findModel($id);
$model->status = 0;
//var_dump($model->validate());
//var_dump($model->getErrors());die;
$model->save();
return $this->redirect(['index']);
}
But unfortunately I cannot change the status of model, because the validation says that (The url xxxxxxx has been taken) so I went to the PostgreSql, and I checked the records, but unfortunately only the updating record has this value! So in my mind the Yii2 unique validatios is bad. I would like to remove the unique validator, but it seems it is impossible. Because I commented out the uniqure row in the rule array, but it did not help me. I restarted the machine, but I do not know, it seems Yii2 want always check the url is unique or not.
You can use scenario
public function scenarios()
{
$scenarios = parent::scenarios();
$scenarios['soft_delete'] = ['status',]; //Scenario Values Only Accepted
return $scenarios;
}
public function actionDelete($id)
{
$model = $this->findModel($id);
$model->status = 0;
$model->scenario = 'soft_delete';
//var_dump($model->validate());
//var_dump($model->getErrors());die;
$model->save();
return $this->redirect(['index']);
}
or another way is suppress validation for this action
public function actionDelete($id)
{
$model = $this->findModel($id);
$model->status = 0;
//var_dump($model->validate());
//var_dump($model->getErrors());die;
$model->save(false);
return $this->redirect(['index']);
}

Either or validation for string and file yii2

I have two fields message and file where one is just plain string and file is an image.
I want to create validator which only allows user to send either one of those 2 fields.
I tried when validator but in when the field $model->file is always null so what is other method to do either or validation with file.
Here is my model code
class Message extends \yii\db\ActiveRecord
{
public $file;
/**
* #inheritdoc
*/
public static function tableName()
{
return 'message';
}
/**
* #inheritdoc
*/
public function rules()
{
return [
[['sender_id', 'receiver_id'], 'integer'],
[['message'], 'string'],
[['file'], 'file', 'extensions'=>'jpg, gif, png,jpeg'],
/*['file', 'required', 'when' => function($model) {
return $model->message == null;
}],
['message', 'required', 'when' => function($model) {
return $this->file->baseName == null;
}]*/
];
}
/**
* #inheritdoc
*/
public function attributeLabels()
{
return [
'id' => 'ID',
'sender_id' => 'Sender ID',
'receiver_id' => 'Receiver ID',
'message' => 'Message',
'file' => 'Image (jpg/png)',
'is_delivered' => 'Is Delivered',
'is_notified' => 'Is Notified',
'is_deleted' => 'Is Deleted',
'created_date' => 'Created Date',
'updated_date' => 'Updated Date',
'is_group' => 'Is Group',
];
}
}
Thank you
This may Help you..
Define Your rules as : -
public function rules()
{
return [
//Your Rules ......
['message' ,'string'],
['file' ,'file'],
['message', 'required', 'when' => function($model) {
return $model->file === null;
} ,'whenClient' => 'function (attribute, value) {
return $("#'. Html::getInputId($this ,'file') .'").val() === null;
}'],
['file', 'required', 'when' => function($model) {
return $model->message === null;
} , 'whenClient' => 'function (attribute, value) {
return $("#'. Html::getInputId($this ,'message') .'").val() == "";
}'],
];
}