How to use 2 attributes in Validation Rule Class Laravel - laravel

How can I use two attributes in the Validation Rule of Laravel. This is my function where I have 2 variables groupid and grouppassword. Currently only groupid is taken.
public function groupPasswordValidationReal(Request $request){
$groupid = $request->input('groupid');
$grouppassword = $request->input('grouppassword');
$validator = \Validator::make($request->all(), [
'groupid' => [new ValidRequest] //How to pass grouppassword in this function???
]);
return redirect('home')->with('success', 'Request is send!');
}
How can I pass both to my Validation Class? Here you can see the functions in the Validation Class.
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
use App\Group;
class ValidTest implements Rule
{
public function passes($attribute, $value)
{
//$value is groupid
$validPassword = Group::where([['idgroups', $value],['group_password', /*Here I need grouppassword*/]])->first();
if($validPassword){
return true;
}else{
return false;
}
}
public function message()
{
return 'Wrong Password!';
}
}

Add property and constructor to your ValidTest class. Pass the required value as an argument to the new object.
ValidTest.php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
use App\Group;
class ValidTest implements Rule
{
/**
* The group password.
*
* #var string
*/
public $groupPassword;
/**
* Create a new rule instance.
*
* #param \App\Source $source
* #param string $branch
* #return void
*/
public function __construct($groupPassword)
{
$this->groupPassword = $groupPassword;
}
public function passes($attribute, $value)
{
//$value is groupid
//$this->groupPassword is group_password
$validPassword = Group::where([['idgroups', $value],['group_password', $this->groupPassword]])->first();
if($validPassword){
return true;
}else{
return false;
}
}
public function message()
{
return 'Wrong Password!';
}
}
Controller
public function groupPasswordValidationReal(Request $request){
$groupid = $request->input('groupid');
$grouppassword = $request->input('grouppassword');
$validator = \Validator::make($request->all(), [
'groupid' => new ValidTest($grouppassword)
]);
return redirect('home')->with('success', 'Request is send!');
}
Source : custom-validation-rules-in-laravel-5-5
Note : This is not a tested solution.

Use the Rule constructor like
class ValidUser implements Rule
{
private $grouppassword;
/**
* Create a new rule instance.
*
* #return void
*/
public function __construct($grouppassword)
{
$this->grouppassword = $grouppassword;
}
/**
* Determine if the validation rule passes.
*
* #param string $attribute
* #param mixed $value
* #return bool
*/
public function passes($attribute, $value)
{
// $this->grouppassword
// $groupid = $value
// Do your logic...
return true;
}
/**
* Get the validation error message.
*
* #return string
*/
public function message()
{
return 'The :attribute must something...';
}
}
Usage
Route::post('/user', function (Request $request) {
// Parameter validation
$validator = Validator::make($request->all(), [
'groupid' => 'required',
'grouppassword' => ['required', new ValidUser($request->groupid), ]
]);
if ($validator->fails())
return response()->json(['error' => $validator->errors()], 422);
});

I think you could use a closure there. So something like this:
$validator = \Validator::make($request->all(), function() use ($groupid, $grouppassword) {
$validPassword = Group::where([['idgroups', $value],['group_password', $grouppassword]])->first();
if($validPassword){
return true;
}else{
return false;
}
}
I haven't run it though.

If you are calling the validation from a Request class, you can access the extra value as e.g.
request()->validate([
'groupid' => [new ValidTest(request()->input('grouppassword'))]
]);

Related

Laravel separate functions to service class

I am refactoring my application according to this article:
https://laravel-news.com/controller-refactor
I had all logic in my controllers so it seems like a good idea to do this. But now I have some struggles with the update function.
class CategoryController extends Controller
{
/**
* Display a listing of the resource.
*
* #param Request $request
* #return JsonResponse
*/
public function index(Request $request): JsonResponse
{
$categories = Category::where('created_by', $request->company->id)->orderBy('order')->get();
return response()->json($categories);
}
/**
* Store a newly created category
*
* #param StoreCategoryRequest $request
* #param CategoryService $categoryService
* #return JsonResponse
*/
public function create(StoreCategoryRequest $request, CategoryService $categoryService): JsonResponse
{
$category = $categoryService->createCategory($request);
if ($category) {
return response()->json(['success' => true, 'message' => 'api.category.save.success']);
}
return response()->json(['success' => false, 'message' => 'api.category.save.failed']);
}
/**
* Update the specified resource in storage.
*
* #param StoreCategoryRequest $request
* #param Category $category
* #param CategoryService $categoryService
* #return JsonResponse
*/
public function update(StoreCategoryRequest $request, Category $category, CategoryService $categoryService): JsonResponse
{
try {
$result = $categoryService->updateCategory($request, $category);
if ($result) {
return response()->json(['success' => true, 'message' => 'api.category.update.success']);
}
return response()->json(['success' => false, 'message' => 'api.category.update.failed']);
} catch (\Exception $e) {
return response()->json(['success' => false, 'message' => 'api.category.update.failed']);
}
}
}
And the route:
Route::put('category/{category}', [CategoryController::class, 'update']);
Laravel is getting the category based on the id, but I don't know how to handle this correctly in my controller. I autoload the CategoryService there, so that I can use the update function. After that I give the actual category as a property to that service, I also don't know if handling the exceptions like this is the 'best way'.
class CategoryService
{
public function createCategory(Request $request): bool {
$category = new Category();
$category->fill($request->all());
$category->created_by = $request->company->id;
return $category->save();
}
/**
* #throws \Exception
*/
public function updateCategory(Request $request, Category $category): bool {
if($this->isOwnerOfCategory($category, $request->company)) {
$category->fill($request->all());
$category->created_by = $request->company->id;
return $category->save();
}
throw new \Exception('Not the owner of the category');
}
private function isOwnerOfCategory(Category $category, Company $company): bool
{
return $category->created_by === $company->id;
}
}
The create function/ flow feels good. But the update function feels like properties are coming from everywhere and the code is a lot less readable. Are there any suggestions to improve this?

how to update email admin with Custom Rule?

I am trying to edit the admin information using a Custom Rule, I want the admin to be able to reset her email when she edits her information but not to reset her email to others.
This is my Custom Rule checkAdminEmailExist.php, please correct it wherever there is a problem
<?php
namespace App\Rules;
use App\Repositories\Interfaces\AdminRepositoryInterface;
use Illuminate\Contracts\Validation\Rule;
use Illuminate\Support\Facades\Auth;
class checkAdminEmailExist implements Rule
{
public AdminRepositoryInterface $admin_repository;
public function __construct()
{
$this->admin_repository = app()->make(AdminRepositoryInterface::class);
}
public function passes($attribute, $value):bool
{
if($value == Auth::user()->email) {
return true;
}
if ($this->admin_repository->checkAdminEmailExist($value)) {
return false;
}
return true;
}
public function message():string
{
return 'The :attribute duplicate';
}
}
You can leverage try to leverage afterHook like
class UpdateUserRequest 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<string, mixed>
*/
public function rules()
{
return [
/** Other rules **/
'email' => ['required', 'email'],
];
}
/**
* Configure the validator instance.
*
* #param \Illuminate\Validation\Validator $validator
* #return void
*/
public function withValidator($validator)
{
$validator->after(function ($validator) {
$alreadyExists = DB::table('users')
->where('id', '<>', auth()->id())
->where('email', $this->email)
->exists();
if (auth()->user()->isAdmin() && $alreadyExists) {
$validator->errors()->add('email', 'The email address is already taken');
}
});
}
}
You will need to implement a isAdmin() method on User model to verify whether the user is an admin or not.
//USer model
public function isAdmin()
{
//Determine if the user has the role of admin
return $this->hasRole('admin');
// or if there's a is_admin boolean column on users table
return $this->is_admin === true;
}
Laravel Docs - Validation - After Validation Hook

Laravel faker email that pass the email validation email:rfc,dns

I get
invalid email address
when using faker to fill the email fields of my app.
'email' => ['required', 'email:rfc,dns']
is there a way to make faker spit out valid email addresses that pass these tests?
I tried safeEmail and some others but they still fail
I have the same problem and it's all related to the Egulias\EmailValidator\Validation\DNSCheckValidation class used by Laravel to validate an email (as you can see here example is a reserved top level domain).
There's no way of avoiding that an email created with faker passes this validation and, IMO, it's just as it should be. But we need to do our tests don't we?
There's a simple way (that can be used also in FormRequest or whatever you're using that implements the validator):
// In your test:
$this->post('my-url', ['email' => $this->faker->unique()->safeEmail])
// In your code
$emailRules = 'email:rfc';
if(!app()->runningUnitTests()) {
$emailRules .= ',dns';
}
$validator = Validator::make($data, ['email' => ['required', $emailRules]]);
// [...]
Another way is to create a custom email validation rule that encapsulate the runningUnitTests logic (and I think it's much more cleaner)
<?php
namespace App\Rules;
use Egulias\EmailValidator\EmailValidator;
use Egulias\EmailValidator\Validation\DNSCheckValidation;
use Egulias\EmailValidator\Validation\MultipleValidationWithAnd;
use Egulias\EmailValidator\Validation\NoRFCWarningsValidation;
use Egulias\EmailValidator\Validation\RFCValidation;
use Egulias\EmailValidator\Validation\SpoofCheckValidation;
use Illuminate\Contracts\Validation\Rule;
use Illuminate\Validation\Concerns\FilterEmailValidation;
/** #codeCoverageIgnore */
class ValidEmail implements Rule
{
protected array $parameters;
/**
* Create a new rule instance.
*
* #return void
*/
public function __construct(array $parameters = [])
{
$this->parameters = $parameters;
}
/**
* Determine if the validation rule passes.
*
* #param string $attribute
* #param mixed $value
* #return bool
*/
public function passes($attribute, $value)
{
if (! is_string($value) && ! (is_object($value) && method_exists($value, '__toString'))) {
return false;
}
$validations = collect($this->parameters)
->unique()
->map(function ($validation) {
if ($validation === 'rfc') {
return new RFCValidation();
} elseif ($validation === 'strict') {
return new NoRFCWarningsValidation();
} elseif ($validation === 'dns' && !app()->runningUnitTests()) {
return new DNSCheckValidation();
} elseif ($validation === 'spoof') {
return new SpoofCheckValidation();
} elseif ($validation === 'filter') {
return new FilterEmailValidation();
}
return null;
})
->values()
->filter()
->all() ?: [new RFCValidation()];
return (new EmailValidator)->isValid($value, new MultipleValidationWithAnd($validations));
}
/**
* Get the validation error message.
*
* #return string
*/
public function message()
{
return __('validation.email');
}
}
Than in your code:
$validator = Validator::make($data, ['email' => ['required', new ValidEmail()]]);
// OR
$validator = Validator::make($data, ['email' => ['required', new ValidEmail(['rfc','filter','dns'])]]);
// [...]

laravel 4 logout after loginning directly?

i try to login by email and password
auth::attempt success login but when redirect to link after login it logout by itself , on localhost all things good but when i upload my project on host this problem occured
login function
public function login(){
$validator=Validator::make(Input::all(),array(
'email' => 'email|required',
'password' => 'required'
));
if($validator->fails()){
$messages = $validator->messages();
$msg='';
foreach ($messages->all() as $message)
{
$msg .= "<li>".$message."</li><br />";
}
return $msg;
}
else{
$email = Input::get('email');
$password = Input::get('password');
$user=Auth::attempt(array(
'email' => $email,
'password' => $password,
'activated' => 1
),true);
//die(Auth::user()->rule);
if($user){
//Auth::login($user);
//die('1111111111');
return 1;
}
else{
return "يوجد خطأ فى البريد الإلكترونى أو كلمة المرور";
}
//die('11111111111111111');
}
}
model
<?php
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableInterface;
use Mmanos\Social\SocialTrait;
class User extends Eloquent implements UserInterface, RemindableInterface
{
use SocialTrait;
protected $fillable = array('username','password','rule', 'email','active','phone','address','add_info','image','first_name','sec_name','country','area','baqah_id');
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'users';
public static function is_admin(){
if(Auth::user()->rule=='admin'){
return true;
}
return false;
}
/*
one to one relation
*/
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = array('password');
/**
* Get the unique identifier for the user.
*
* #return mixed
*/
public function getAuthIdentifier()
{
return $this->getKey();
}
/**
* Get the password for the user.
*
* #return string
*/
public function getAuthPassword()
{
return $this->password;
}
/**
* Get the e-mail address where password reminders are sent.
*
* #return string
*/
public function getReminderEmail()
{
return $this->email;
}
public function getRememberToken()
{
return $this->remember_token;
}
public function setRememberToken($value)
{
$this->remember_token = $value;
}
public function getRememberTokenName()
{
return 'remember_token';
}
}
?>

Laravel 5 FormRequest validator with multiple scenarios

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

Resources