I have created a new custom validation in Laravel 5.0 like below to match the exact length validation. I have also setup the message for this in validation.php
// app/Validators/CustomValidator.php
<?php namespace App\Validators;
use App;
use Illuminate\Validation\Validator;
class CustomValidator extends Validator
{
public function validateExactLength($attribute, $values, $params)
{
return ($params[0] === strlen($values));
}
}
// resources/lang/en/validation.php
"exact_length" => "The :attribute must be a :exact_length characters."
It works like charm and prints following message if it fails ...
"myFieldName must be a :exact_length characters."
Notice above the bold char, i.e. it does not replace it with the value that I have provided in Validation Request file which is
app/Http/Requests/StoreSmsPriceRequest.php
public function rules()
{
return [
'MyFieldName' => 'required|exact_length:6'
];
}
So the problem is does not print the required length in the error message. How can do that?
Related
Say I have a custom logic for validating the uniqueness of a FormRequest field, something requiring to find another resource in the database like below:
class CreateMyResourceRequest extends FormRequest {
public function rules() {
return [
'my_field' => [
Rule::unique('some_other_resource', 'some_column')
->where(function ($query) {
$otherResource = SomeOtherResource::where(...)->firstOrFail();
// Process the retrieved resource
}),
]
The firstOrFail() call obviously makes the request fail with a 404 - Not found while I would like to return a 422 with a validation error on the field.
Is there a way to achieve this while still using the Rule::unique() provided by the framework?
Thanks in advance!
I'd suggest the following
public function rules()
{
return [
"your_field" => ["you_can_have_more_validations_here", function($key, $value, $cb) {
$queryResult = SomeModel::find(1);
if (someCondition) {
$cb("your fail message");
}
}]
];
}
when the $cb run the validation will fail with 422
Don't use firstOrFail, instead just use first and check if the output is null. If it is, return false.
Alternatively, the more Laravel way of doing it is:
$query->leftJoin('otherresource', 'primarykey', 'foreignkey')->where(...)
I'm trying to write some tests for my forms in order to confirm the validators retrieve the expected errors when required.
The form only has 3 fields: name, discount and expiration and the validator looks like this:
$this->validate($request, [
'name' => 'required',
'discount' => 'required|numeric|between:1,100',
'expiration' => 'required|date_format:d/m/Y',
]);
That works fine both when submitting the form and when running the tests with phpunit using the following code:
/**
* Discount must be numeric check
*/
$response = $this->post(route('offer.create'), [
'name' => $faker->sentence(4),
'discount' => 'asdasd',
'expiration' => $faker->dateTimeBetween('+1 days', '+5 months')
]);
// Check errors returned
$response->assertSessionHasErrors(['discount']);
Since discount is not numeric it throws the expected error and everybody is happy.
Now, if I want to add a new rule to make sure that the expiration is equal or greater to today I add the after:yesterdayrule leaving the validator like:
$this->validate($request, [
'name' => 'required',
'discount' => 'required|numeric|between:1,100',
'expiration' => 'required|date_format:d/m/Y|after:yesterday',
]);
That works fine when submitting the form. I get the error saying the discount is not numeric, but when testing with phpunit it doesn't get the error as expected:
1) Tests\Feature\CreateSpecialOfferTest::testCreateSpecialOffer
Session missing error: expiration
Failed asserting that false is true.
Why adding this new validation rule to expirationgenerates a false validation in discount? Is this a bug in the validator or am I missing something?
Also:
1 - is there a better way to test form validators?
2 - is there an assert that is the opposite of assertSessionHasErrors() to check a certain error is NOT been thrown?
If you see this kind of errors in PHPUnit: Failed asserting that false is true., you can add the 'disableExceptionHandling' function to tests/TestCase.php:
<?php
namespace Tests;
use Exception;
use App\Exceptions\Handler;
use Illuminate\Contracts\Debug\ExceptionHandler;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
use CreatesApplication;
protected function disableExceptionHandling()
{
// Disable Laravel's default exception handling
// and allow exceptions to bubble up the stack
$this->app->instance(ExceptionHandler::class, new class extends Handler {
public function __construct() {}
public function report(Exception $exception) {}
public function render($request, Exception $exception)
{
throw $exception;
}
});
}
}
In your test you call it like this:
<?php
/** #test */
public function your_test_function()
{
$this->disableExceptionHandling();
}
Now, the full output of the error and stacktrace will be shown in the PHPUnit console.
In my AppServideProvider's boot method I've set a custom validation rule for cyrillic. Here is what it lookes like:
public function boot()
{
Validator::extend('cyrillic', function ($attribute, $value, $parameters, $validator) {
return preg_match('/[А-Яа-яЁё]/u', $value);
});
}
It works as expected. However if the validation doesn't pass because of latin input I get the error message 'validation.cyrillic'. How can I fix that? How can I pass a custom message to my 'cyrillic' validation rule?
If you want to define it globally, you need to edit the validation file or files, which is/are located in resources/lang/LANG/validation.php where LANG is whatever language you want to define is.
For instance, for the English ones, open the file resources/lang/en/validation.php and add your message like below.
return [
'accepted' => 'The :attribute must be accepted.',
'active_url' => 'The :attribute is not a valid URL.',
// Add yours to somewhere in the first level of the array
'cyrillic' => 'The :attribute is not Cyrillic.'
]
For locally, you can define it within a Request.
public function messages()
{
return [
'cyrillic' => 'The :attribute is not Cyrillic.'
];
}
I have this custom validation in a controller to validate phone numbers,
Controller
$this->validate($request, [
...
'phonenumber' => 'required|phone_number'
]);
AppServiceProvider
public function boot(){
$this->app['validator']->extend('is_phone', function($attribute, $value, $parameters) {
...
if($rest_result->status == 200){
// I need this $rest_result->phone_number ;
return true ;
}
return false ;
});
}
I implemented the validator and put it in the boot method of AppServiceProvider so I can reuse it elsewhere and that works. I use a web service to verify the number and if the call is successful the service formats the number in the corresponding national format which I need in my app.
How do I pass that value back to my controller ?
Bonus: It would be really cool if it could also overwrite the value in old so that the form gets it too
I've found a way to handle this all in one shot.
After you've done your validation, use the merge method on the request to merge the validated and modified input back into the request object.
You would put the following
public function boot(){
$this->app['validator']->extend('is_phone', function($attribute, $value, $parameters) {
...
if($rest_result->status == 200){
request()->merge(['phone' => $rest_result->phone]);
return true;
}
return false ;
});
}
Now in your controller, that phone would be included with the request... Obviously the phone number wouldn't be replaced by the phone number returned from the service until you actually make the validation object.
$phone = request()->phone;
I am setting up a Request class in Laravel 5.2 to process a form. I have a select menu within the form. The first element in the select menu something like the following:
<select name="unique-id">
<option value="none">Select one...</option>
<option value="real">Real</option>
</select>
By default the select menu is on "none" - submitting the form with this option selected should result in error; so, I have the following:
class SomeRequest extends Request
{
...
public function rules()
{
return [
'unique-id' => 'not-in:none', // error is caught
'required-field' => 'required' // error is caught
];
}
...
}
The "not-in" rule is caught by the request handler and returns an error; however, the default error message says, "The selected unique-id is invalid" - obviously not the most human of error messages. So, I created the following:
class SomeRequest extends Request
{
...
public function messages()
{
return [
'unique-id.not-in' => 'Must select a valid XXXX.', // does not return
'required-field.required' => 'Verifying custom errors.' // returns
];
}
...
}
I have tried a few variations, but don't want to cloud anyone's answers with that. So, the above is what is currently failing.
It should be "not_in" not "not-in". Laravel Not In