Rule::notIn([]) - is not catching the duplicate entry? - laravel

Rule::notIn([]) - is not catching the duplicate entry? is not working for me?
I have a group of people that I don't want to enter the same email address. if a new member joins, they need to enter a unique email address for that group. [$this->familyEmails] holds the email already in the group. It is a one dimensional array. Here is the result of two dd() calls.
And the Rules function (notice that the dd() calls that produced the output in the above graphics is commented out:
public function rules(){
//dd('family emails',$this->familyEmails);
$w = [];
foreach($this->familyEmails as $femail){
if($femail != $this->email && array_search(strtolower($femail),$w)==null)
$w[] = strtolower($femail);
}
// dd(['rules_array'=>$w]);
return [
'email' => ['nullable','string', 'email', 'max:255',Rule::notIn($w)],
];
}
and the Custom Messages function
public function customMessages(){
return [
'email.string' => 'Email Address: The email you entered is not a proper email address; please change.',
'email.email' => 'Email Address: The email you entered is not a proper email address; please change.',
'email.max' => 'Email Address: The email you entered is too long; please change.',
'email.not_in' => 'This email address is already used in the family. Please enter another Email Address',
];
}
I also have an updated() function which validates data entered on the fly
public function updated($propertyName){
$data = $this->validateOnly($propertyName, $this->rules(), $this->customMessages());
$this->resetErrorBag($propertyName);
$this->changesMade = true;
//dd(['data'=>$data,'rules'=>$this->rules()]);
}
After I enter in an email address into the form, the dd() that you see commented out just above produces the following when i enter in an email that is in the notIn rule. The dd() seen just above should not be reached, but it is.

okay, i decided to answer my own question as it may help others.
I was creating the Rule:notIn([]) inside the Rules() function. But that caused problems.
As it seems that the Rules() function (of course) runs everytime you call the validate() helper. the answer was right in front of me.
When I put the loop in the Mount() function, I achieved my objective.
Before I did that, when creating the rules noIn array in the Rules() function it contained "sam#whistle.com". When I went to enter an email address for another person in the group, I entered "sam#whistle.com" .
So when I typed in the duplicate email, the rules() function ran, and recreated the rules array, and used the email that I typed in and it took it out of the rule. I used the $this->email which is the public property on the component. So I had to put it in the mount() function.
As I write this, I realize I need to put the array back in the Rules() function, and reference the model where the email address is stored, not reference the public property on the component that stores the typed in value.
$w = [];
foreach($this->familyEmails as $femail){
if($femail != $this->person->email && array_search(strtolower($femail),$w)==null)
$w[] = strtolower($femail);
}
instead of $this->email, I reference the model for the email address.

Related

Making Laravel 9 validation rule that is unique on 2 columns

I am trying to update a row in the pages table.
The slug must be unique in the pages table on the slug and app_id field combined.
i.e. there can be multiple slugs entitled 'this-is-my-slug' but they must have unique app_id.
Therefore I have found that formula for the unique rule is:
unique:table,column,except,idColumn,extraColumn,extraColumnValue
I have an update method and getValidationRules method.
public function update($resource,$id,$request){
$app_id=22;
$request->validate(
$this->getValidationRules($id,$app_id)
);
// ...store
}
When I test for just a unique slug the following works:
public function getValidationRules($id,$app_id){
return [
'title'=> 'required',
'slug'=> 'required|unique:pages,slug,'.$id
];
}
However, when I try and add the app_id into the validation rules it returns server error.
public function getValidationRules($id,$app_id){
return [
'title'=> 'required',
'slug'=> 'required|unique:pages,slug,'.$id.',app_id,'.$app_id
];
}
I have also tried to use the Rule facade, but that also returns server error. Infact I can't even get that working for just the ignore id!
public function getValidationRules($id,$app_id){
return [
'title'=> 'required',
'slug'=> [Rule::unique('pages','slug')->where('app_id',$app_id)->ignore($id)]
];
}
Any help is much appreciated :)
Thanks for the respsonses. It turned out a couple of things were wrong.
Firstly if you want to use the Rule facade for the validation rules, make sure you've included it:
use Illuminate\Validation\Rule;
The other method for defining the validation rule seems to be limited to the following pattern:
unique:table,column,except,idColumn
The blog post that I read that showed you could add additional columns was for laravel 7, so i guess that is no longer the case for laravel 9.
Thanks for your responses and help in the chat!
I recommend you to add your own custom rule.
First run artisan make:rule SlugWithUniqueAppIdRule
This will create new file/class inside App\Rules called SlugWIthUniqueAppRule.php.
Next inside, lets add your custom rule and message when error occured.
public function passes($attribute, $value)
{
// I assume you use model Page for table pages
$app_id = request()->id;
$pageExists = Page::query()
->where('slug', $slug)
->where('app_id', $app_id)
->exists();
return !$pageExists;
}
public function message()
{
return 'The slug must have unique app id.';
}
Than you can use it inside your validation.
return [
'title'=> 'required|string',
'slug' => new SlugWithUniqueAppIdRule(),
];
You can try it again and adjust this custom rule according to your needs.
Bonus:
I recommend to move your form request into separate class.
Run artisan make:request UpdateSlugAppRequest
And check this newly made file in App\Http\Requests.
This request class by default will consists of 2 public methods : authorize() and rules().
Change authorize to return true, or otherwise this route can not be accessed.
Move your rules array from controller into rules().
public function rules()
{
return [
'title'=> 'required|string',
'slug' => new SlugWithUniqueAppIdRule(),
];
}
To use it inside your controller:
public function update(UpdateSlugAppRequest $request, $resource, $id){
// this will return validated inputs in array format
$validated = $request->validated();
// ...store process , move to a ServiceClass
}
This will make your controller a lot slimmer.

Validation of an email address in Laravel

I have created an edit form in my Laravel application form which accepts an email address. I have used Laravel validation on the server-side. It is validating the email address correctly when I pass a clearly invalid value like 'xxxxxx'.
But the problem is when I send an email address with only a top level domain and no dot like 'xxxxxx#yyyyyy', it accepts it as a valid email address.
How can I validate the email address to ensure it's using a proper domain?
With Laravel 7: you can use
'email' => 'email:rfc,dns'
You can simply do:
$validator = Validator::make($request->all(), [
'Email'=>'required|email'
]);
Try this:
$this->validate($request, [
'email' => 'required|regex:/(.+)#(.+)\.(.+)/i',
]);
It is not a Laravel issue. That is technically a valid email address.
Notice that if you tell the browser to validate the email address, it will also pass.
But you can use package EmailValidator for validating email addresses.
At first, also check these: https://laravel.com/docs/6.x/validation#rule-email
Or,
use the checkdnsrr function.
<?php
$email = 'email#gmail.com';
list($username, $domain) = explode('#', $email);
if (checkdnsrr($domain, 'MX')) {
echo "verified";
}
else {
echo "failed";
}
Laravel email validation and unique email insert database use for code:
'email_address' => 'required|email|unique:customers,email_address'

What is the correct order of form validation and retrieving input values?

I wonder if I should do form validation before retrieving input values or vice versa.
I usually do validation first as I see no benefit in trying to access input values that might not be valid. However, a coworker looked at my code recently and found it strange. Is there any correct order for these steps?
public function createGroups(Request $request)
{
$this->validate($request, [
'courses' => 'required_without:sections',
'sections' => 'required_without:courses',
'group_set_name' => 'required',
'group_number' => 'required|integer|min:1'
]);
$courses = $request->input('courses');
$sections = $request->input('sections');
$group_set_name = $request->input('group_set_name');
$group_number = $request->input('group_number');
Positioning the validation for your controller logic at the beginning of a method is probably the way to go here, as you have required parameters defined. If you receive data that does not fully satisfy the requirements, you produce a validation error back to the user. This follows the productive "Fail Fast" line of thinking: https://en.wikipedia.org/wiki/Fail-fast
It's also important that you're not using any data that hasn't passed your stringent requirements from validation. Data that fails validation should no longer be trusted. Unless there's some other reason you need to be, say, logging any incoming data from the frontend, the order here looks good to me.
I totally agree with #1000Nettles response, to elaborate a little bit more on his/her answer (who should be the accepted one): There isn't any need to continue with your business logic when the data doens't comply with your specifications. Let's say you expected a string of a N characters long, because you defined your database with that limitation (in order to optimize the db desing), will you try to persist it even when it'll throw an exception? Not really.
Besides, Laravel has a particular way to extract validation classes: Form Request. This are injected in controllers. When a call reach the controller it means that already passed the validation, if not, an 422error be returned.
Create a custom request and keep the mess out of your controller, it doesn't even hit your controller function if validation failed and can just grab the data in your controller if validation passed.
php artisan make:request GroupRequest
In app/Http/Requests/GroupRequest.php:
public function authorize()
{
// return true;
return request()->user()-isAdmin; // <-- example, but true if anyone can use this form
}
public function rules()
{
return [
'courses' => ['required_without:sections'],
'sections' => ['required_without:courses'],
'group_set_name' => ['required'],
'group_number' => ['required', 'integer', 'min:1'],
];
}
The best part is you can even manipulate the data in here (GroupRequest.php) after it has been validated:
public function validated()
{
$validated = $this->getValidatorInstance()->validate();
// EXAMPLE: hash password here then just use new hashed password in controller
$validated['password'] = Hash::make($validated['password']);
return $validated;
}
In your controller:
public function createUser(UserRequest $request) // <- in your case 'GroupRequest'
{
$validated = $request->validated(); // <-- already passed validation
$new_user = User::create($validated); // <-- password already hashed in $validated
return view('dashboard.users.show')->with(compact('user'));
}
In your case, if you use my GroupRequest block above, you can return to view in 1 line of code:
public function createGroups(GroupRequest $request)
{
return view('example.groups.show')->with($request->validated()); // <-- already an array
}
In you blade view file, you can then use your variables like {{ $group_set_name }} and {{ $group_number }}

Password resetting in laravel when email address is not unique

This might sound like an antipattern or a weak system design, but the client of my app has demanded that there can be multiple users with same email address.
So I added another unique column named username to the users table and removed ->unique() constraint from email column.
Registration, Login are working fine but the problem arises during the password reset.
Consider the scenario:
username - johndoe, email - john#example.com
username - janedoe, email - john#example.com
username - jimmydoe, email - john#example.com
If any one of them makes a request for a password reset link, they would have to use johndoe#example.com as their email. So which user's password is actually going to be reset when they click on reset link from mail? Turns out, the first user, in this case, johndoe. Even if the request was made by janedoe or jimmydoe.
So how do I reset password for a single username, rather than an email? What changes should I make in the ForgotPasswordController and/or ResetPasswordController controllers to solve this? Or, do I have to make changes in the core framework? If so, where and how?
Tested in Laravel 5.3 [This answer modifies some core files(you may override it if capable) and it's not a clean solution.]
Ask user for the unique username value instead of email on password forget form.
Override the sendResetLinkEmail() method in ForgotPasswordController.php as folows. [Originally written in SendsPasswordResetEmails.php].
public function sendResetLinkEmail(Request $request)
{
$this->validateEmail($request);
$response = $this->broker()->sendResetLink(
$request->only('username')
);
return $response == Password::RESET_LINK_SENT
? $this->sendResetLinkResponse($response)
: $this->sendResetLinkFailedResponse($request, $response);
}
you would also need to override the validateEmail() method.
protected function validateEmail(Request $request)
{
$this->validate($request, ['username' => 'required']);
}
Add username field instead of email on password reset form.
Override rules() in ResetPasswordController.php to over come the email field change.
protected function rules()
{
return [
'token' => 'required',
'username' => 'required',
'password' => 'required|confirmed|min:6',
];
}
Also override the credentials() in ResetPasswordController.php
protected function credentials(Request $request)
{
return $request->only(
'username', 'password', 'password_confirmation', 'token'
);
}
Update or override the getEmailForPasswordReset() method in Illuminate\Auth\Passwords\CanResetPassword.php to the folowing.
public function getEmailForPasswordReset()
{
return $this->username;
}
Laravel uses key-value pair to find the user and send email. If you pass 'username => 'xyz' it will look for the first record with value 'xyz' in username field.
Note: The unique column in users table is expected as username.
Illuminate\Auth\Passwords\CanResetPassword.php is a trait, and I was not able to overide the getEmailForPasswordReset method, so i just modified the core file itself.
This might sound like an antipattern or a weak system design, but the client of my app has demanded that there can be multiple users with same email address.
Then you need to rewrite this feature and ask user for some more unique information, no matter what it is going to be. Laravel provided password reset expects email to be unique and with your current design it won't work. There's no magic here. You you cannot disambiguate your user using non unique data.
You will need to rework some things for this, but I feel like the user experience is better. Generate a unique key for each user (for data hiding). There is a helper method for creating unique keys.
Then, when the email is sent out, link the button to a route that utilizes this key.
Then, modify or create that route that points to the reset password controller. You would then know which user it was referring to.
Remove the need for the user to insert their password because you'd already know who it was.

how to generate error message in laravel

Hello stackoverflow geeks, I'm in my final stages of the laravel learning curve all thanks to you guys.
However, i need to generate a warning message like "You cannot delete a role assigned to a user" every time a user tries to delete a role assigned to a user.
instead it loads a page with an sql error. how to i do it?
And how do i avoid a password that has been already been stored from being hashed again. eg:- $2y$10$p8JwI5P4yE2UFo2.vHP99.0dP2jU7ll/9w73IzUa9/yegKOSTHJWq is always hashed every time i edit a user's information.
Thanks you all who've made learning laravel easy for me by answering in time
code
public function destroy(Request $request,$id)
{
// delete
// $role = Role::find($id);
//$role->delete();
$role = Role::find ($id);
if ($role->users() !=null) {
return redirect()->back()->withInput(['warning' => 'Not allowed']);
}
$role->delete();
// redirect
Session::flash('message', 'Record successfully deleted!');
Session::flash('alert-type', 'success');
return Redirect::to('role');
}
This highly depends on how you want to handle the errors. You can either catch the sql exception and display your custom error OR what is probably better for you is to handle the incoming request, validate it and return an error if validation fails.
Here are the validation docs : https://laravel.com/docs/5.3/validation
You have multiple options on how to validate a request. Simple example to validate a title is unique in the table posts and is maximum 255 chars long:
$this->validate($request, [
'title' => 'required|unique:posts|max:255'
]);
If you cannot find a rule that is helping you simply define your own validation rule https://laravel.com/docs/5.3/validation#custom-validation-rules
Ofcourse you can also do the validation manually. In your request or in your controller (depends on your setup) just check for it
// assuming you want to delete an entry
public function delete(Request $request, $id)
{
$role = App\Role::findOrFail($id);
if ($role->users() != null) {
return redirect()->back()->withInput(['message' => 'Not allowed']);
// now you can output $message
}
$role->delete();
return ...
}

Resources