I have required file field which is recorded in the database as a path. I want to make it optionally required if its field in the database is null. In the update action of my controller I have set the following validation:
$this->validate(request(),[
'drawings' => 'requiredIfEmpty:'.$product->drawings.'|file|max:'. config('fox.photoMaxSize').'|mimes:pdf',
Then in app/Providers/AppServiceProvider.php I defined requiredIfEmpty validator:
Validator::extend('requiredIfEmpty',function ($attribute,$value,$parameters,$validator){
if(is_null($parameters[0]) || empty($parameters[0])){
if (is_null($value)){
return false;
}
}
return true;
});
Validator::replacer('requiredIfEmpty', function ($message, $attribute, $rule, $parameters) {
return __('The :attr is required',['attr' => $attribute]);
});
In the view _form I use laravelcollective form helper like the following for the drawings field:
<div class="form-group {{$errors->first('drawings','has-error')}}">
#if (!is_null($product->drawings))
<img src="/imgs/pdf.png" alt="{{__('PDF File')}}" title="{{__('PDF File')}}" />
<br>
#else
<img src="/imgs/empty.png" width="64" height="64" alt="{{__('Empty')}}..." title="{{__('Empty')}}..." /> <br>
#endif
{!! Form::label('drawings', __('Drawings')) !!}
{!! Form::file('drawings',['class' => 'btn btn-info','title' =>__('PDF file')]); !!}
#php ($eleE = $errors->first('drawings'))
#include('layouts.form-ele-error')
</div>
The problem is, my custom validation rule does not invoked because the field is not required and it has null value. I need any way that allows the two scenarios:
when the drawings file field is empty and $product->drawings is not null, there's no validation occured
when the drawings file field is empty and $product->drawings is null, the validation occurred.
In other words, I need a built in validation rule like requiredIf but it does not take another form field as parameter, it just take another value and it always works even the form field value is empty and the field is not required.
You should use the extendImplicit function to run rules when their values are empty.
Validator::extendImplicit('required_if_exists', function($attribute, $value, $parameters, $validator) {
//
});
Alternatively, in a Rule class, you should implement the ImplicitRule interface instead of Rule.
use Illuminate\Contracts\Validation\ImplicitRule;
class RequiredIfExists implements ImplicitRule {
//
}
I have found a solution that does not related directly with Laravel validation. It will depend on keeping original validation for the file field and keep it not required then make validation logic in the controller's action that return errorbag message if an error is found. The solution looks like the following snippet:
public function update(Request $request, Product $product)
{
$product = Product::find(request('product'));
$this->validate(request(),[
'drawings' => 'file|max:'. config('fox.photoMaxSize').'|mimes:pdf' //Original validation
]);
if(is_null($request->drawings) && is_null($product->drawings)){
return redirect()->back()->withErrors(['drawings' => __('A drawing file must be specified')]);
} //If the field is null and the database attribute is null too an error message is returned to form's file field.
...
}
However, this solution does not answer how could we define a custom validation rule that always invoked regarding-less the field is set required or not?
Reference
Related
I have two issues when sending a form with the GET method with Symfony 4. This form contains filters and submitting this form updates the list of displayed items according to the selected filters.
The form is built like this:
class MyForm extends AbstractType {
...
public function buildForm(...) {
$builder
->setMethod("GET")
->add(
"first_filter",
ChoiceType::class,
...
)
->add(
"second_filter",
EntityType::class,
...
)
->add(
"button_apply",
SubmitType::class
);
First problem, after sending the form, the URL looks like this:
/action?my_form[first_filter]=...&my_form[second_filter]=...
Is it normal that the form name is included before every field name, and why the URL could not simply be:
/action?first_filter=...&second_filter=...
The second problem is that the submit button is part of the params visible into the URL:
/action?my_form[button_apply]=&...
As far as I know, the submit button itself should not be a parameter ?
Thanks in advance
it's the normal behaviour, and it can be circumvented, but it requires to call createNamed instead of create on a form factory (see ControllerTrait::createForm) ... so Symfony\Component\Form\FormFactory::createNamed())
// at the top: use Symfony\Component\Form\FormFactory
public function yourControllerAction(FormFactory $formFactory, Request $request) {
$form = $formFactory->createNamed(
'', // form name, otherwise form class name camel_cased
YourFormType::class, // your form type
[], // initial data or object or whatever!
[] // form options
);
// rest of the script is identical, it's still a normal form
}
or you don't inject it and do what the trait does:
$form = $this->container->get('form.factory')->createNamed(
// parameters same as before
);
for the button to disappear from the GET/POST, I would advise you to remove the button from your form and instead add it to your template (also increases reusability).
{{ form_start(form) }}
{{ form_widget(form) }}
<button type="submit">{{ 'your label'|trans }}</button>
{{ form_end(form) }}
which makes the button submit the form, but since it has no name and value it won't add to the data (in contrast to the SubmitType button form type).
TLDR;
(In Laravel 5.8) Upon validation error, how can I return to a specific view with all input and the errors?
I'm creating a multistep form in which I need to validate the input on each step. Therefore, I have created the first page as a create and then the subsequent pages to update.
On the second page, when the validator fails, I am returning the specific view as I want it to go to this partial and not the beginning of the form. I am also returning the errors and the input on fail.
Controller:
if ($validator->fails()) {
return view('partials.question_02_' . $type)
->with('type', $type)
->with('id', $id)
->withErrors($validator)
->withInput($request->all());
}
Here is my validation logic:
$validator = Validator::make($request->all(), [
'email' => 'required',
'first_name' => 'required',
'last_name' => 'required',
'phone' => 'required',
]);
With this, I am able to go back to the specific view and with the validation errors. However, I am not able to receive the old input.
The following in Blade will show that there is an erro (the error class appears), however, it will not show the old input.
<input class="#if ($errors->has('first_name')) error #endif"
type="text" name="first_name" placeholder="First Name" value="{{ old('first_name') }}">
*note, I only put it on two lines for legibility here
What is weird is that if I die and dump the request, I can see all of the input:
dd($request->all());
However, if I try to get it from the session, I don't see any of the input:
#if($errors)
{{var_dump(Session::all())}}
#endif
Any idea why {{ old('first_name') }} isn't working in this case?
Thanks!
You're Returning a view return view() instead you need to return redirect return redirect()as a return view will generate a fresh page and old() will not work.
So instead of
return view('partials.question_02_' . $type)
Use
return redirect()
&in your case
redirect()->action('LoginController#secondPage'); //you controller and method handling your second page
Consider the manual authentication. If the order ID has not been found in database, we redirect user to page with input fields and with error 'wrongOrderId':
public function login(Request $request) {
$inputted_orderId = $request->input('order_id');
$orderIdInDB = DB::table(self::SITE_IN_DEVELOPMENT_TABLE_NAME)
->where(self::ORDER_ID_FIELD_NAME, $inputted_orderId)->first();
if (is_null($orderIdInDB)) {
return Redirect::route('loginToSitesInDevelopmentZonePage')->withErrors('wrongOrderId');
}
}
In this example, we don't need to pass the error message: the message box is already exists in View; all we need is to display this message box when user has been redirected with error 'wrongOrderId':
#if (!empty($errors->first('wrongOrderId')))
<div class="signInForm-errorMessagebox" id="invalidID-errorMessage">
<!-- ... -->
</div>
#endif
All above code is working without laravel/php errors; I get into is_null($orderIdInDB) if-block when input wrong order number, however the error message box don't appears. I tried a lot of ways, and (!empty($errors->first('wrongOrderId'))) is just last one. What I doing wrong?
Did you try printing {{$errors->first()}}?
first(string), works as a key value pair, it invokes its first key VALUE
try this,
#if($errors->any())
<div class="signInForm-errorMessagebox" id="invalidID-errorMessage">
<!-- ... -->
</div>
#endif
If you want to get specific error from validator error, use the get() method.
$errors->get('wrongOrderId'); get all the wrongOrderId errors.
$errors->first('wrongOrderId'); get first error of wrongOrderId errors
I explored that $errors->first('wrongOrderId') has non-displayable but non-null value. So #if ( $errors->first('wrongOrderId') !== null ) will work.
Something more elegantly like #if ($errors->first('wrongOrderId'))? Unfortunately just one this check is not enough: even if we define
return Redirect::route('loginToSitesInDevelopmentZonePage')->withErrors(['wrongOrderId' => true]);
php will convert true to 1. So, #if ( $errors->first('wrongOrderId') == true ) will working, but #if ($errors->first('wrongOrderId')) will not.
However, we can to cast $errors->first('wrongOrderId') to bool, so the most elegant solution will be
#if ((bool) $errors->first('wrongOrderId'))
I have a problem with laravel, and my form.
In my form (createBand.blade.php), i made a form with a dropdownlist wich call a database table: musicalGenre.
It should be noted that the dropdownmenu/list (select form) calls to another table in the database called MusicalGenre, and the form where I want the dropdownlist is to register a band in the database table Bands.
The select form works, i could choose in a dropdownlist all musicalGenre_name in my table after seeding them. Here's the blade page code (of course i have open, close the form and the section like laravel requires):
<p>
{{Form::label('name','Name of the Band: ')}}
{{Form::text('name',Input::old('name'))}}
</p>
#if ($errors->has('name'))
<p class='error'> {{ $errors->first('name')}}</p>
#endif
<br>
<br>
<p>
{{Form::label('email','Band Email: ')}}
{{Form::text('email',Input::old('email'))}}
</p>
#if ($errors->has('email'))
<p class='error'> {{ $errors->first('email')}}</p>
#endif
<br>
<br>
<p>
{{Form::label('musicalGenre','your style: ')}}
{{Form::select('musicalGenre_name', $genre_options = array('' => 'select your style') + musicalGenre::lists('name'), Input::old('musicalGenre_name')) }}
</p>
#if ($errors->has('musicalGenre'))
<p class='error'> {{ $errors->first('musicalGenre_name')}}</p>
#endif
<br>
<br>
I have a controller named createBandController, where i made some rules for validators for the blade form. The poblem is:
i can't pass the validators, that is to say, even if i choose a musical genre in my dropdownlist, for laravel there no choice made.
I have the error "musical genre is required". I don't understand the validator rules for a select form in my controller, or i don't know what i'm suposed to input in the rules for musical genre. Here's the controller code:
public function createBand() {
$result = MusicalGenre::all();
$genre = $result->lists('name');
$inputs = Input::all();
$rules = array(
'name' => 'required|between:1,64|unique:bands,name',
'email' => 'between:1,128|email|unique:bands,email',
'musicalGenre' => 'integer|required|in:musicalGenre'
);
$validation = Validator::make($inputs, $rules);
if ($validation->fails()) {
return Redirect::to('createBand')->withInput()->withErrors($validation)
->with('alert_error', 'you have some mistakes');
Don't pay attention to the names, (i'm french, i changed them in order to be clear for you), and i'm sure that is the validator of my dropdownlist who make problems when i fill out my form, because i can't pass it. In my original project code, there are no spelling mistakes.
All my validators and variable names work. I think i need to find the correct rules for a select form input and validators in order to laravel knows i made a choice when i choose in my dropdownlist.
At first i thought to specify that the dropdownlist use the database table musicalGenre. Like i specified that some fields are in the database table Bands, like this:
'name' => 'required|between:1,64|unique:bands,name'
Here, "name" is a field of the database table Bands. But it didn't work too.
If anyone have a solutions or wants to help, i'm interested.
Thank you (and sorry if my english seems so bad).
The validation rule in: on the field musicalGenre won't work the way that you have implemented it. It expects a comma delimited list of strings which the field value is scrubbed against.
For example if the field was 'Gender' the validation would be:
'gender' => 'in:male,female'
To validate against musicalGenre against a model you will need to write a custom Validator. See http://laravel.com/docs/validation#custom-validation-rules
I am currently writing a custom validator for this 'belongs_to' validation and when I have it working I'll post it here.
UPDATE
I have written a custom validation rule that should help. Firstly create a validators.php file and include it in global.php
require app_path().'/validators.php';
Within validators.php create the custom rule:
Validator::extend('exists_in', function($attribute, $value, $parameters)
{
$result = $parameters[0]::find($value);
return ($result == null) ? FALSE : TRUE;
});
In your validations you could now have:
'musicalGenre' => 'integer|required|exists_in:musicalGenre'
The exists_in validation takes a parameter of the Model Class name
And to give this validation an error message, add the following to the array in app/lang/en/validation.php:
"exists_in" => "The selected :attribute is invalid."
Does exists:musicalGenre instead of in:musicalGenre help?
Is there any utility function in Laravel that allows you to give an alternative value for a specific input field if the old value is empty? Currently I have the following code:
<input type="text" class="form-control" id="title" name="title" value="{{ (!empty(Input::old('title'))) ? Input::old('title') : 'hey' }}">
But its not really that pretty. Any ideas?
use
Input::old('title', 'fallback value')
Yes! Don't use the input tag :)
If you use {{ Form you will get this, and much, much more!
{{ Form::text('email', null, array('class'=>'form-control', 'placeholder'=>'Email Address')) }}
Check out the docs here (http://laravel.com/docs/html & http://laravel.com/docs/requests) and you will notice that when the input is flashed to the session, this input box rendered by blade will automatically replace that "null" (the second parameter) with the flashed value from the session.
This removes the need to check for the old input or have any nasty if/else checks inside your template. Also, you no longer need to worry about any HTML code injections or XSS happening, because Form::text will ensure that the text is correctly converted to their HTML entities.
Where you are checking for errors, you should use a Laravel validator. Something similar to this:
protected function createUser(){
$rules = array(
'email'=>'required|email',
'password'=>'required|min:6|confirmed',
'password_confirmation'=>'required'
);
$validator = Validator::make(Input::all(), $rules);
if (! $validator->passes()) {
Input::flashExcept('password', 'password_confirmation');
return Redirect::to('my_form');
} else {
// do stuff with the form, it's all good
}
return Redirect::intended('/complete');
}
Additionally, in your template, you can show all of the errors from the form:
<ul>
#foreach($errors->all() as $error)
<li>{{ $error }}</li>
#endforeach
</ul>
Or just select the first error and show it under the {{ Form::text
#if ($errors->has('first_name'))
<span class="error">{{$errors->first('first_name')}}</span>
#endif
Laravel has all of this built in, and you get it for free! Using Requests, Validators, Blade/HTML