j-p's avatar
Level 1

How do I add an input value on the fly inside a request instance?

I'm validating a form's input using a request object. The form contains three fields (dd-mm-yyyy) that I parse into a date object so it can be validated as such. However, since I'm doing that right before the validation anyway, I want it to be stored in the request as its own input value so it can be passed on the the command bus when validation succeeds.

I just can't seem to figure out how to add it to the request as an input value.

0 likes
19 replies
willvincent's avatar

You could parse your separate fields and populate a hidden field with the parsed data with javascript. Then it would already be getting submit as an input value.

kfirba's avatar

@ilmarinen Let's say after appending + validating your dd-mm-yyy fields, you have the variable $fullDate. Now you can simply add it to your Request:

$this['date'] = $fullDate;
j-p's avatar
Level 1

Actually @kfirba I was a bit too quick to approve your answer. Doing it your way, the value doesn't actually become a request parameter and as such cannot be validated inside the request instance.

The correct way I found after some more fiddling is: $this->request->add(['some_key' => 'some_value']);

1 like
j-p's avatar
Level 1

@kfirba & @nolros... just found out unfortunately that it still won't be picked up by the validator this way :( Need to find another way again.

j-p's avatar
Level 1

@nolros

I have a controller for processing a form. The method that handles the post request has a Form Request Validation class injected so the validation is done before the method is called.

My problem is that I have three separate post values for a date of birth: day, month and year. They need to be parsed into a valid date format and I figured that if I do that in the Form Request class itself and store is there the validator would access it.

public function rules()
{
    
    $this->request->add(['date_of_birth' => implode('-', $this->only('year', 'month', 'day'))]);

    return [
        'name'          => 'required|between:3,255',
        'email'         => 'required|email|between:3,100',
        'date_of_birth' => 'required|date' // Validation should be done here then :)
    ];
}

1 like
nolros's avatar

@ilmarinen

Why are you not just passing the date through the controller / command and manage it there? Is there a reason you want to do it in validation? Is the date_of_birth a valid date, seems like you already have the date?

Carbon can to this for you if you need, but again I would do it at the end when you process the request unless you are expecting some sort of validation, but it looks like you are injecting it vs validating against it:

$date = Carbon::parse( $date)->toFormattedDateString();
j-p's avatar
Level 1

@nolros

For now we have three very simple input field for this using an HTML <select>. The problem is that the available amount of days for now is set to 31, regardless the chosen month which could result in incorrect dates like February 31st.

(Facebook has the exact same thing when signing up by the way.)

I want to validate it so I can return an error message after submitting stating that the date is not valid if that's the case (or someone has been messing with the option fields in developer mode).

I know I can use carbon to format the date but as far as I know the validation of a date string is already ran through carbon by Laravel's validator. Simply parsing it into a string as I did should've been sufficient were it not for the fact that the validator doesn't take the input values from the Form Request object anymore at that point apparently. :(

j-p's avatar
j-p
OP
Best Answer
Level 1

@nolros

Now I have got it working... Not that pretty yet though but I'll see if I can extract this to somewhere later.

In the Form Request's constructor I inject \Illuminate\Http\Request. I can then add my value to the request instance as such:

    public function __construct(\Illuminate\Http\Request $request)
    {
        $request->request->add(['date_of_birth' => implode('-', $request->only('year', 'month', 'day'))]);
    }

After that I can just add the rule for validating date_of_birth in the rules method.

This has three advantages in my opinion:

  1. Laravel will pick it up as if the parsed date were posted along with the form.
  2. All validation will be done before my controller's method is called and any invalid date will be returned as error (plus some other validation like before or after)
  3. I can use dispatchFrom to very easily generate my command DTO

All in all my controller will stay very very lean this way.

10 likes
j-p's avatar
Level 1

Thanks @blackbird !

Where's the part in the documentation about creating your own Request object to inject in the constructor? Could you tell me a bit more about it perhaps if you have the time?

bobbybouwmann's avatar

The incoming form request is validated before the controller method is called, meaning you do not need to clutter your controller with any validation logic. It has already been validated!

If validation fails, a redirect response will be generated to send the user back to their previous location. The errors will also be flashed to the session so they are available for display. If the request was an AJAX request, a HTTP response with a 422 status code will be returned to the user including a JSON representation of the validation errors.

So, how are the validation rules executed? All you need to do is type-hint the request on your controller method. Time for an example:

Example 1: using the constructor

class MoviesController extends Controller {
    
    protected $movieRepository;

    protected $movieRequest;

    /**
     * Constructor
     */
    public function __construct(MovieRepository $movieRepository, MovieRequest $movieRequest)
    {
        $this->movieRepository = $movieRepository
        $this->movieRequest = $movieRequest;
    }

    public function store()
    {
        $this->movieRepository->store($this->movieRequest);
        
        return redirect('movies');
    }
}

Example 2: just one function

use App\Http\Request\MovieRequest;
use App\Repositories\MovieRepository;

class MoviesController extends Controller {

    protected $movieRepository;

    /**
     * Constructor
     */
    public function __construct(MovieRepository $movieRepository)
    {
        $this->movieRepository = $movieRepository;
    }

    public function store(MovieRequest $request)
    {
        $this->movieRepository->store($request);
        
        return redirect('movies');
    }

}

And then your request can look like this

<?php namespace App\Http\Requests;

class MovieRequest 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 [
            'name' => 'required|min:3',
            'description' => 'required',
            'publishdate' => 'required|date'
        ];
    }

}
j-p's avatar
Level 1

@blackbird

Thanks, but that's exactly what I already did! :)

Basically my entire problem revolved around me wanting to use a Form Request Object. My problem was that the form it was validating posted three separate values for a date of birth (year, month and day obviously) from a <select> dropdown.

I wanted my Form Request Object to validate whether or not that date was valid and if the user was old enough ('date|before') but since the values were posted as separate variables I couldn't validate it this way. In order to do so I needed to parse the values into a valid date string and add it to the request parameters so the validator would pick it up.

I tried setting the request parameter in the rules() method of the Form Request Object but somehow that's already too late and the validator won't "see" it anymore.

Then I tried the constructor but the Request object isn't available as property from the parent yet at that point.

Apparently it only works when I directly inject the current request object into the Form Request Object's constructor.

koufax's avatar

This helped me tremendously with creating dynamic inputs on the back end. Thanks for the post!

1 like
stevejamesson's avatar

@travoltron

Not 5.3, but I just got it working with 5.4.

My situation: The form submits these inputs via POST:

`date`          // '2018-01-01'
`start_time`    // '10:30 AM'
`end_time`      // '2:00 PM'

I needed to parse those into Carbon instances (starts_at and ends_at), then compare those two dates in the validator, then use them elsewhere (i.e. the Controller).

class MyFormRequest extends FormRequest
{
    public function __construct(\Illuminate\Http\Request $request)
    {
        $request->merge([
            'starts_at' => Carbon::parse("$request->date $request->start_time"),
            'ends_at'   => Carbon::parse("$request->date $request->end_time"),
        ]);

        // don't use `$this->some_input` in constructor, though
        // fails since `$this` isn't fully instantiated yet (or something)
        $request->merge(['some_input' => $this->some_input]);
        # Fails: cannot `get()`...
    }

    public function rules()
    {
        // These are now available here
        $this->starts_at; // Carbon{}
        $this->ends_at;   // Carbon{}

        return [
            // can do
            'starts_at' => 'required', // etc.
            // ...
        ];
    }

    // but you probably need this
    public function withValidator(Validator $validator)
    {
        $validator->after(function ($validator) {
            // if starts_at > ends_at, add an error
        });
    }
}

And those fields are available on the $request from, say, the controller:

class MyController
{
    public function store(MyFormRequest $request)
    {
        $request->starts_at; // Carbon instance #1
        $request->ends_at;   // Carbon instance #2

        // the originals are still available, too
        $request->date;
        $request->start_time;
        $request->end_time;
    }
}

I think using $request->merge() is cleaner than $request->request->add(), though add() should still work.

Please or to participate in this conversation.