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'])]]);
// [...]
Related
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;
}
}
i want make field name be written in a quadrilateral
same
"andy hosam rami entaida"
or
"حسام احممد محمد متولى"
i tray
'name' => 'regex:/^[\wء-ي]+\s[\wء-ي]+\s[\wء-ي]+\s[\wء-ي]+/
in english all true put in arabic is false
regex is true i test it hear regexr.com/57s61
i can do with php in another way , so how can write in laravel ?
if(count(explode(' ',$name)) < 4)
{
$error[] ='enter full name with 4 words';
}
You can make a custom Rule class to do custom validation, in an encapsulated manner.
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class NumWords implements Rule
{
private $attribute;
private $expected;
public function __construct(int $expected)
{
$this->expected = $expected;
}
/**
* Determine if the validation rule passes.
*
* #param string $attribute
* #param mixed $value
* #return bool
*/
public function passes($attribute, $value)
{
$this->attribute = $attribute;
$trimmed = trim($value);
$numWords = count(explode(' ', $trimmed));
return $numWords === $this->expected;
}
/**
* Get the validation error message.
*
* #return string
*/
public function message()
{
return 'The '.$this->attribute.' field must have exactly '.$this->expected.' words';
}
}
Then you can use it anywhere in validation, as below:
public function rules()
{
return [
'name' => [ 'required', 'string', new NumWords(4)],
];
}
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'))]
]);
I am very new to testing, but have now found it essential to automate my testing.
I have a test that is working fine up until it gets to the link '/cart' it gets to the link '/cart' no problem, but any other link I try to click afterwards always ends up back at the cart.
here is my error after trying to navigate away from the cart.
Failed asserting that two strings are equal.
--- Expected
+++ Actual
## ##
-'http://ngwenya-mtb.dev/events'
+'http://ngwenya-mtb.dev/cart'
And here is my test script
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ExampleTest extends TestCase {
//use Illuminate\Foundation\Testing\WithoutMiddleware;
//use DatabaseTransactions;
//use withoutMiddleware;
//use DatabaseMigrations;
/**
*
* A basic functional test example.
* Please choose a unique email address for your new participant
* #return void
*/
public function testNewUserRegistration() {
$this->visit('http://ngwenya-mtb.dev/')
// View Event
->click('View event details')
->seePageIs('/event?id=30')
->click('#enter-race47')
->press('Enter yourself to this race')
->seePageIs('/events/courses/register/addtocart')
//->withSession(['This email is already registered' => 'alert-danger'])
/////////////////////////////////////////////
// Fill the register for for new user
/////////////////////////////////////////////
->type('Bingo', 'first_name')
->type('11111111', 'password')
->type('11111111', 'password_confirmation')
->type(''.substr(md5(time()), 0, 12).'#tesing.com', 'email')
//->check('terms')
->select('Male', 'gender')
->select('1985', 'year')
->select('07', 'month')
->select('21', 'day')
->select('Small', 'shirt_size')
->select('Swaziland ID', 'id_type')
->type('badassnumber', 'id_number')
->select('Swazi', 'nationality')
//Contact details Physical
->type('Dawlish', 'town_physical')
->select('Swaziland', 'country_physical')
->type('864741', 'phone_cell')
//Emergency contact details 1
->type('Simon', 'emergency_contact_1')
->type('Brother', 'emergency_relationship_1')
->type('864741', 'emergency_phone_1');
$this->press('Register');
$this->seePageIs('/cart');
/////////////////////////////////////////////
// Add a new user
/////////////////////////////////////////////
$this->visit('http://ngwenya-mtb.dev/');
$this->click('#events-link')
->seePageIs('/events');
dd($this->response->getContent());exit;
$this->click('#event-30');
$this->seePageIs('/event?id=30');
$this->click('#enter-race48');
$this->press('Enter someone else to this race');
$this->seePageIs('/events/courses/register/addtocart');
}
}
Everything is working fine up until this comment
/////////////////////////////////////////////
// Add a new user
/////////////////////////////////////////////
Here is my Registration controller
<?php
namespace App\Http\Controllers;
use Vinkla\Hashids\HashidsManager;
use Illuminate\Routing\Controller as BaseController;
use Sentinel\FormRequests\RegisterRequest;
use Sentinel\FormRequests\EmailRequest;
use Sentinel\FormRequests\ResetPasswordRequest;
use Sentinel\Repositories\Group\SentinelGroupRepositoryInterface;
use Sentinel\Repositories\User\SentinelUserRepositoryInterface;
use Sentinel\Traits\SentinelRedirectionTrait;
use Sentinel\Traits\SentinelViewfinderTrait;
use Sentry;
use View;
use Request;
use Event;
use Redirect;
use Session;
use Config;
use App\Models\Users;
use Illuminate\Support\Facades\Input;
use Gloudemans\Shoppingcart\Facades\Cart;
class RegistrationController extends BaseController
{
/**
* Traits
*/
use SentinelRedirectionTrait;
use SentinelViewfinderTrait;
/**
* Constructor
*/
public function __construct(
SentinelUserRepositoryInterface $userRepository,
SentinelGroupRepositoryInterface $groupRepository,
HashidsManager $hashids
) {
$this->userRepository = $userRepository;
$this->groupRepository = $groupRepository;
$this->hashids = $hashids;
}
/**
* Show the registration form, if registration is allowed
*
* #return Response
*/
public function registration()
{
// Is this user already signed in? If so redirect to the post login route
if (Sentry::check()) {
return $this->redirectTo('session_store');
}
//If registration is currently disabled, show a message and redirect home.
if (! config('sentinel.registration', false)) {
return $this->redirectTo(['route' => 'home'], ['error' => trans('Sentinel::users.inactive_reg')]);
}
// All clear - show the registration form.
return $this->viewFinder(config('sentinel.view.user_register', 'Sentinel::users.register'));
}
/**
* Process a registration request
*
* #return Response
*/
public function register(RegisterRequest $request)
{
// Gather input
$data = $request->all();
// collect cart items
$email = Input::get('email');
$course_id = Input::get('course_id');
$event_name = Input::get('event_name');
$entry_fee = Input::get('entry_fee');
// check user exists
if (Users::where('email', '=', $email)->exists()) {
// user found
$request->session()->flash('alert-danger', 'Warning: This email is already registered.');
Input::flash();
return View::make('sentinel.users.register')
->with('course_id',$course_id)
->with('event_name',$event_name)
->with('entry_fee',$entry_fee);
}
// Add user and course to cart
if ($course_id) {
$firstUserRowId = Cart::add($course_id, $event_name , 1, $entry_fee, [
'first_name' => Input::get('first_name'),
'last_name' => Input::get('last_name'),
'email' => Input::get('email'),
'no_email' => 0,
'master_user' => 1,
'gender' => Input::get('gender'),
'dob' => Input::get('dob'),
'shirt_size' => Input::get('shirt_size'),
'id_type' => Input::get('id_type'),
'id_number' => Input::get('id_number'),
'nationality' => Input::get('nationality'),
'phone_cell' => Input::get('phone_cell'),
'town_physical' => Input::get('town_physical'),
'country_physical' => Input::get('country_physical'),
'emergency_contact_1' => Input::get('emergency_contact_1'),
'emergency_relationship_1' => Input::get('emergency_relationship_1'),
'emergency_phone_1' => Input::get('emergency_phone_1'),
]);
}
// get email from request
$email = $request->only('email');
foreach ($email as $userModel) {}
// Edit date of birth from request
$year = Input::get('year');
$month = Input::get('month');
$day = Input::get('day');
$dob = $year.'-'.$month.'-'.$day;
$data['dob'] = $dob;
// Attempt Registration
$result = $this->userRepository->store($data);
// Log user in
FunctionsController::loginUser($userModel);
// It worked! Use config to determine where we should go.
return $this->redirectViaResponse('registration_complete', $result);
}
/**
* Activate a new user
*
* #param int $id
* #param string $code
*
* #return Response
*/
public function activate($hash, $code)
{
// Decode the hashid
$id = $this->hashids->decode($hash)[0];
// Attempt the activation
$result = $this->userRepository->activate($id, $code);
// It worked! Use config to determine where we should go.
return $this->redirectViaResponse('registration_activated', $result);
}
/**
* Show the 'Resend Activation' form
*
* #return View
*/
public function resendActivationForm()
{
return $this->viewFinder('Sentinel::users.resend');
}
/**
* Process resend activation request
* #return Response
*/
public function resendActivation(EmailRequest $request)
{
// Resend the activation email
$result = $this->userRepository->resend(['email' => e($request->get('email'))]);
// It worked! Use config to determine where we should go.
return $this->redirectViaResponse('registration_resend', $result);
}
/**
* Display the "Forgot Password" form
*
* #return \Illuminate\View\View
*/
public function forgotPasswordForm()
{
return $this->viewFinder('Sentinel::users.forgot');
}
/**
* Process Forgot Password request
* #return Response
*/
public function sendResetPasswordEmail(EmailRequest $request)
{
// Send Password Reset Email
$result = $this->userRepository->triggerPasswordReset(e($request->get('email')));
// It worked! Use config to determine where we should go.
return $this->redirectViaResponse('registration_reset_triggered', $result);
}
/**
* A user is attempting to reset their password
*
* #param $id
* #param $code
*
* #return Redirect|View
*/
public function passwordResetForm($hash, $code)
{
// Decode the hashid
$id = $this->hashids->decode($hash)[0];
// Validate Reset Code
$result = $this->userRepository->validateResetCode($id, $code);
if (! $result->isSuccessful()) {
return $this->redirectViaResponse('registration_reset_invalid', $result);
}
return $this->viewFinder('Sentinel::users.reset', [
'hash' => $hash,
'code' => $code
]);
}
/**
* Process a password reset form submission
*
* #param $hash
* #param $code
* #return Response
*/
public function resetPassword(ResetPasswordRequest $request, $hash, $code)
{
// Decode the hashid
$id = $this->hashids->decode($hash)[0];
// Gather input data
$data = $request->only('password', 'password_confirmation');
// Change the user's password
$result = $this->userRepository->resetPassword($id, $code, e($data['password']));
// It worked! Use config to determine where we should go.
return $this->redirectViaResponse('registration_reset_complete', $result);
}
}
It seems that when you click and a your link "Register" your redirection fail, so check if you have multiple "Register" links/buttons, and if they are pointing to the right URL
And for easiest debugging, you should make less assertions per Test, you will gain in visibility :)
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