Yii2: Standalone validation not triggering client validation - validation

Validation functions don't work.
Validation customs rules are not applied to the usernmane field
module dektrium/user
PHP 7.1
Yii 2.0.16
Already try all from here: https://www.yiiframework.com/doc/guide/2.0/en/input-validation (Inline Validators and Standalone Validators)
Model Agent :
class Agent extends Profile
{
public $username;
public $password;
public $password2;
public function rules()
{
$rules = [
['username', AgentValidator::className()],// it's not work
[['email'], 'email'], // it's work
['password2', 'compare', 'compareAttribute' => 'password', 'message' => 'Пароли должны совпадать'],//// it's work
];
return array_merge(parent::rules(), $rules);
}
}
AgentValidator.php
<?php
namespace app\components;
use yii\validators\Validator;
class AgentValidator extends Validator
{
public function validateAttribute($model, $attribute)
{
if (User::findOne(['username' => $this->$attribute]]) {
$this->addError($attribute, 'Такой логин уже занят');
}
}
}

You are using standalone validator and you want the frontend validation to be working along with the backend so you need to override the yii\validators\Validator::clientValidateAttribute() in your Standalone validator AgentValidator, 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.
You can go through the section Implementing Client Validation to read in detail.
Apart from everything listed above you have a mistake in your validator code User::findOne(['username' => $this->$attribute]], you need to use $model->$attribute rather than $this->$attribute which will never get the exact value entered in the form. You might have mistakenly added it from the model.
Your current validator should be like below
<?php
namespace app\components;
use yii\validators\Validator;
class AgentValidator extends Validator
{
public $message='Такой логин уже занят';
public function validateAttribute($model, $attribute)
{
if (User::findOne(['username' => $model->$attribute])!==null)
{
$model->addError($attribute, $this->message);
}
}
public function clientValidateAttribute($model, $attribute, $view) {
//check if user exists
$userExists = User::findOne(['username' => $model->$attribute])!==null;
$message = json_encode($this->message, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
return <<<JS
if($userExists){
messages.push($message);
return;
}
JS;
}
}

So, Thank Muhammad Omer Aslam for right answer.
Yii2 does't generate any js code for validation by custom rules. Therefore it is necessary to add a check to the controller and the form
For me it:
Controller
if (\Yii::$app->request->isAjax) {
\Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
$model->load(\Yii::$app->request->post());
return \yii\widgets\ActiveForm::validate($model);
}
Form
$form = ActiveForm::begin([ 'enableAjaxValidation' => true]);

Related

In Laravel with ReCaptcha V3, how do I access the "score" returned from a rule?

In Laravel, I am setting up Google ReCaptcha V3 which now returns a "score". I've managed to setup a validation rule to allow my form to submit (all working), but this is only returning true or false to pass the validation.
How do I base this on the score instead?
I'm using this composer package to help me out - https://github.com/google/recaptcha
This is in my controller (where I am sending the token to validate with the server):
// validation
$this->validate( $request, array(
'g_recaptcha_response' => ['required', 'string', new Captcha()]
));
This is the rule:
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
use ReCaptcha\ReCaptcha;
class Captcha implements Rule
{
public function __construct()
{
//
}
public function passes($attribute, $value)
{
$recaptcha = new ReCaptcha('SECRET');
$response = $recaptcha->verify($value, $_SERVER['REMOTE_ADDR']);
return $response->isSuccess();
}
public function message()
{
return 'Are you a robot?';
}
}
Can I access the class somehow from the controller? I can see in the package that I need to use ->getScore() but I don't know how to access it?
As you are doing in the validation rule, you can also get the score in controller :
public function something(YourRequest $request){
$recaptcha = new ReCaptcha('SECRET');
$response = $recaptcha->verify($request->g_recaptcha_response, $request->ip());
$score = $response->getScore();
}
More available methods of response can be found here

proengsoft/laravel-jsvalidation How to use Custom Rule Messages?

I use proengsoft/laravel-jsvalidation in combination with a custom FormRequest and custom validation rules that I defined via Validator::extend(...) in a service provider. This works well.
However, when I port my custom rules to the new(ish) custom Rule class in Laravel 5.5+, JsValidator fails at getting my custom Rule messages.
I have this custom rule:
use Illuminate\Contracts\Validation\Rule;
class MyRule implements Rule
{
public function passes($attribute, $value) {
return $value > 10;
}
public function message() {
return 'Your :attribute is pretty small, dawg!';
}
}
My form request uses this rule:
use Illuminate\Foundation\Http\FormRequest;
use App\Rules\MyRule;
class MyRequest extends FormRequest
{
public function authorize() {
return true;
}
public function rules() {
$rules = [
'foo' => 'required|numeric',
'bar' => ['required', new MyRule()],
];
return $rules;
}
}
This should work, but I get thrown an Exception on
{!! JsValidator::formRequest('\App\Http\Requests\MyRequest') !!}
An Exception is thrown from a call to Str::snake(Object(App\Rules\MyRule)) made by Proengsoft\JsValidation\Javascript\MessageParser.php.
JsValidation does not look at the $rule object type before calling Validator->getMessage($attribute, $rule)
where instead it should be calling $rule->messages();
Can I work around this bug somehow, and use laravel-jsvalidation together with my custom Rule and FormRequest -- or does it necessarily require i make a pull request and hope it will be fixed... someday? I'd like to make this work now-ish.
This can be archived by passing JsValidator instance based on rules and message arrays instead of passing the form request. In controller Pass this instance to blade. Read here for more details.
JsValidator::make($rules, $messages, $customAttributes, $selector)
In controller,
$validator = JsValidator::make(
[
'name' => 'required',
],
[
'name.required' => 'Name field is a required field',
]
)
return View::make("Your view", compact($validator));
In blade,
{!! $validator->selector('.wizard_frm') !!}
<form class='wizard_frm'></form>
And in this case we can create object of request class and pass rules function returned array to JsValidator::make if needed.

laravel 5 form request validation issue

in my laravel 5.1 app, I've got a Book model with a required "Title" field and several others non-required fields. To validate Book create/update, I use form request validation like this:
class StoreBookRequest extends Request
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'title' => 'required',
'year' => 'numeric',
'pages' => 'numeric',
];
}
}
I then type-hint the request on the controller action and everytning works fine. Now I need to create a new controller action that updates only one of the non-required fields. To do so, I created another request like this:
class StoreReviewRequest extends Request
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'vote' => 'numeric',
];
}
}
and I type-hint the request in the controller action:
public function updateReview(StoreReviewRequest $request, Book $book)
{
$input = array_except(Input::all(), '_method');
$book->update($input);
Session::flash('message', 'Review updated');
return redirect('/book');
}
The problem is that when I use the new controller action, the update form does not pass validation, but complains about missing "Title" field, even tough I'm not decalring that field as required in my StoreReviewRequest class. What am I doing wrong? Thanks!
As #Needpoule suggested, I was using the wrong action in my form.

Laravel 5 Form request validation with parameters

I am using form request validation and there are some rules that needs external values as a parameters.
Here are my validation rules for editing a business profile inside a form request class,
public function rules()
{
return [
'name' => 'required|unique:businesses,name,'.$business->id,
'url' => 'required|url|unique:businesses'
];
}
I can use this on the controller by type hinting it.
public function postBusinessEdit(BusinessEditRequest $request, Business $business)
{
//
}
But how to pass the $business object as a parameter to the rules method?
Lets say this is your model binding:
$router->model('business', 'App\Business');
Then you can reference the Business class from within the FormRequest object like this:
public function rules()
{
$business = $this->route()->getParameter('business');
// rest of the code
}
Note that if you use your form request both for create and update validation, while creating the record, the business variable will be null because your object does not exists yet. So take care to make the needed checks before referencing the object properties or methods.
There can be many ways to achieve this. I do it as below.
You can have a hidden field 'id' in your business form like bellow,
{!! Form::hidden('id', $business->id) !!}
and you can retrieve this id in FormRequest as below,
public function rules()
{
$businessId = $this->input('id');
return [
'name' => 'required|unique:businesses,name,'.$businessId,
'url' => 'required|url|unique:businesses'
];
}
For those who switched to laravel 5 :
public function rules()
{
$business = $this->route('business');
// rest of the code
}
Let say if we have a scenario like we want to change our validation rules depends on the type that we pass in with the route. For example:
app.dev/business/{type}
For different type of business, we have different validation rules. All we need to do is type-hint the request on your controller method.
public function store(StoreBusiness $request)
{
// The incoming request is valid...
}
For the custom form request
class StoreBussiness extends FormRequest
{
public function rules()
{
$type = $this->route()->parameter('type');
$rules = [];
if ($type === 'a') {
}
return rules;
}
}
In Laravel 5.5 at least (haven't checked older versions), once you did your explicit binding (https://laravel.com/docs/5.5/routing#route-model-binding), you can get your model directly through $this:
class StoreBussiness extends FormRequest
{
public function rules()
{
$rules = [];
if ($this->type === 'a') {
}
return rules;
}
}
Since Laravel 5.6 you may type hint it in the rules method:
public function rules(Business $business)
{
return [
'name' => 'required|unique:businesses,name,'.$business->id,
'url' => 'required|url|unique:businesses'
];
}
See the docs for more:
You may type-hint any dependencies you need within the rules method's signature. They will automatically be resolved via the Laravel service container.

Cakephp Validation Messages on API

I have a registration form for my web client and API for the same registration form. I want to validate the data from the API by using the same rules that I use for the web client in the Model but I need to display different messages. In the web client I have messages like “‘Error in field Name” and for the API I need message like “1”. Now I`m doing this with if statement in the controller, if the error is ‘Error in field Name’ give me message ‘1’. The problem is that if I have to validate 10 fields I need to write 10 if statements in the controller. Is there any smarter way for doing this?
Model:
class User extends AppModel {
public $validate = array(
'name'=>array(
'rule'=>'notEmpty',
'message'=> ‘Error in field Name’
)
);
}
Controller
class RestUsersController extends AppController {
$errors = $this->User->invalidFields();
if(array_shift(array_slice($errors, 0, 1))== ' Error in field Name '){
$message='1';
}
}
Thank you in advance!
You can set validations rules in beforeValidation() callback in your model. In this method you can prepare two arrays of validation sets and and put one variable in AppModel which will be working like switch to choose proper validation set. All you need to make it working will be setting proper value for this switch in your API controller in beforeFilter() callback. For better understanding of my solution, look at code sample below.
Model
class User extends AppModel {
public function beforeValidate($options = array()) {
parent::beforeValidate($options);
$this->_prepareValidationRules();
}
protected function _prepareValidationRules() {
if (!empty($this->apiValidation)) { // for API
$this->validate = array(
'name' => array(
'rule' => 'notEmpty',
'message' => 'Error in field Name'
));
} else { // default behaviour
$this->validate = array(
'name' => array(
'rule' => 'notEmpty',
'message' => '1'
));
}
}
}
Controller
class RestUsersController extends AppController {
public function beforeFilter() {
parent::beforeFilter();
$this->User->apiValidation = true;
}
}
AppModel.php
class AppModel extends Model {
public $apiValidation = false;
(...)
}
Of course you could define $apiValidation variable as protected and controll it by method, but this is up to you.

Resources