I'm learning Laravel 5 and trying to validate if an email exists in database yet then add some custom message if it fails. I found the After Validation Hook in Laravel's documentation
$validator = Validator::make(...);
$validator->after(function($validator) use ($email) {
if (emailExist($email)) {
$validator->errors()->add('email', 'This email has been used!');
}
});
if ($validator->fails()) {
return redirect('somewhere')
->withErrors($validator);
}
but I don't really understand what this is. Because I can simply do this:
//as above
if (emailExist($email)) {
$validator->errors()->add('email', 'This email has been used!');
}
//redirect as above
It still outputs the same result. When should I use the 1st one to validate something instead of the 2nd one?
The point of the first method is just to keep everything contained inside of that Validator object to make it more reusable.
Yes, in your case it does the exact same thing. But imagine if you wanted to validate multiple items.
foreach ($inputs as $input) {
$validator->setData($input);
if ($validator->fails()) { ... }
}
In your case you will have to add that "if" check into the loop. Now imagine having to run this validation in many different places (multiple controllers, maybe a console script). Now you have this if statement in 3 different files, and next time you go to modify it you have 3x the amount of work, and maybe you forget to change it in one place...
I can't think of many use cases for this but that is the basic idea behind it.
By the way there is a validation rule called exists that will probably handle your emailExist() method
$rules = [
'email' => 'exists:users,email',
];
http://laravel.com/docs/5.1/validation#rule-exists
There may be many scenarios where you may feel it's requirement.
Just assume that you are trying to build REST api for a project. And you have decided that update request method will not have any required rule validation for any field in request (as there maybe many parameters and you do not want to pass them all just to change one column or maybe you do not have all the columns because you aren't allowed access to it) .
So how will you handle this validation in UpdatePostRequest.php class where you have put all the validation rules in rules() method as given in code.
Further more there may be requirement that sum of values of two or more request fields should be greater or less than some threshold quantity. Then what?
I agree that you can just check it in controller and redirect it from there but wouldn't it defeat the purpose of creating a dedicated request class if we were to do these checks in controllers.
What I feel is controllers should be clean and should not have multiple exit points based on validation. These small validation checks can be handled in request class itself by creating a new Rule or extending your own custom validation or creating after validation hooks and all of them have their unique usage in Laravel.
Therefore what you may want to to do here is create a validation hook where it's assigned is to check whether request is empty or not like the example given below
public function withValidator($validator)
{
$validator->after(function ($validator) {
if (empty($this->toArray())) {
$validator->errors()->add('body', 'Request body cannot be empty');
}
if (!$this->validateCaptcha()) {
$validator->errors()->add('g-recaptcha-response', 'invalid');
}
});
}
And here is the full example for it.
<?php
namespace App\Http\Requests\Posts;
use App\Helpers\General\Tables;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class UpdatePostRequest extends FormRequest
{
public function authorize()
{
return auth()->user()->can('update-post', $this);
}
public function rules()
{
return [
'name' => ['string', 'min:3', 'max:255'],
'email' => ['string', 'email', 'min:3', 'max:255'],
'post_data' => ['string', 'min:3', 'max:255'],
];
}
public function withValidator($validator)
{
$validator->after(function ($validator) {
if (empty($this->toArray())) {
$validator->errors()->add('body', 'Request body cannot be empty');
}
});
}
}
Thanks..
passedValidation method will trigger if the validation passes in FormRequest class. Actually this method is rename of afterValidation method. See: method rename Commit
So you can do like
class RegistrationRequest extends FormRequest
{
/**
* Handle a passed validation attempt.
*
* #return void
*/
protected function passedValidation()
{
$this->merge(
[
'password' => bcrypt($this->password),
]
);
}
}
Related
In my Laravel controllers I am always using the same validation to check if user submitted data is valid/invalid.
public function schedule(Request $request)
{
$request->validate([
'assessment_id' => 'required|integer',
'user_id' => 'required|integer',
'due_date' => 'required|string'
]);
$assessment_id = $request->input('assessment_id');
$user_id = $request->input('user_id');
$due_date = $request->input('due_date');
$staff = auth()->user();
$company = $staff->companies()->first();
$user = $this->staffAssessmentRepository->getUserById($user_id);
$assessment = $this->staffAssessmentRepository->getAssessmentById($assessment_id);
$date = Carbon::parse($due_date);
if(!$user || !$assessment){
return response()->json('Cannot find assessment and/or user!', 404);
}
if(!$company->hasUser($user)){
return response()->json('User does not belong to this company!', 401);
}
if(!$user->hasRole(Role::ROLE_CANDIDATE_NAME)){
return response()->json('User is not a candidate', 401);
}
if($user->hasAssessment($assessment, $company)){
return response()->json('Candidate already has this assessment!', 401);
}
$user_assessment = $this->staffAssessmentRepository->scheduleUserAssessment($user, $company, $assessment, $date, $staff);
if(!$user_assessment){
return response()->json('Failed to create user assessment!', 500);
}
return response()->json($user_assessment, 201);
}
Please look at this specific part from the PHP code above:
if(!$user || !$assessment){
return response()->json('Cannot find assessment and/or user!', 404);
}
if(!$company->hasUser($user)){
return response()->json('User does not belong to this company!', 401);
}
if(!$user->hasRole(Role::ROLE_CANDIDATE_NAME)){
return response()->json('User is not a candidate', 401);
}
if($user->hasAssessment($assessment, $company)){
return response()->json('Candidate already has this assessment!', 401);
}
In my controller methods I am always needing to validate the same user cases, and check if they fail (return response JSON) many times and it's become too repetitive. I'm trying to follow the DRY principle (Don't repeat yourself), and would like your solutions as to how I can prevent repetition in user validation. Solutions can be in PHP/Laravel but I am working in a Laravel project.
EDIT: please not that it isn't the fact that there are many if statements, the issue isn't that. The problem is that the SAME if statements are used amongst multiple different methods in my controllers and I need a architectural decision as to how I can decouple my code so my controllers can inherit the same validation (the if statements).
You should aim to clearly separate the various components of your application, meaning validation should be a single process rather than multiple fragmented processes. Your current approach -- validating some of the input using the Laravel Validator and then manually validating the rest -- is less than ideal.
The ideal approach is to use the Laravel Validator for validating all of the input, including state. There are additional features that can help with this, Form Requests which allow you to implement more advanced validation and re-use validation across multiple controllers, and there's Rule Objects which allow you to implement custom validation logic for attributes.
Your code does the following:
Checks if the User exists
Checks if the Assessment exists
Checks if the User belongs to the Company
Checks if the User is a candidate
Checks if the User already has an assessment
Each of these can be implemented as custom Rules, then you can create a single Form Request which would look something like this:
/**
* Get the validation rules that apply to scheduling an Assessment.
*
* #return array
*/
public function rules(): array
{
return [
'assessment_id' => 'required|exists:assessments',
'user_id' => ['required', 'exists:users', new BelongsToCompany, new IsCandidate],
'due_date' => 'required|date',
];
}
Then for more complex validation (such as validating that the user doesn't already have that assessment which requires 2 input values) you can either have a Rule that you explicitly pass in the additional values or you can use withValidator to extend the validator -- that's covered in the documentation.
Passing in the additional value:
/**
* Get the validation rules that apply to scheduling an Assessment.
*
* #return array
*/
public function rules(): array
{
return [
'assessment_id' => ['required', 'exists:assessments', new AssessmentAvailable(request()->input('user_id'))],
'user_id' => ['required', 'exists:users', new BelongsToCompany, new IsCandidate],
'due_date' => 'required|date',
];
}
Extending the validator:
public function rules(): array
{
// ...
}
/**
* Validates whether or not an assessment is available for the User.
*
* #param \Illuminate\Validation\Validator $validator
*
* #return void
*/
public function withValidator($validator)
{
$validator->after(function ($validator) {
$user = User::findOrFail($this->input('user_id'));
$assessment = Assessment::findOrFail($this->input('assessment_id'));
if ($user->hasAssessment($assessment)) {
$validator->errors()->add('assessment_id', 'The user already has this assessment.');
}
});
}
This approach gives you easy to re-use validation logic and the full power of Laravel's validation system, including input errors for users.
i have example object with fields
name => John
surname => Dow
job => engineer
and output form with placeholders. some required, some not.
what is best practice for check if it requred and show error with null fields?
There are multiple ways actually you can do that inside of controller method or make use of Laravels Request Classes for me I prefer to use Request Classes
look below I will list the two examples
Validate inside the controller's method
public function test(Request $request){
if($request->filled('name){
/*filled will check that name is set on the current
request and not empty*/
//Do your logic here
}
}
Second way is by using the Validator Facade inside your controller
use Validator;
class TestController{
public function test(Request $request){
$validator = Validator::make($request->all(), [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
/*continue with your logic here if the request failed on
validator test Laravel will automatically redirect back
with errors*/
}
}
Third way my favorite one personally
you can generate a Request class using this command
php artisan make:request AddBookRequest
that will generate the request class under "app/Http/Requests/AddBookRequest" , inside of any generated request class you will find two methods authorize() and rules()
in the authorized method you have to return truthy or falsy value this will detect if the current user making the request has authorization to fire this request inside of the rules method you do pretty much as you did in the Validator in the second way check the example
public function authorize(){
return true;
}
public function rules(){
return [
'title' => 'required|string',
'author_id' => 'required|integer'
];
}
then simply in your controller you can use the generated request like this
use App\Http\Requests\AddBookRequest;
public function store(AddBookRequest $request){
/* do your logic here since we uses a request class if it fails
then redirect back with errors will be automatically returned*/
}
Hope this helps you can read more about validation at
https://laravel.com/docs/5.6/validation
I think "simple is the best", just through object and check if properties exists
Ref: property_exists
Example:
if (property_exists($object, 'name')) {
//...do something for exists property
} else {
//...else
}
In order to reuse code, I created my own validator rule in a file named ValidatorServiceProvider :
class ValidatorServiceProvider extends ServiceProvider
{
public function boot()
{
Validator::extend('checkEmailPresenceAndValidity', function ($attribute, $value, $parameters, $validator) {
$user = User::where('email', $value)->first();
// Email has not been found
if (! $user) {
return false;
}
// Email has not been validated
if (! $user->valid_email) {
return false;
}
return true;
});
}
public function register()
{
//
}
}
And I use this rule like this :
public function rules()
{
return [
'email' => 'bail|required|checkEmailPresenceAndValidity'
];
}
But, I want to set different error messages for each case, something like this :
if (! $user) {
$WHATEVER_INST->error_message = 'email not found';
return false;
}
if (! $user->valid_email) {
$WHATEVER_INST->error_message = 'invalid email';
return false;
}
But I don't figure out how to achieve this without doing 2 different rules ...
Of course it could work with multiple rules but it will also perform multiple SQL queries, and I really want to avoid that.
Also, keep in mind that in real case I could have more than 2 validations like theses in a single rule.
Does anyone have an idea ?
=====
EDIT 1 :
Actually, I think that I want something that works in a similar way to the between or size rules.
They represent one single rule, but provide multiple error messages :
'size' => [
'numeric' => 'The :attribute must be :size.',
'file' => 'The :attribute must be :size kilobytes.',
'string' => 'The :attribute must be :size characters.',
'array' => 'The :attribute must contain :size items.',
],
Laravel checks if the value represents a numeric, a file, a string or an array ; and gets the right error message to use.
How do we achieve this kind of thing with custom rule ?
Unfortunately Laravel doesn't currently provide a concrete way to add and call your validation rule directly from your attribute params array. But that does not exclude a potential and friendly solution based on Trait and Request usage.
Please find below my solution for example.
First thing is to wait for the form to be processed to handle the form request ourselves with an abstract class. What you need to do is to get the current Validator instance and prevent it from doing further validations if there's any relevant error. Otherwise, you'll store the validator instance and call your custom user validation rule function that you'll create later :
<?php
namespace App\Custom\Validation;
use \Illuminate\Foundation\Http\FormRequest;
abstract class MyCustomFormRequest extends FormRequest
{
/** #var \Illuminate\Support\Facades\Validator */
protected $v = null;
protected function getValidatorInstance()
{
return parent::getValidatorInstance()->after(function ($validator) {
if ($validator->errors()->all()) {
// Stop doing further validations
return;
}
$this->v = $validator;
$this->next();
});
}
/**
* Add custom post-validation rules
*/
protected function next()
{
}
}
The next step is to create your Trait which will provide the way to validate your inputs thanks to the current validator instance and handle the correct error message you want to display :
<?php
namespace App\Custom\Validation;
trait CustomUserValidations
{
protected function validateUserEmailValidity($emailField)
{
$email = $this->input($emailField);
$user = \App\User::where('email', $email)->first();
if (! $user) {
return $this->v->errors()->add($emailField, 'Email not found');
}
if (! $user->valid_email) {
return $this->v->errors()->add($emailField, 'Email not valid');
}
// MORE VALIDATION POSSIBLE HERE
// YOU CAN ADD AS MORE AS YOU WANT
// ...
}
}
Finally, do not forget to extend your MyCustomFormRequest. For example, after your php artisan make:request CreateUserRequest, here's the easy way to do so :
<?php
namespace App\Http\Requests;
use App\Custom\Validation\MyCustomFormRequest;
use App\Custom\Validation\CustomUserValidations;
class CreateUserRequest extends MyCustomFormRequest
{
use CustomUserValidations;
/**
* Add custom post-validation rules
*/
public function next()
{
$this->validateUserEmailValidity('email');
}
/**
* 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 [
'email' => 'bail|required|email|max:255|unique:users',
'password' => 'bail|required',
'name' => 'bail|required|max:255',
'first_name' => 'bail|required|max:255',
];
}
}
I hope that you'll find your way in what I suggest.
If you are using Laravel 8 and would like to display different error messages for a specific validation, follow the steps below.
I am going to create a validation rule that checks if a field is a valid email or a valid phone number. It will also return different error messages.
Make a custom validtion rule like
php artisan make:rule EmailOrPhone
Navigate to the created file in Rules Folder ie Root->App->Rules->EmailOrPhone.php
Paste the following code
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
class EmailOrPhone implements Rule
{
public $error_message;
public function __construct()
{
}
public function passes($attribute, $value)
{
$value = trim($value);
if (is_numeric($value)){
if (strlen($value) != 10){
$this->error_message = "Phone number must contain 10 digits";
return false;
}else if (!Str::startsWith($value, '0')){
$this->error_message = "Phone number must start with 0";
return false;
}else{
return true;
}
}else{
$validator = Validator::make(['email' => $value],[
'email' => 'required|email'
]);
if($validator->passes()){
return true;
}else{
$this->error_message = "Please provide a valid email address";
return false;
}
}
}
public function message()
{
return $this->error_message;
}
}
You can now use the custom validation in your validator like
return Validator::make($data, [
'firstname' => ['required', 'string', 'max:255'],
'lastname' => ['required', 'string', 'max:255'],
'email_phone' => ['required', 'string', 'max:255', new EmailOrPhone()],
'password' => ['required', 'string', 'confirmed'],
]);
Poor handling of custom validation rules is why I ditched laravel (well, it was one of many reasons, but it was the straw that broke the camel's back, so to speak). But anyway, I have a three part answer for you: a reason why you don't want to do this in this specific case, a quick general overview of the mess you have to deal with, and then the answer to your question in case you still want to do it.
Important security concern
Best security practices for managing logins dictate that you should always return one generic error message for login problems. The quintessential counter-example would be if you returned "That email is not registered with our system" for an email-not-found and "Wrong password" for a correct email with the wrong password. In the case where you give separate validation messages, you give potential attackers additional information about how to more effectively direct their attacks. As a result, all login-related issues should return a generic validation message, regardless of the underlying cause, something to the effect of "Invalid email/password combination". The same is true for password recovery forms, which often say something like, "Password recovery instructions have been sent to that email, if it is present in our system". Otherwise you give attackers (and others) a way to know what email addresses are registered with your system, and that can expose additional attack vectors. So in this particular case, one validation message is what you want.
The trouble with laravel
The issue you are running into is that laravel validators simply return true or false to denote whether or not the rule is met. Error messages are handled separately. You specifically cannot specify the validator error message from inside your validator logic. I know. It's ridiculous, and poorly planned. All you can do is return true or false. You don't have access to anything else to help you, so your pseudo code isn't going to do it.
The (ugly) answer
The simplest way to create your own validation messages is to create your own validator. That looks something like this (inside your controller):
$validator = Validator::make($input, $rules, $messages);
You would still have to create your validator on boot (your Valiator::Extend call. Then you can specify the $rules normally by passing them in to your custom validator. Finally, you can specify your messages. Something like this, overall (inside your controller):
public function login( Request $request )
{
$rules = [
'email' => 'bail|required|checkEmailPresenceAndValidity'
]
$messages = [
'checkEmailPresenceAndValidity' => 'Invalid email.',
];
$validator = Validator::make($request->all(), $rules, $messages);
}
(I don't remember if you have to specify each rule in your $messages array. I don't think so). Of course, even this isn't very awesome, because what you pass for $messages is simply an array of strings (and that is all it is allowed to be). As a result, you still can't have this error message easily change according to user input. This all happens before your validator runs too. Your goal is to have the validation message change depending on the validation results, however laravel forces you to build the messages first. As a result, to really do what you want to do, you have to adjust the actual flow of the system, which isn't very awesome.
A solution would be to have a method in your controller that calculates whether or not your custom validation rule is met. It would do this before you make your validator so that you can send an appropriate message to the validator you build. Then, when you create the validation rule, you can also bind it to the results of your validation calculator, so long as you move your rule definition inside of your controller. You just have to make sure and not accidentally call things out of order. You also have to keep in mind that this requires moving your validation logic outside of the validators, which is fairly hacky. Unfortunately, I'm 95% sure there really isn't any other way to do this.
I've got some example code below. It definitely has some draw backs: your rule is no longer global (it is defined in the controller), the validation logic moves out of the validator (which violates the principle of least astonishment), and you will have to come up with an in-object caching scheme (which isn't hard) to make sure you don't execute your query twice, since the validation logic is called twice. To reiterate, it is definitely hacky, but I'm fairly certain that this is the only way to do what you want to do with laravel. There might be better ways to organize this, but this should at least give you an idea of what you need to make happen.
<?php
namespace App\Http\Controllers;
use User;
use Validator;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class LoginController extends Controller
{
public function __construct() {
Validator::extend('checkEmailPresenceAndValidity', function ($attribute, $value, $parameters, $validator) {
return $this->checkLogin( $value ) === true ? true : false;
});
}
public function checkLogin( $email ) {
$user = User::where('email', $email)->first();
// Email has not been found
if (! $user) {
return 'not found';
}
// Email has not been validated
if (! $user->valid_email) {
return 'invalid';
}
return true;
}
public function login( Request $request ) {
$rules = [
'email' => 'bail|required|checkEmailPresenceAndValidity'
]
$hasError = $this->checkLogin( $request->email );
if ( $hasError === 'not found' )
$message = "That email wasn't found";
elseif ( $hasError === 'invalid' )
$message = "That is an invalid email";
else
$message = "Something was wrong with your request";
$messages = [
'checkEmailPresenceAndValidity' => $message,
];
$validator = Validator::make($request->all(), $rules, $messages);
if ($validator->fails()) {
// do something and redirect/exit
}
// process successful form here
}
}
Also, it is worth a quick note that this implementation relies on $this support for closures, which (I believe) was added in PHP 5.4. If you are on an old version of PHP you'll have to provide $this to the closure with use.
Edit to rant
What it really boils down to is that the laravel validation system is designed to be very granular. Each validation rule is specifically only supposed to validate one thing. As a result, the validation message for a given validator should never have to be changed, hence why $messages (when you build your own validator) only accepts plain strings.
In general granularity is a good thing in application design, and something that proper implementation of SOLID principles strive for. However, this particular implementation drives me crazy. My general programming philosophy is that a good implementation should make the most common uses-cases very easy, and then get out of your way for the less-common use-cases. In this cases the architecture of laravel makes the most common use-cases easy but the less common use-cases almost impossible. I'm not okay with that trade off. My general impression of Laravel was that it works great as long as you need to do things the laravel way, but if you have to step out of the box for any reason it is going to actively make your life more difficult. In your case the best answer is to probably just step right back inside that box, i.e. make two validators even if it means making a redundant query. The actual impact on your application performance likely will not matter at all, but the hit you will take to your long-term maintainability to get laravel to behave the way you want it will be quite large.
Alternatively to the other proposals, I think you could also call Validator::replacer('yourRule', function()) in addition to Validator::extend('yourRule', function(...)) and keep track of what causes validation failures in the service provider class you're extending the validator from. This way, you are be able to completely replace the default error message with another one.
According to docs, replacer() is meant for making placeholder replacements in the error message before it is being returned, so while this is not strictly that case, it is close enough. Of course, it's kind of an ugly(ish) workaround, but it will probably work (at least it seems to work for me, at a first glance).
One thing to keep in mind though is that you'll probably have to keep track of these failure causes in an array if you want to avoid automatically returning same message for all fields that failed your custom validation rule.
Where have you found the error messages for the size validation?
I looked up the validation rules in the
Illuminate\Validation\ConcernsValidatesAttributes trait and all functions return a bool value (also the size validation).
protected function validateSize($attribute, $value, $parameters)
{
$this->requireParameterCount(1, $parameters, 'size');
return $this->getSize($attribute, $value) == $parameters[0];
}
What you have found belongs to this part:
$keys = ["{$attribute}.{$lowerRule}", $lowerRule];
In this case it's only for formatting the the output by setting a lowerRule value, that laravel handles in special cases, like the size validation:
// If the rule being validated is a "size" rule, we will need to gather the
// specific error message for the type of attribute being validated such
// as a number, file or string which all have different message types.
elseif (in_array($rule, $this->sizeRules)) {
return $this->getSizeMessage($attribute, $rule);
}
So as long as validation rules have to return a bool value there is no way to return more than one error message. Otherwise you have to rewrite some party of the validation rules.
An approach for your problem with the validation you could use the exists validation:
public function rules()
{
return [
'email' => ['bail', 'required', Rule::exists('users')->where(function($query) {
return $query->where('valid_email', 1);
})]
];
}
So you would need 2 exists validation rules. I would suggest to use the existing one from laravel to check if the email is set and a custom one to check if the account is validated.
I am trying to unit test various custom FormRequest inputs. I found solutions that:
Suggest using the $this->call(…) method and assert the response with the expected value (link to answer). This is overkill, because it creates a direct dependency on Routing and Controllers.
Taylor’s test, from the Laravel Framework found in tests/Foundation/FoundationFormRequestTest.php. There is a lot of mocking and overhead done there.
I am looking for a solution where I can unit test individual field inputs against the rules (independent of other fields in the same request).
Sample FormRequest:
public function rules()
{
return [
'first_name' => 'required|between:2,50|alpha',
'last_name' => 'required|between:2,50|alpha',
'email' => 'required|email|unique:users,email',
'username' => 'required|between:6,50|alpha_num|unique:users,username',
'password' => 'required|between:8,50|alpha_num|confirmed',
];
}
Desired Test:
public function testFirstNameField()
{
// assertFalse, required
// ...
// assertTrue, required
// ...
// assertFalse, between
// ...
}
public function testLastNameField()
{
// ...
}
How can I unit test (assert) each validation rule of every field in isolation and individually?
I found a good solution on Laracast and added some customization to the mix.
The Code
/**
* Test first_name validation rules
*
* #return void
*/
public function test_valid_first_name()
{
$this->assertTrue($this->validateField('first_name', 'jon'));
$this->assertTrue($this->validateField('first_name', 'jo'));
$this->assertFalse($this->validateField('first_name', 'j'));
$this->assertFalse($this->validateField('first_name', ''));
$this->assertFalse($this->validateField('first_name', '1'));
$this->assertFalse($this->validateField('first_name', 'jon1'));
}
/**
* Check a field and value against validation rule
*
* #param string $field
* #param mixed $value
* #return bool
*/
protected function validateField(string $field, $value): bool
{
return $this->validator->make(
[$field => $value],
[$field => $this->rules[$field]]
)->passes();
}
/**
* Set up operations
*
* #return void
*/
public function setUp(): void
{
parent::setUp();
$this->rules = (new UserStoreRequest())->rules();
$this->validator = $this->app['validator'];
}
Update
There is an e2e approach to the same problem. You can POST the data to be checked to the route in question and then see if the response contains session errors.
$response = $this->json('POST',
'/route_in_question',
['first_name' => 'S']
);
$response->assertSessionHasErrors(['first_name']);
I see this question has a lot of views and misconceptions, so I will add my grain of sand to help anyone who still has doubts.
First of all, remember to never test the framework, if you end up doing something similar to the other answers (building or binding a framework core's mock (disregard Facades), then you are doing something wrong related to testing).
So, if you want to test a controller, the always way to go is: Feature test it. NEVER unit test it, not only is cumbersome to unit test it (create a request with data, maybe special requirements) but also instantiate the controller (sometimes it is not new HomeController and done...).
They way to solve the author's problem is to feature test like this (remember, is an example, there are plenty of ways):
Let's say we have this rules:
public function rules()
{
return [
'name' => ['required', 'min:3'],
'username' => ['required', 'min:3', 'unique:users'],
];
}
namespace Tests\Feature;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class HomeControllerTest extends TestCase
{
use RefreshDatabase;
/*
* #dataProvider invalid_fields
*/
public function test_fields_rules($field, $value, $error)
{
// Create fake user already existing for 'unique' rule
User::factory()->create(['username' => 'known_username']);
$response = $this->post('/test', [$field => $value]);
$response->assertSessionHasErrors([$field => $error]);
}
public function invalid_fields()
{
return [
'Null name' => ['name', null, 'The name field is required.'],
'Empty name' => ['name', '', 'The name field is required.'],
'Short name' => ['name', 'ab', 'The name must be at least 3 characters.'],
'Null username' => ['username', null, 'The username field is required.'],
'Empty username' => ['username', '', 'The username field is required.'],
'Short username' => ['username', 'ab', 'The username must be at least 3 characters.'],
'Unique username' => ['username', 'known_username', 'The username has already been taken.'],
];
}
}
And that's it... that is the way of doing this sort of tests... No need to instantiate/mock and bind any framework (Illuminate namespace) class.
I am taking advantage of PHPUnit too, I am using data providers so I don't need to copy paste a test or create a protected/private method that a test will call to "setup" anything... I reuse the test, I just change the input (field, value and expected error).
If you need to test if a view is being displayed, just do $response->assertViewIs('whatever.your.view');, you can also pass a second attribute (but use assertViewHas) to test if the view has a variable in it (and a desired value). Again, no need to instantiate/mock any core class...
Have in consideration this is just a simple example, it can be done a little better (avoid copy pasting some errors messages).
One last important thing: If you unit test this type of things, then, if you change how this is done in the back, you will have to change your unit test (if you have mocked/instantiated core classes). For example, maybe you are now using a FormRequest, but later you switch to other validation method, like a Validator directly, or an API call to other service, so you are not even validating directly in your code. If you do a Feature Test, you will not have to change your unit test code, as it will still receive the same input and give the same output, but if it is a Unit Test, then you are going to change how it works... That is the NO-NO part I am saying about this...
Always look at test as:
Setup minimum stuff (context) for it to begin with:
What is your context to begin with so it has logic ?
Should a user with X username already exist ?
Should I have 3 models created ?
Etc.
Call/execute your desired code:
Send data to your URL (POST/PUT/PATCH/DELETE)
Access a URL (GET)
Execute your Artisan Command
If it is a Unit Test, instantiate your class, and call the desired method.
Assert the result:
Assert the database for changes if you expected them
Assert if the returned value matches what you expected/wanted
Assert if a file changed in any desired way (deletion, update, etc)
Assert whatever you expected to happen
So, you should see tests as a black box. Input -> Output, no need to replicate the middle of it... You could setup some fakes, but not fake everything or the core of it... You could mock it, but I hope you understood what I meant to say, at this point...
Friends, please, make the unit-test properly, after all, it is not only rules you are testing here, the validationData and withValidator functions may be there too.
This is how it should be done:
<?php
namespace Tests\Unit;
use App\Http\Requests\AddressesRequest;
use App\Models\Country;
use Faker\Factory as FakerFactory;
use Illuminate\Routing\Redirector;
use Illuminate\Validation\ValidationException;
use Tests\TestCase;
use function app;
use function str_random;
class AddressesRequestTest extends TestCase
{
public function test_AddressesRequest_empty()
{
try {
//app(AddressesRequest::class);
$request = new AddressesRequest([]);
$request
->setContainer(app())
->setRedirector(app(Redirector::class))
->validateResolved();
} catch (ValidationException $ex) {
}
//\Log::debug(print_r($ex->errors(), true));
$this->assertTrue(isset($ex));
$this->assertTrue(array_key_exists('the_address', $ex->errors()));
$this->assertTrue(array_key_exists('the_address.billing', $ex->errors()));
}
public function test_AddressesRequest_success_billing_only()
{
$faker = FakerFactory::create();
$param = [
'the_address' => [
'billing' => [
'zip' => $faker->postcode,
'phone' => $faker->phoneNumber,
'country_id' => $faker->numberBetween(1, Country::count()),
'state' => $faker->state,
'state_code' => str_random(2),
'city' => $faker->city,
'address' => $faker->buildingNumber . ' ' . $faker->streetName,
'suite' => $faker->secondaryAddress,
]
]
];
try {
//app(AddressesRequest::class);
$request = new AddressesRequest($param);
$request
->setContainer(app())
->setRedirector(app(Redirector::class))
->validateResolved();
} catch (ValidationException $ex) {
}
$this->assertFalse(isset($ex));
}
}
Is it possible to conditionally set a custom language file (e.g. resources/lang/en/validation_ajax.php) for a validation request? Just to be clear, I don't want to change the app language, just use another set of messages depending on the request origin.
When I make an ajax validation call I want to use different messages since I'm showing the error messages below the field itself. So there's no need to show the field name (label) again.
I know you can define labels on 'attributes' => [] but it's not worth the effort since I have so many fields in several languages.
I'm using a FormRequest (there's no manual call on the Controller just a type hint).
You can override the messages() method for a specific request (let's say login request). Let me show you: At first place, you need yo create a new custom Form Request, here we will define a custom message for email.required rule:
<?php namespace App\MyPackage\Requests;
use App\Http\Requests\Request;
class LoginRequest extends Request {
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
public function messages()
{
return [
'email.required' => 'how about the email?',
];
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'email' => ['required', 'email'],
'password' => ['required', 'confirmed']
];
}
}
Only email.required rule message will be override. For password it will display the default message set at validation.php file.
Now, apply the form request at your controller function like a type hint:
class LoginController{
public function validateCredentials(LoginRequest $request){
// do tasks here if rules were success
}
}
And that is all. The messages() method is useful if you need are creating custom packages and you want to add/edit validation messages.
Update
If you need to carry the bag of messages on into your package's lang file then you can make the following changes:
At your package create your custom lang file:
MyPackage/resources/lang/en/validation.php
Add the messages keeping the same array's structure as project/resources/lang/en/validation.php file:
<?php
return [
'email' => [
'required' => 'how about the email?',
'email' => 'how about the email format?',
],
];
Finally, at your messages() method call the lang's line of your package respectively:
public function messages(){
return [
'email.required' => trans('myPackage::validation.email.required'),
'email.emial' => trans('myPackage::validation.email.valid'),
];
}