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.
Related
this us my trait. validation rules are not working even i give 1 for the page number getting
response not validation error.
trait ValidatePagination
{
protected function pagination(Request $request)
{
$rules['page'] = 'integer|gt:4';
$rules['per_page'] = 'integer|gt:0|lte:100';
$validator = \Validator::make($request->all(), $rules);
if ($validator->fails()) {
return response()->json($validator->errors(), Response::HTTP_BAD_REQUEST);
}
}
}
my controller's method
public function get(Request $request): JsonResponse
{
$companyId = $request['user']->cid;
$perPage = $request->query('per_page', 15);
$this->pagination($request);
$staffTable = TableBuilder::get($companyId, STAFF);
$staff = $staffTable->get(['pid', 'name', 'mobile', 'pay_rate', 'is_working', 'pay_start_date', 'pay_end_date']);
$staff = $staffTable->orderBy('updated_at', 'desc')->simplePaginate($perPage);
return $this->success('Fetched staff members', $staff);
}
The problem is, you are not using the returned data, you should throw an exception instead, this is kinda tricky to get the correct data into the validation exception. The most correct approach is to use form requests, which you can also reuse across controllers.
The request can be created by using php artisan make:request PaginatedRequest. For further information see the docs.
use Illuminate\Foundation\Http\FormRequest;
class PaginatedRequest extends FormRequest {
public function rules()
{
return [
'page' => 'integer|gt:4',
'per_page' => 'integer|gt:0|lte:100',
];
}
}
This will automatically resolve the validator and throw the correct exception. The pattern is also recommended as a good practice in Laravel best practices.
public function get(PaginatedRequest $request): JsonResponse
{
....
}
you should return the failed validation inside get function also like so:
public function get(Request $request): JsonResponse
{
$companyId = $request['user']->cid;
$perPage = $request->query('per_page', 15);
if ($validatorValue = $this->pagination($request)) {
return $validatorValue;
}
$staffTable = TableBuilder::get($companyId, STAFF);
$staff = $staffTable->get(['pid', 'name', 'mobile', 'pay_rate', 'is_working', 'pay_start_date', 'pay_end_date']);
$staff = $staffTable->orderBy('updated_at', 'desc')->simplePaginate($perPage);
return $this->success('Fetched staff members', $staff);
}
To me, it looks like you are missing returning the validation error from the controller code. An easy fix would be to run the validate instead of the fails code. I would say the most correct approach would be #mrhn solution since you should ideally create a FormRequest.
trait ValidatePagination
{
protected function pagination(Request $request)
{
$rules['page'] = 'integer|gt:4';
$rules['per_page'] = 'integer|gt:0|lte:100';
$validator = \Validator::make($request->all(), $rules);
$validator->validate();
}
I created a custom rule with php artisan make:rule, set it in controller, but it not working. What the problem can be?
class CheckDeliveryDate implements Rule
{
public $client_id;
private $is_after_midday;
private $error_messge;
public function __construct(int $client_id)
{
$this->client_id = $client_id;
$this->is_after_midday = Carbon::now()->greaterThan(Carbon::now()->midDay());
$this->error_messge = 'Error';
}
public function passes($attribute, $value)
{
$delivery_date = Carbon::parse($value);
if ($delivery_date->isToday()) {
$this->error_messge = 'Error';
return false;
}
if ($delivery_date->endOfDay()->isPast()) {
$this->error_messge = 'Error';
return false;
}
return true;
}
public function message()
{
return $this->error_messge;
}
}
In controller i set method rules:
public function rules($client_id)
{
return [
'orders.*.positions.*.delivery_time' => [
'required',
'date',
new CheckDeliveryDate($client_id)
],
];
}
and when i store my order, validator->fails() return me "false".
$validator = Validator::make(
$request->all(),
$this->rules($client_id)
);
I tried set dd or dump in Rule, not working. Where is my mistake?
As stated on laravel documentation (https://laravel.com/docs/5.8/validation#custom-validation-rules) you are not supposed to pass the parameter to your custom rule instance. Laravel does it for you.
Thus:
new CheckDeliveryDate($client_id)
Becomes :
new CheckDeliveryDate
Have a nice day !
Wrong JSON format date. This is amazing mistake...
I am building a website using PHP Yii2 Framework and dektrium\yii2-user extension for user authentication.
I want to ask the user for a captcha if the number of unsuccessful logins is over three, but by default the extension doesn't support this.
Now, I have overridden the extension's User and LoginForm model, and added the fields and checks required. However, I cannot figure out how to add a rule to make the captcha required only from the fourth attempt.
Is it possible to add rules dynamically? I have shown a simplified code view below and my comments where I need help. I will write the functions, just need help with the commented part.
<?php
namespace app\models\dektrium\user;
class LoginForm extends \dektrium\user\models\LoginForm
{
public $captcha;
public $need_captcha;
public function rules() {
$rules = parent::rules();
//This is how you'd normally add a rule, but this will require it for every login
//The following rule should be added from the login()
$rules[] = ['captcha', 'captcha', 'message' => 'Too many attempts. Captcha required.'];
$rules[] = ['need_captcha', 'boolean'];
return $rules;
}
public function login() {
$success = false;
$requireCaptcha = false;
if ($this->validate() && $this->user) {
if ($this->user->login_attempts > 3) {
//add rule here to require captcha
$requireCaptcha = true;
}
$success = !$requireCaptcha && $this->validateCaptcha() && $this->validateLogin();
if ($success) {
$this->user->updateAttributes(['last_login_at' => time()]);
}
}
return $success;
}
}
?>
EDIT:
If there is an 'optional' parameter converse to 'required', that'd suffice to. I can check for the captcha within my login().
EDIT 2:
I tried to use scenarios as follows, but the model is not loading the captcha value in the controller action, before doing the validation.
<?php
namespace app\models\dektrium\user;
class LoginForm extends \dektrium\user\models\LoginForm
{
public $captcha;
public $need_captcha;
public $login_count;
public function rules() {
$rules = parent::rules();
$rules[] = ['captcha', 'captcha', 'message' => 'Too many attempts. Captcha required.', 'on' => ['required_captcha']];
$rules[] = ['need_captcha', 'boolean'];
$rules[] = ['login_count', 'integer'];
return $rules;
}
public function login() {
$this->user = $this->finder->findUserByUsernameOrEmail(trim($this->login));
if($this->user && $this->user->login_count > 3) {
$this->scenario = 'required_captcha';
$this->need_captcha = true;
}
$success = parent::login();
if ($success) {
$this->user->login_count = 0;
$this->user->save();
} else {
$this->login_count++;
if ($this->user) {
$this->user->login_count++;
$this->user->save();
}
}
if ($this->login_count > 3) {
$this->scenario = 'required_captcha';
$this->need_captcha = true;
}
return $success;
}
}
Use
['field', 'required', 'when' => function($model) { return your_true_condition; } when setting up validation rules.
Condition can be something from session/cookie, like an integer that you will increment each time
So here's how I ended up doing this...
I used a scenario based rule and in the controller action, I set the scenario when the condition for which I needed captcha was true. In reality, the controller was also of any extension, so I had to do some supported controller mapping and set the scenario via an event.
My first attempts at doing this failed since I was setting the scenario during the validation function, but should have probably set it prior to doing so, where it worked.
<?php
namespace app\models\dektrium\user;
class LoginForm extends \dektrium\user\models\LoginForm
{
public $captcha;
public $need_captcha;
public $login_count;
public function rules() {
$rules = parent::rules();
$rules[] = ['captcha', 'required', 'on' => ['use_captcha']];
$rules[] = ['captcha', 'captcha', 'on' => ['use_captcha']];
$rules[] = ['need_captcha', 'boolean'];
$rules[] = ['login_count', 'integer'];
return $rules;
}
public function login() {
$success = true;
$this->user = $this->finder->findUserByUsernameOrEmail(trim($this->login));
if(!$this->need_captcha && $this->user && $this->user->login_count > 3) {
$this->need_captcha = true;
$success = false;
}
$success = $success && parent::login();
if ($success) {
$this->user->login_count = 0;
$this->user->save();
} else {
$this->login_count++;
if ($this->user) {
$this->user->login_count++;
$this->user->save();
}
if ($this->login_count > 2)
$this->need_captcha = true;
}
return $success;
}
}
I am using a repository pattern in my Laravel 4 project but come across something which I think I am doing incorrectly.
I am doing user validation, before saving a new user.
I have one method in my controller for this:
public function addNewUser() {
$validation = $this->userCreator->validateUser($input);
if ( $validation['success'] === false )
{
return Redirect::back()
->withErrors($validation['errors'])
->withInput($input);
}
return $this->userCreator->saveUser($input);
}
Then the validateUser method is:
public function validate($input) {
$rules = array(
'first_name' => 'required',
'last_name' => 'required',
'email_address' => 'unique:users'
);
$messages = [
];
$validation = Validator::make($input, $rules, $messages);
if ($validation->fails())
{
$failed = $validation->messages();
$response = ['success' => false, 'errors' => $failed];
return $response;
}
$response = ['success' => true];
return $response;
}
This may be okay, but I dont like doing the if statement in my controller? I would rather be able to handle that in my validation class.
But to be able to redirect from the validation class, I need to return the method in the controller.
What if I then want to have 5 methods called, I cant return them all?
I would like to be able to simply call the methods in order, then in their respective class handle what I need to and if there is any errors redirect or deal with them. But if everything is okay, simply ignore it and move to the next function.
So example:
public function addNewUser()
{
$this->userCreator->validateUser($input);
$this->userCreator->formatInput($input);
$this->userCreator->sendEmails($input);
return $this->userCreator->saveUser($input);
}
If doing the if statement in the controller isn't as bad as I think then I can continue, but this seems incorrect?
For repository pattern, you can use this :-
setup your basemodel like this
<?php namespace App;
use Illuminate\Database\Eloquent\Model;
class BaseModel extends Model{
protected static $rules=null;
protected $errors=null;
public function validateForCreation($data)
{
$validation=\Validator::make($data,static::$rules);
if($validation->fails())
{
$this->errors=$validation->messages();
return false;
}
return true;
}
/**
* #return errors
*/
public function getErrors() { return $this->errors; }
}
now in your repository, add these methods
protected $model;
protected $errors=null;
public function model(){ return $this->model; }
public function getErrors(){ return $this->errors; }
public function create($inputs)
{
if(!$this->model->validateForCreation($inputs))
{
$this->errors=$this->model->getErrors();
return false;
}
$new=$this->model->create($inputs);
return $new;
}
and the controller will look like this..
public function postCreate(Request $request)
{
$inputs=$request->all();
if($new=$this->repo->create($inputs))
{
return redirect()->back()
->with('flash_message','Created Successfully');
}
return redirect()->back()->withInput()->withErrors($this->repo->getErrors())
->with('flash_message','Whoops! there is some problem with your input.');
}
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/