I am trying to validate a nested JSON object in Laravel. I have created a custom rule to do this however I have an issue currently, I want to be able to pass the object at the current array index to my custom validator:
<?php
namespace App\Http\Requests\App;
use App\Rules\CheckoutDepatureCheck;
use App\Rules\SeatIsAvailable;
use Illuminate\Foundation\Http\FormRequest;
class CheckoutRequest 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 [
"company" => "required",
"seats" => "required|array",
"seats.*.seat_no" => ['required', new SeatIsAvailable()], // would like to pass seat.* to the constructor of my custom validator here
"seats.*.schedule_id" => "required|numeric",
"seats.*.date" => "required|date"
];
}
}
The point for this is my custom validator needs schedule_id and data as well as the seat_no to successfully validate the request.
How do I do this in Laravel?
You can dynamically add rules depending on the length of the seats' array input
<?php
namespace App\Http\Requests\App;
use App\Rules\CheckoutDepatureCheck;
use App\Rules\SeatIsAvailable;
use Illuminate\Foundation\Http\FormRequest;
class CheckoutRequest 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()
{
$rules = [
'company' => 'required',
'seats' => 'required|array',
];
return array_merge($rules, $this->seatsRules());
}
private function seatsRules(): array
{
$rules = [];
foreach ((array) $this->request->get('seats') as $key => $seat) {
$rules["seats.$key.seat_no"] = ['required', new SeatIsAvailable($seat)];
$rules["seats.$key.schedule_id"] = 'required|numeric';
$rules["seats.$key.date"] = 'required|date';
}
return $rules;
}
}
Related
I am using Request validation to validate the user's input.
This is UpdateUser:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Gate;
class UpdateUser extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return Gate::allows('update-user');
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
$user_id = Arr::get($this->request->get('user'), 'id');
return [
'user.firstname' => 'required|string|max:255',
'user.lastname' => 'required|string|max:255',
'user.email' => "required|string|email|max:255|unique:users,email,{$user_id}",
'user.password' => 'sometimes|nullable|string|min:4|confirmed',
];
}
}
As you can see, there is some update-specific stuff happening:
The authorize() method checks whether the user is allowed to update-user and inside the rules I am excluding the row of the current user from being unique:
'user.email' => "required|string|email|max:255|unique:users,email,{$user_id}",
As I would like to merge UpdateUser and StoreUser, what would be the most efficient and readable way to determine, whether I am updating or saving?
This is my current approach:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Gate;
class UpdateUser extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
if($this->isUpdating())
{
return Gate::allows('update-user');
}
return Gate::allows('create-user');
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
if($this->isUpdating()){
$user_id = Arr::get($this->request->get('user'), 'id');
return [
...
];
}
return [];
}
/**
* #return bool
*/
protected function isUpdating(){
return $this->isMethod('put') || $this->isMethod('patch');
}
}
I am wondering if I may extend the FormRequest class and provide isUpdating() by default.
Your update and store method are not the same request type, you have PUT and PATCH method on your request instance, so you can check the request type as like :
switch ($request->method()) {
case 'PATCH':
// do anything in 'patch request';
break;
case 'PUT':
// do anything in 'put request';
break;
default:
// invalid request
break;
}
I learnt about a new approach to validation some time ago using separate validator class and I kinda like it a lot. Let me show you
Create a directory Validators and a class inside UserValidator
class UserValidator
{
public function rules(User $user)
{
return [
'user.firstname' => [
'required',
'string',
'max:255',
],
'user.lastname' => [
'required',
'string',
'max:255',
],
'user.email' => [
$user->exists ? 'sometimes' : null,
'required',
'string',
'email',
'max:255',
Rule::unique('users', 'email')->ignore($user->exists ? $user->id : null)
],
'user.password' => [
$user->exists ? 'sometimes' : null,
'required',
'string',
'min:8'
],
];
}
public function validate(array $data, User $user)
{
return validator($data, $this->rules($user))
//->after(function ($validator) use ($data, $user) {
// Custom validation here if need be
//})
->validate();
}
}
Then authorization can be done in Controller
class UserController
{
use AuthorizesRequests;
/**
* #param Request $request
*/
public function store(Request $request)
{
$this->authorize('create_user', User::class);
$data = (new UserValidator())->validate(
$request->all(),
$user = new User()
);
$user->fill($data)->save();
}
/**
* #param Request $request
* #param \App\user $user
*/
public function update(Request $request, User $user)
{
$this->authorize('update_user', $user);
$data = (new UserValidator())->validate(
$request->all(),
$user
);
$user->fill($data)->save();
}
}
This was proposed and explained by (twitter handle) #themsaid
Actually, I tried to create rule object which is able to validate every image type in array of images and not only enough but also, I must to show custom message in override message function in rule object.
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class ImagesArray implements Rule
{
/**
* Create a new rule instance.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Determine if the validation rule passes.
*
* #param string $attribute
* #param mixed $value
* #return bool
*/
public function passes($attribute, $value)
{
return [$attribute => 'mimes:jpeg,jpg,png' ];
here i need to validate these file types.
}
/**
* Get the validation error message.
*
* #return string
*/
public function message()
{
return 'The validation error message.';
here, I need to show my custom messgae.
}
}
You should use Request.
For example, create q request class: php artisan make:request MyRequest.
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class MyRequest 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 [
'image' => 'mimes:jpeg,jpg,png',
];
}
public function messages()
{
return [
'image.mimes' => 'This image is not supported.',
];
}
}
In your controller import class MyRequest and in the method use MyRequest
e.g:
public function store(MyRequest $request)
{ // your code
}
Let me know if that was helpful. Thanks!
When validating arrays or nested parameters, you should use . in your rules access a specific array index. but if you want to apply a rule to every index on that array, you can use .*.
$validator = Validator::make($request->all(), [
'image.*' => 'mimes:jpeg,jpg,png',
], [
'image.*' => 'Invalid file type.',
]);
Or if you're using Request Forms
public function rules(){
return [
'image.*' => 'mimes:jpeg,jpg,png',
];
}
public function mesages(){
return [
'image.*' => 'Invalid file type.',
];
}
For more info, see Laravel's Documentation on Validation Arrays
I need to use required_id with a in:1 as this field is required to be checked only if another field equal to 4
'affirm_agency' => 'required_if:role,4|in:1',
but when I write it that way I can't pass the validation when it doesn't equal to 1
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class userRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return false;
}
public function rules()
{
return [ 'affirm_agency' => 'required_if:role,4|in:1',];
}
}
I typically handle something like this like so:
/**
* Validation rules
*
* #return array
*/
public function rules() : array
{
$rules = [
// add whatever default rules you always want
];
if (request()->role == 4) {
$rules['affirm_agency'] = 'required|in:1';
}
return $rules;
}
Please see laravel documentation
in:foo,bar,...
The field under validation must be included in the given list of values. Since this rule often requires you to implode an array, the Rule::in method may be used to fluently construct the rule:
use Illuminate\Validation\Rule;
Validator::make($data, [
'zones' => [
'required',
Rule::in(['first-zone', 'second-zone']),
],
]);
You need to update you authorize function as it is returning false now.
public function authorize()
{
return true;
}
I'm new to Laravel so sorry for my newbie question:
I have in input field, it must be valid only if it's zero or if it's value exists in a "products" db table.
I'm using Form Request Validation with Laravel 5.2.
This is my code:
class CreateRequest extends Request
{
/**
* 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 [
'product_id' => 'sometimes|exists:products,id'
];
}
}
This works for the database validation, but how can I make it valid if it's zero?
Thanks for your help!
This might be helpful
public function rules()
{
return [
'product_id' => 'integer|size:0|exists:products,id'
];
}
You have to create a Custom validation rules
If you are using 5.6 you can create a custom rule or pass a closure
class CreateRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
public function rules()
{
return [
'product_id' => ['integer', function($attribute, $value, $fail) {
$product = Product::find($value);
if ($value != 0 && !$product ) {
return $fail($attribute.' is invalid.');
}
},
];
}
}
Hope this helps
I would like to ask how should I handle validation on multiple scenarios using FormRequest in L5? I know and I was told that I can create saparate FormRequest files to handle different validations but it is very redundant and also noted that I would need to inject it into the controller manually using the use FormRequest; keyword. What did previously in L4.2 is that I can define a new function inside my customValidator.php which then being called during controller validation via trycatch and then the data is being validated by service using the below implementation.
class somethingFormValidator extends \Core\Validators\LaravelValidator
{
protected $rules = array(
'title' => 'required',
'fullname' => 'required',
// and many more
);
public function scenario($scene)
{
switch ($scene) {
case 'update':
$this->rules = array(
'title' => 'required',
'fullname' => 'required',
// and other update validated inputs
break;
}
return $this;
}
}
Which then in my LaravelValidator.php
<?php namespace Core\Validators;
use Validator;
abstract class LaravelValidator {
/**
* Validator
*
* #var \Illuminate\Validation\Factory
*/
protected $validator;
/**
* Validation data key => value array
*
* #var Array
*/
protected $data = array();
/**
* Validation errors
*
* #var Array
*/
protected $errors = array();
/**
* Validation rules
*
* #var Array
*/
protected $rules = array();
/**
* Custom validation messages
*
* #var Array
*/
protected $messages = array();
public function __construct(Validator $validator)
{
$this->validator = $validator;
}
/**
* Set data to validate
*
* #return \Services\Validations\AbstractLaravelValidator
*/
public function with(array $data)
{
$this->data = $data;
return $this;
}
/**
* Validation passes or fails
*
* #return Boolean
*/
public function passes()
{
$validator = Validator::make(
$this->data,
$this->rules,
$this->messages
);
if ($validator->fails())
{
$this->errors = $validator->messages();
return false;
}
return true;
}
/**
* Return errors, if any
*
* #return array
*/
public function errors()
{
return $this->errors;
}
}
and then finally this is how i call the scenarios inside services like this
public function __construct(somethingFormValidator $v)
{
$this->v = $v;
}
public function updateSomething($array)
{
if($this->v->scenario('update')->with($array)->passes())
{
//do something
else
{
throw new ValidationFailedException(
'Validation Fail',
null,
$this->v->errors()
);
}
}
So the problem is now since i have migrated to L5 and L5 uses FormRequest, how should I use scenario validation in my codes?
<?php namespace App\Http\Requests;
use App\Http\Requests\Request;
class ResetpasswordRequest extends Request {
/**
* 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 [
'login_email' => 'required',
'g-recaptcha-response' => 'required|captcha',
];
}
public function messages()
{
return [
'login_email.required' => 'Email cannot be blank',
'g-recaptcha-response.required' => 'Are you a robot?',
'g-recaptcha-response.captcha' => 'Captcha session timeout'
];
}
public function scenario($scene)
{
switch ($scene) {
case 'scene1':
$this->rules = array(
//scenario rules
);
break;
}
return $this;
}
}
also how should I call it in the controller?
public function postReset(ResetpasswordRequest $request)
{
$profile = ProfileService::getProfileByEmail(Request::input('login_email'));
if($profile == null)
{
$e = array('login_email' => 'This email address is not registered');
return redirect()->route('reset')->withInput()->withErrors($e);
}
else
{
//$hash = ProfileService::createResetHash($profile->profile_id);
$time = strtotime('now');
$ip = Determinator::getClientIP();
MailProcessor::sendResetEmail(array('email' => $profile->email,
'ip' => $ip, 'time' => $time,));
}
}
I believe the real issue at hand is everything is validated through the form request object before it reaches your controller and you were unable to set the appropriate validation rules.
The best solution I can come up with for that is to set the validation rules in the form request object's constructor. Unfortunately, I am not sure how or where you are able to come up with the $scene var as it seems to be hard-coded in your example as 'update'.
I did come up with this though. Hopefully reading my comments in the constructor will help further.
namespace App\Http\Requests;
use App\Http\Requests\Request;
class TestFormRequest extends Request
{
protected $rules = [
'title' => 'required',
'fullname' => 'required',
// and many more
];
public function __construct()
{
call_user_func_array(array($this, 'parent::__construct'), func_get_args());
// Not sure how to come up with the scenario. It would be easiest to add/set a hidden form field
// and set it to 'scene1' etc...
$this->scenario($this->get('scenario'));
// Could also inspect the route to set the correct scenario if that would be helpful?
// $this->route()->getUri();
}
/**
* 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 $this->rules;
}
public function scenario($scene)
{
switch ($scene) {
case 'scene1':
$this->rules = [
//scenario rules
];
break;
}
}
}
You can use laratalks/validator package for validation with multiple scenarios in laravel. see this repo