I'm trying to create a custom validation rule that accept a parameter, but this parameter is the name of another field in the request, like for the required_with rule.
I easily can handle given params in my rule, but i'm struggling to find out how to retrieve the other field value.
Currently i'm creating my rule class as
class MyClassRule
{
public function validate($attribute, $value, $parameters, $validator) : bool
{
// do some stuff here to return true/false
}
}
and registering it in my service provider with
Validator::extend('my_rule', 'path\to\MyClassRule#validate');
so i can use it in my request as
public function rules()
{
return [
'field' => ['my_rule'],
];
}
What i would like to be able to do is
public function rules()
{
return [
'other_field' => [...],
'field' => ['my_rule:other_rule'],
];
}
and use the other_field value in my rule class, but validate()'s $parameters value is just ['other_field']. i.e. an array containing the other field name, not its value.
How can i do this?
I'm running this in Laravel 7.x.
In my case, I am trying to make a rule to compare whether two field in my form is equal to one another.
Let's make a new Rule Object as instructed from Laravel's documentation.
https://laravel.com/docs/7.x/validation#custom-validation-rules
Below is the console command to make the Rule class template.
php artisan make:rule StrEqualTo
Below is the generated custom Rule class with the full implementation of the logic.
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class StrEqualTo implements Rule{
private $referredField = null;
public function __construct(string $referredField){
$this->referredField = $referredField;
}
public function passes($attribute, $value){
return request()->input($this->referredField) === $value;
}
public function message(){
return 'The :attribute must match with' . $this->referredField . '.';
}
}
We first create a private attribute and a constructor with a parameter, the parameter will accept the 'name' attribute of the field you want to refer. We then assign the value from the constructor parameter to our private attribute in our rule class.
private $referredField = null;
public function __construct(string $referredField){
$this->referredField = $referredField;
}
As stated in Laravel's docs, this function must return true if the validation succeeds, otherwise it must return false. What we do here is to use the request() helper function provided by Laravel and get the value of the field we referred from the form input($this->referredField).
public function passes($attribute, $value){
return request()->input($this->referredField) === $value;
}
We can edit the error message it will create when the validation failed in this function below.
public function message(){
return 'The :attribute must match with' . $this->referredField . '.';
}
We then instantiate the custom Rule class to an object to be used as validation rule like the code below.
'confirm-new-pass' => ['required', 'string', 'max:100', new StrEqualTo('new-pass')]
Hope this helps!!!
Artisan command
php artisan make:rule ValidateOtherField
Class ValidateOtherField
class ValidateOtherField implements Rule
{
private $error = '';
public function passes($attribute, $value)
{
if(request()->has('field') && request()->get('field') === 'MyValueSuccess'){
if(is_string($value)){
return true;
} else {
$this->error = '- not valid field';
}
}
return false;
}
public function message()
{
return "Error :attribute {$this->error}";
}
}
rules
public function rules()
{
return [
'field' => ['string'], //Validate field
'other_field' => [new ValidateOtherField],
];
}
Because $validator is a full instance of the Validator object being used, we can retrieve data from it using getData():
public function validate($attribute, $value, $parameters, $validator)
{
// You may want to check to make sure this exists first.
$otherField = $parameters[0];
$otherValue = data_get($validator->getData(), $otherField);
// #todo Validate $otherValue
}
Using data_get() allows you to use dot notation for nested array values as well.
Related
How can we change attribute values on validation classes in laravel ?
consider this code:
class Twice implements Rule
{
public function passes($attribute, $value)
{
$value = $value * 2;
return $value > 20;
}
}
and now we receive value multiply by 2 in score attribute:
$rules = [
'score' => [new Twice]
]
if we can't, what is your suggestion ?
You can use the prepareForValidation method in a validator class:
protected function prepareForValidation()
{
$this->merge([
'score' => $this->score * 2
]);
}
This will change the value of score to score * 2 and use that value for any validation, and also return that value with your request data.
Doing this, you also won't need it in the passes method on your Rule since the score passed in will already be multiplied by 2:
public function passes($attribute, $value)
{
return $value > 20;
}
Or you could even skip the custom rule altogether at that point and use:
$rules = [
'score' => 'min:21'
]
Actually we are trying to migrate our huge application from yii2 to laravel.
In yii2 framework we made some rule classes that tweak data before inserting to database, for example "ConverToEnglishNumber" which convert "Persian" numbers (۱۲۳۴۵) into "English" numbers (12345).
public function rules()
{
return [
['content', ConverToEnglishNumber::class]
]
}
but it seem impossible in laravel validation rules classe.
after a struggling day, I think using casts is the best way to do that:
namespase App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class Double implements CastsAttributes
{
public function set($model, $key, $value, $attributes)
{
return $value * 2;
}
}
namespace App\Models;
use App\Casts\Double;
use Illuminate\Database\Eloquent\Model;
class Player extends Model
{
protected $casts = [
'score' => Double::class
];
}
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
I have code in Request class:
public function rules()
{
return [
'id' => 'required|check_xxx',
];
}
public function attributes()
{
return [
'id' => 'AAA',
];
}
As you can see. I have cusom validation method name check_xxx. This method in inside class CustomValidator.
So, I have code:
class ValidationServiceProvider extends ServiceProvider
{
public function boot()
{
$this->app->validator->resolver(function ($translator, $data, $rules, $messages) {
return new CustomValidator($translator, $data, $rules, $messages);
});
}
}
And error message for required is: Please input :attribute
But I got the message: Please input id, (TRUE is: Please input AAA)
I discovered that $this->app->validator->resolver make attributes() method in Request is useless.
How can I fix that? Thank you.
I had this issue in Laravel 5.2 but found a QUICK solution as following. In example following you will add rule directly inside the APP Provider.
File to add rule: app/Providers/AppServiceProvider.php
public function boot()
{
// ....
#/
#/ Adding rule "even_number" to check even numbers.
#/
\Validator::extend('even_number', function($attribute, $value, $parameters, $validator) {
$value = intval($value);
return ($value % 2) == 0);
// ...
}
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.
I'm a Symfony noob trying unsuccessfully to visualize how best to validate a form field based on either it or a different field. The case: a form will solicit either a date of birth or an age. If the dob is entered, age is ignored. If age is entered and dob is empty, the dob is said to be today's date less age in years. If neither is entered a validation error is thrown. I've accomplished this with Smarty validation; as a learning exercise I'm trying to reproduce the application in Symfony.
I've looked at this solution where both fields are properties of an entity. In my case age is not, only dob. So it's not clear to me how to apply that solution. I'd greatly appreciate pointers.
Thanks.
George
PS: (edited: dreck removed)
PPS: (edited: removed to make room for nearly working version)
Form:
// src\Mana\AdminBundle\Resources\views\Form\Type\NewClientType.php
namespace Mana\AdminBundle\Form\Type;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Mana\AdminBundle\Validator\Constraints;
class NewClientType extends AbstractType {
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array('validation_groups' => 'client_new',
'validation_constraint' => new DOBorAge(),
));
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('fname', null, array('required' => false));
$builder->add('sname', null, array('required' => false));
$builder->add('dob', 'birthday', array('widget' => 'single_text', 'required' => false));
$builder->add('age', null, array('mapped' => false, 'required' => false));
}
public function getName() {
return 'client_new';
}
}
services:
services:
client_new:
class: Mana\AdminBundle\Validator\Constraints\DOBorAgeValidator
scope: request
tags:
- { name: validator.constraint_validator, alias: dobage_validator}
Validators:
// src\Mana\AdminBundle\Form\Type\DOBorAge.php
namespace Mana\AdminBundle\Form\Type;
use Mana\AdminBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
class DOBorAge extends Constraint {
public $message = 'Either a date of birth or age must be present';
public function validatedBy() {
return 'dobage_validator';
}
public function getTargets() {
return Constraint::CLASS_CONSTRAINT;
}
}
and
// src\Mana\AdminBundle\Validator\Constraints\DOBorAgeValidator.php
namespace Mana\AdminBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class DOBorAgeValidator extends ConstraintValidator {
protected $request;
public function __construct(Request $request) {
$this->request = $request;
}
public function validate($value, Constraint $constraint) {
var_dump($this->request->request->get('client'));
die();
return true;
}
}
A Symfony-esque solution: a single form field that takes either a date or an age, then transform the entry into a date. (Now, if I could only turn this into a custom field...)
namespace Mana\AdminBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Mana\AdminBundle\Form\DataTransformer\AgeToDOB;
class NewClientType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$transformer = new AgeToDOB();
$builder->add('fname', null, array('required' => false,
'invalid_message' => 'First name not be blank',));
$builder->add('sname', null, array('required' => false,
'invalid_message' => 'Last name not be blank',));
$builder->add(
$builder->create('dob', 'text', array('required' => false,
))->addModelTransformer($transformer));
}
public function getName() {
return 'client_new';
}
}
and the transformer:
namespace Mana\AdminBundle\Form\DataTransformer;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Mana\AdminBundle\Entity\Client;
class AgeToDOB implements DataTransformerInterface {
public function reverseTransform($dob) {
if (null == $dob) {
return '';
}
if ((substr_count($dob, '/') == 2 && strtotime($dob))) {
$date = new \DateTime($dob);
return date_format($date, 'Y-m-d');
}
if (is_numeric($dob)) {
$date = new \DateTime();
$interval = 'P' . $dob . 'Y';
$date->sub(new \DateInterval($interval));
return date_format($date, 'Y-m-d');
}
}
public function transform($client) {
if (null == $client) {
return '';
}
if (is_object($client)) {
$dob = $client->getDob();
// date: test for two / characters
if ((substr_count($dob, '/') == 2 && strtotime($dob))) {
$date = new \DateTime($dob);
return date_format($date, 'm/d/Y');
}
if (is_numeric($dob)) {
$date = new \DateTime();
$interval = 'P' . $dob . 'Y';
$date->sub(new \DateInterval($interval));
return date_format($date, 'm/d/Y');
}
}
}
}
I think that the best way to manage this kind of validation is to add a customized validation constraint to your form.
Just add a class that extend Symfony\Component\Validator\Constraint and override the validatedBy() method. You can then specify a customized validator that you've to create so that it contains all the logic of your form validation.
This customized validator should extend Symfony\Component\Validator\ConstraintValidator class and implement the isValid() method for Symfony 2.0.x or validate() method for Symfony 2.1.x.
You can then Check your values, compare them and do whatever you want inside this method and add eventually add violations if one or many of your validation rules aren't respected.
Also check Symfony2 validation based on two fields.