Laravel Requests Validation rules only if value is not null? - laravel

I've created a request for my update method called CandidateProfileUpdateRequest.php:
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'photo' => ['mimes:jpeg,png,jpg,gif,bmp', 'max:4096'],
'video_one' => ['mimes:mp4,mov,ogg,qt', 'max:30720'],
'video_two' => ['mimes:mp4,mov,ogg,qt', 'max:30720'],
'video_three' => ['mimes:mp4,mov,ogg,qt', 'max:30720'],
'resume' => ['mimes:doc,docx,pdf', 'max:4096'],
'job_title' => ['required'],
];
}
public function messages()
{
return [
'photo.max' => 'The photo may not be greater than 4MB.',
'video_one.max' => 'The video may not be greater than 30MB.',
'video_two.max' => 'The video may not be greater than 30MB.',
'video_three.max' => 'The video may not be greater than 30MB.',
'resume.max' => 'The resume may not be greater than 4MB.',
];
}
For these 4 fields that aren't required photo, video_one, video_two, video_three,
I only want to apply these rules, if a file is being uploaded in either of these form fields.
So for example if video_two is empty i.e. the User isn't uploading anything here, and clicks Update, it shouldn't return any rules for video_two. Is this possible?

Check out the sometimes rule.
In some situations, you may wish to run validation checks against a field only if that field is present in the data being validated. To quickly accomplish this, add the sometimes rule to your rule list:
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'photo' => ['mimes:jpeg,png,jpg,gif,bmp', 'max:4096'],
'video_one' => ['mimes:mp4,mov,ogg,qt', 'max:30720'],
'video_two' => ['sometimes', 'mimes:mp4,mov,ogg,qt', 'max:30720'],
// ^^^^^^^^^^^
'video_three' => ['mimes:mp4,mov,ogg,qt', 'max:30720'],
'resume' => ['mimes:doc,docx,pdf', 'max:4096'],
'job_title' => ['required'],
];
}

The sometimes rule didn't work. Thank you to lagbox, the nullable rule worked!

Related

Laravel FormRequest is modifying the input

I am having trouble using the Laravel Validator to validate some data. The validator is modifying properties of the input to null.
The data passed to be validated is a mix of array and objects (in this case, a model instance).
Just for clarification: I know how to use FormRequest in controllers, I am full aware that Laravel would inject the FormRequest in the methods, and FormRequest is primaly to be used to validate user data, etc, etc. The point is why the validator need to modify the data I sent to validation?
Here's an example that you can directly paste in a php artisan tinker session:
$rules = [
'users' => [
'required',
'array',
'min:1',
],
'users.*' => [
'required',
],
'users.*.name' => [
'required',
'string',
'max:255',
],
'users.*.age' => [
'required',
'integer',
],
'users.*.best_friend' => [
'required',
],
];
$data = [
'users' => [
(new \App\Models\User)->forceFill([
'name' => 'USER #1',
'age' => 30,
'best_friend' => (new \App\Models\User)->forceFill(['name' => 'User X'])
]),
],
];
echo 'BEFORE: ' . data_get($data, 'users.0.name'); // USER #1
$validator = Validator::make($data, $rules);
echo 'AFTER: ' . data_get($data, 'users.0.name'); // NULL
dd($data);
OK, the data PASSES. But the problem is that the validation modified the variable $data, setting null to the fields with these patterns: users.*.name, users.*.age and users.*.best_friend.
If I dare to validate any model attribute, it sets to null.
I debugged and I reached the source of the modification:
/vendor/laravel/framework/src/Illuminate/Validation/ValidationData.php:42:
/**
* Gather a copy of the attribute data filled with any missing attributes.
*
* #param string $attribute
* #param array $masterData
* #return array
*/
protected static function initializeAttributeOnData($attribute, $masterData)
{
$explicitPath = static::getLeadingExplicitAttributePath($attribute);
$data = static::extractDataFromPath($explicitPath, $masterData);
if (! str_contains($attribute, '*') || str_ends_with($attribute, '*')) {
return $data;
}
// here some debug info:
// $explicitPath="users"
// $attribute="users.*.name"
// $data=User
return data_set($data, $attribute, null, true);
}
I know data_set modifies by reference.
But I could not understand why the code modifies the data if there is data already there. Should not it check for data before setting to null?
The validator is making the validated properties of my model to be null. Why and how to fix?
Maybe a different approach? Maybe this could be considered an bug/improvement for the Illuminate lib?
Any help would be apreciated.
VERSIONS:
Laravel Framework 9.33.0
PHP 8.1.2
Laravel transforms the keys from your validation rules: name and age. But that didn't work as expected because users are objects. To solve that you need to call toArray() after forceFill
$data = [
'users' => [
(new \App\Models\User) -> forceFill([
'name' => 'USER #1',
'age' => 30,
'best_friend' => (new \App\Models\User) -> forceFill(['name' => 'User X'])
])->toArray(),
]
];
If you need validation for best_friend.name you need to call toArray() on that too. But without validation you will get the object as it is.

Laravel Email validation rules fail only on live server. Possible causes?

I run a customer order input through a simple form validation.
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class CreateOrderWebstoreRequest 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()
{
// Use additional Address valudation rules if Order is not picked up
// dd('debugging halt',request());
$address_rules = [];
if (! request()->is_pickup) {
$address_rules = [
'street' => 'required|min:3|max:100',
'house_number' => 'required|numeric|max:100000',
'house_number_suffix' => 'nullable|max:10',
'postal_code' => 'required|max:10',
'town' => 'required|min:3|max:50',
'country' => 'required|min:2|max:2',
'region' => 'nullable|min:3|max:50',
'owner' => 'nullable|min:3|max:25',
];
}
return array_merge([
'first_name' => 'required|min:3|max:50',
'middle_name' => 'nullable|min:1|max:15',
'last_name' => 'required|min:3|max:50',
'company_name' => 'nullable|min:3|max:50',
'email_address' => 'required|email:rfc,dns|max:50',
'telephone_number' => 'nullable|min:10|max:25',
'description' => 'nullable|min:5|max:250',
], $address_rules);
}
public function messages()
{
return [
];
}
}
The weird thing this has been working without customer having errors for months. But in de last week or so we suddenly got multiple customers complaining that the validation of their email failed (the first 'rule' that triggers the 'email' response from validation.php
The problem is that testing this on a local host we can't reproduce this, only on the live server.
The host is a shared host, dedicated for Laravel apps, currently running Laravel 6.18.15 and PHP 7.4.16. Local is running running Laravel 6.18.10 and PHP 7.4.2 We had some problems before where the host failed to update PHP, but that doesn't seem to be the case here (if even possible)
The problem is that I don't really know how to fix this or even circumvent it. Changing the email validation to
'email' => 'required|regex:/(.+)#(.+)\.(.+)/i'
is giving me an 'IDN Conversion Failed' error.

Laravel avoid duplicate entry from model

I'm building a Laravel API. I have a models called Reservations. I want to avoid that a user creates two reservations for the same product and time period.
I have the following:
$reservation = Reservation::firstOrCreate([
'listing_id' => $request->listing_id,
'user_id_from' => $request->user_id_from,
'start_date' => $request->start_date,
'end_date' => $request->end_date,
]);
Edit after comments:
I'm also using validation
$validator = Validator::make($request->all(), [
'listing_id' => 'required|exists:listings,id',
'user_id_from' => 'required|exists:users,id',
'start_date' => 'required|date_format:"Y-m-d"|after:today',
'end_date' => 'required|date_format:"Y-m-d"|after:start_date'
]);
if ($validator->fails()) {
return response()->json(['error' => 'Validation failed'], 403);
}
Validation is working properly.
End of Edit
In my model I have casted the start_date and end_date as dates.
class Reservation extends Model
{
protected $fillable = ['listing_id', 'start_date', 'end_date'];
protected $dates = [
'start_date',
'end_date'
];
....
....
Documentation says:
The firstOrCreate method will attempt to locate a database record
using the given column / value pairs
However I notice that I'm still able to insert entries with the same attributes.
Any idea what I'm doing wrong or suggestions to fix it?
Probably there's a better way than this, but you can create an static method on Reservation to do this, like:
public static function createWithRules($data) {
$exists = $this->where('product_id', $data['product_id'])->whereBetween(*date logic that i don't remember right now*)->first();
if(!$exists) {
* insert logic *
} else {
* product with date exists *
}
}
So you can call Reservation::createWithRules($data)
You can achieve this using Laravel's built in ValidateRequest class. The most simple use-case for this validation, is to call it directly in your store() method like this:
public function store(){
$this->validate($request, [
'listing_id' => 'required|unique,
'start_date' => 'required|unique,
//... and so on
], $this->messages);
$reservation = Reservation::firstOrCreate([
'listing_id' => $request->listing_id,
'user_id_from' => $request->user_id_from,
'start_date' => $request->start_date,
'end_date' => $request->end_date,
]);
}
With this, you're validating users $request with by saying that specified columns are required and that they need to be unique, in order for validation to pass.
In your controller, you can also create messages function to display error messages, if the condition isn't met.
private $messages = [
'listing_id.required' => 'Listing_id is required',
'title.unique' => 'Listing_id already exists',
//... and so on
];
You can also achieve this by creating a new custom validation class:
php artisan make:request StoreReservation
The generated class will be placed in the app/Http/Requests directory. Now, you can add a few validation rules to the rules method:
public function rules()
{
return [
'listing_id' => 'required|unique,
'start_date' => 'required|unique,
//... and so on
];
}
All you need to do now is type-hint the request on your controller method. The incoming form request is validated before the controller method is called, meaning you do not need to clutter your controller with any validation logic:
public function store(StoreReservation $request)
{
// The incoming request is valid...
// Retrieve the validated input data...
$validated = $request->validated();
}
If you have any additional question about this, feel free to ask. Source: Laravel official documentation.

Validating not all fields in form - Laravel

In laravel, I have created a form. At the moment, I am working on the validation of the input fields of this form. I ran into a problem when I tried to validate some input fields and others not. For example, mail should be validated but catering_name not (it isn't necessary to fill in this field, its an option)
I have tried all validation methods I could find. I keep getting the same error.
Method Illuminate\Validation\Validator::validatePhone does not exist.
I guess I am missing something.
I have tried:
Validator::make($request->...
$this->validate(request(), [ ...
$request->validate([ ...
Bellow, you will find all the data that should be inputted in the database.
If I remove the validation part, the data got inserted into the database. I think the problem lays with how I try to validate. Thanks for any help.
$this->validate(request(), [
'add_name' => 'required|min:3',
'add_mail' => 'required|email',
'name' => 'required|min:3',
'email' => 'required|email',
'telefone' => 'numeric|phone',
'gsm' => 'numeric|phone',
'event' => 'required|min:3',
'date_start' => 'required|date|after:tomorrow',
'date_end' => 'required|date|after_or_equal:event_date_start',
'location' => 'required|min:3',
'number' => 'required',
]);
$event = new Event;
$event->add_name = request('add_name');
$event->add_mail = request('add_mail');
$event->name = request('name');
$event->email = request('email');
$event->telefone = request('telefone');
$event->gsm = request('gsm');
$event->name = request('name');
$event->date_start = request('date_start');
$event->date_end = request('date_end');
$event->location = request('location');
$event->number = request('number');
$event->catering = request('catering');
$event->catering_name = request('catering_name');
$event->remarks = request('remarks');
$event->status = Event::STATUS_0;
$event->save();
Unfortunately phone is not one of the default validation. You can try something like:
[
'telefone' => 'required|regex:/(01)[0-9]{9}/',
]
You can see the available list of validations given by Laravel here.
There are a wide variety of more complex options depending on how important it is to you.
There are packages for easy plug and play like Laravel-Phone.
You can create your own custom validation using php artisan make:rule phone_number and then editing the new rule made:
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class PhoneNumber implements Rule
{
/**
* Determine if the validation rule passes.
*
* #param string $attribute
* #param mixed $value
* #return bool
*/
public function passes($attribute, $value)
{
// logic here, most likely some sort of regex.
}
/**
* Get the validation error message.
*
* #return string
*/
public function message()
{
return 'The :attribute must be a valid phone number.';
}
}

Laravel 5.2 required_without_all Request Issue

I'm having an issue with required_without_all. I have three elements, and at least one should be filled. My file input's name is image[]. Adding an image but leaving title and body empty still results in a validation error, even though it shouldn't.
Any thoughts on what I'm doing wrong?
public function rules()
{
return [
'title' => 'required_without_all:body,image.*',
'body' => 'required_without_all:title,image.*',
'image.*' => 'required_without_all:body,title',
];
}
public function messages()
{
return [
'title.required_without_all' => 'At least one field is required',
'body.required_without_all' => 'At least one field is required',
'image.*.required_without_all' => 'At least one field is required',
];
}
Answered here: https://stackoverflow.com/a/39089295/2101328
Basically, add image as an array and remove the * from the rules.
There was also a bug in Laravel 5.3 that prevented similar from working; see this thread: https://github.com/laravel/framework/issues/15044#issuecomment-244364706
public function rules()
{
return [
'title' => 'required_without_all:body,image',
'body' => 'required_without_all:title,image',
'image' => 'array',
'image.*' => 'required_without_all:body,title',
];
}

Resources