Laravel validation exists in this table OR that table - laravel

I need to validate the existence of an API request parameter
but check it in one of two tables, users and teachers
something like this
$rules = ['apiToken' => 'required|string|min:70|exists:users,api_token ((OR)) exists:teachers,api_token']
is there a way to achieve this using Laravel validator?

For something like this you will probably need to use custom validation:
$rules = [
'apiToken' => [
'required', 'string', 'min:70', function ($attribute, $value, $fail) {
if (!DB::table('users')->where('api_token', $value)->exists() || !DB::table('teachers')->where('api_token', $value)->exists()) {
return $fail("The provided $attribute is not valid.");
}
}
]
];
You can change the returned error message by editing the text passed to the $fail function.

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 - exclude current email from unique validation

I am trying to use unique on an email address in Laravel 5.5 validation like this..
$rules = [
'email' => 'email|unique:users,email,
];
This is working and is checking the 'users' table in the 'email' column
But if the email address is the same as the currently saved one then this also fails validation.
Is there a way I can add an exception rule to the validation to ignore $user-email?
The unique rule takes a third parameter: a record to ignore, see "Forcing A Unique Rule To Ignore A Given ID".
Pass in the ID of the record you do not wish to be included in the unique test, e.g:
$rules = [
'email' => 'email|unique:users,email,' . $user->id
];
THIS IS AN EASY SOLUTION
Just add $this->route('id') as the third parameter
if your route was defined like this:
Route::put('{company}', 'CompanyController#update')
->name('update');
then your parameter name is "company"
So in your FormRequest:
public function rules()
{
$rules = [
'url' => [
'required',
'url',
'unique:companies,url,'.$this->route('company') ?? 0
],
];
// dd($rules); << check yourself
return $rules;
}
This FormRequest works the same for insert or update.
this line will instruct to ignore "0" in case of insert
$this->route('company') ?? 0

unique value with custom request laravel 5.3

i have custom request in laravel ..
this is the code
public function rules()
{
if($this->ajax())
{
return [];
}
else
{
return
[
'username'=> 'required|min:3|max:30|unique:users',
'password'=> 'required|min:6',
'email'=>'required|min:3|max:35|unique:users',
'permission'=>'required',
'phone'=>'required',
'division'=>'required',
];
}
}
and i need to to ignore the current id from validation
i tried this
public function rules()
{
if($this->ajax())
{
return [];
}
else
{
return
[
'username'=> 'required|min:3|max:30|unique:users,id'.$this->id,
'password'=> 'required|min:6',
'email'=>'required|min:3|max:35|unique:users',
'permission'=>'required',
'phone'=>'required',
'division'=>'required',
];
}
}
but its ignoring the whole user name from validation not just the current id ..
Use auth()->user()->id instead of $this->id to get current user's ID.
Also, I'm not sure about the syntax you're using when trying to add ignoring ID. From unique() rule docs:
To instruct the validator to ignore the user's ID, we'll use the Rule class to fluently define the rule. In this example, we'll also specify the validation rules as an array instead of using the | character to delimit the rules:
use Illuminate\Validation\Rule;
Validator::make($data, [
'email' => [
'required',
Rule::unique('users')->ignore($user->id),
],
]);

Email validation rule in Laravel?

I do email validation using the simple rule:
'email' => 'required|email|unique:users,email',
How do I modify the option unique so that it will work only if the entered email is different from the primordial?
A sample:
The field email contains the default value from table users: example#gmail.com
Then I push the button without making any changes in the form I should not check unique:users.
Otherwise, if I even changed one symbol in example#gmail.com1 I must validate the incoming value using: unique:users.
You can find an example here https://laracasts.com/discuss/channels/requests/laravel-5-validation-request-how-to-handle-validation-on-update
You will need to have multiple rules depending on the request method (update or create) and you can pass a third parameter to unique to ensure no fail if you know the user / email
'user.email' => 'required|email|unique:users,email,'.$user->id,
Switch for method
switch($this->method())
{
...
}
I did this using the conditional checks:
$validator = Validator::make($request->all(), []);
$validator->sometimes('email', 'unique:users,email', function ($input) {
return $input->email !== Auth::user()->email;
});
I think it is as loophole in laravel validation.
I update the code for email validation. This is working fine for me.
'email' => [
'required', 'email:rfc',
function($attribute, $value, $fail) {
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
$fail($attribute . ' is invalid.');
}
}],

Using form request specific custom validation attributes

Using Laravel's localization (http://laravel.com/docs/5.1/localization) I have created some custom validation attributes to provide friendlier validation errors (for instance, 'First Name' instead of first name etc).
I am using form requests (http://laravel.com/docs/5.1/validation#form-request-validation) in order to validate user submissions and there are scenarios where I would like to provide store-specific custom validation attributes (for instance, I may have a 'name' field that is Brand Name in one context, and Product Name in another).
The messages() method allows me to specify validation rule specific message overrides, but that isn't ideal as it's not the validation message as such we need to override, just the attribute name (for example, if we have 5 validation rules for 'email', we have to provide 5 overrides here, rather than one override for, let's say, Customer Email).
Is there a solution to this? I note references to formatValidationErrors() and formatErrors() in the Laravel documentation, but there is not really any information on how to correctly override these, and I've not had much luck in trying.
You can override the attribute names, which is defaulting to whatever the field name is.
With form request
In your form request class override the attributes() method:
public function attributes()
{
return [
'this_is_my_field' => 'Custom Field'
];
}
With controller or custom validation
You can use the 4th argument to override the field names:
$this->validate($request, $rules, $messages, $customAttributes);
or
Validator::make($data, $rules, $messages, $customAttributes);
Simple working example
Route::get('/', function () {
// The data to validate
$data = [
'this_is_my_field' => null
];
// Rules for the validator
$rules = [
'this_is_my_field' => 'required',
];
// Custom error messages
$messages = [
'required' => 'The message for :attribute is overwritten'
];
// Custom field names
$customAttributes = [
'this_is_my_field' => 'Custom Field'
];
$validator = Validator::make($data, $rules, $messages, $customAttributes);
if ($validator->fails()) {
dd($validator->messages());
}
dd('Validation passed!');
});
As detailed in my question, I was looking for a way to provide specific form request stores (http://laravel.com/docs/5.1/validation#form-request-validation) with custom attribute names.
The Laravel documentation only covers two methods for Requests in this context - rules() and authorize(). I was aware there is a messages() method to provide validation specific custom error messages, but it also appears there is an attributes() method, which fits my requirements exactly:
public function attributes()
{
return [
'name' => 'Product Name'
]
}
This overrides the attribute name in the context of my store.

Resources