Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Shivangi58's avatar

Validation rule error

Please help I am unable to validate here

My post request broke as I realised I have to validate for every single field and not just the one that are required, so I am written a test, which helped me figure that out, but I am unable to understand what validation rule should go for patient_id

This is my TreatmentRequest class

public function rules()
    {
        return [
            //
            'title' => ['required'],
            'patient_id' => ['unique'],
            'notes' => ['nullable'],
            'start_date' => ['required'],
            'end_date' => ['date'],
            'start_time' => ['nullable'],
            'end_time' => ['nullable'],

        ];
    }

Another thing I am trying to do, is make

end_date => ['nullable|date|after:start_date'] 

and I get the following error

Method Illuminate\Validation\Validator::validateRequired|date|after does not exist.

0 likes
44 replies
tykus's avatar

You need to tell the validator which table (and optionally column) to check for uniqueness, e.g.

'patient_id' => ['unique:treatments,patient_id'],

I don't know what your rule is intended to check so adjust according to your needs

You're using array syntax, not pipes; so, separate the strings

end_date => ['nullable', 'date', 'after:start_date'] 
Shivangi58's avatar

@tykus patient id is like a user id, so it is referring to the user here as a foreign id My database

 Schema::create('treatments', function (Blueprint $table) {
            $table->id();
            $table->foreignId('patient_id')->references('id')->on('users')->onDelete('cascade');
            $table->string('title');
            $table->text('notes')->nullable();
            $table->date('start_date');
            $table->date('end_date')->nullable();
            $table->time('start_time')->nullable();
            $table->time('end_time')->nullable();
            $table->boolean('is_completed')->default(false);
            $table->integer('parent_id')->nullable();
            $table->timestamps();
        });

My controller store function that was breaking and the tests told me that validation is the issue

public function store(TreatmentRequest $request)
    {

        return auth()->user()
            ->treatments()
            ->create($request->validated());
        //return Treatment::create($request->all());

    }

PS : fixed the syntax issue by removing brackets

tykus's avatar

@Shivangi58 right... my italicized question was about the meaning of the unique validation rule - the patient_id should unique in what context?

Shivangi58's avatar

Yeah so I didn't know what validation rule applies, I was just trying it out, of course, and like user id is unique so in that sense

tykus's avatar

@Shivangi58 I don't think you need unique In that case, but maybe exists to ensure the given patient_id exists as a record in the users table:

'patient_id' => ['exists:users,id'],

I couldn't imagine a scenario where the patient_id should be unique in the treatments table - meaning a Patient could have only one Treatment (ever)

Shivangi58's avatar

This is how I am testing the post controller

$treatment = Treatment::factory()->make();

        //$treatment = $this->createTreatment();
        //dd($treatment);

        $response = $this->postJson(route('treatments.store'), [
            'title' => $treatment->title,
            'notes' => $treatment->notes,
            'start_date' => $treatment->start_date,
            'end_date' => $treatment->end_date,
            'patient_id' => $treatment->patient_id,
            'start_time' => $treatment->start_time,
            'end_time' => $treatment->end_time
        ])
        ->assertCreated()
        ->json();
        //dd($response);


        $this->assertEquals($treatment->title, $response['title']);
        $this->assertEquals($treatment->notes, $response['notes']);
        $this->assertEquals($treatment->patient_id, $response['patient_id']);
        $this->assertEquals($treatment->start_date, $response['start_date']);
        $this->assertEquals($treatment->end_date, $response['end_date']);
        $this->assertEquals($treatment->start_time, $response['start_time']);
        $this->assertEquals($treatment->end_time, $response['end_time']);

        $this->assertDatabaseHas('treatments', [
            'title' => $treatment->title,
            'notes' => $treatment->notes,
            'start_date' => $treatment->start_date,
            'end_date' => $treatment->end_date,
            'patient_id' => $treatment->patient_id,
            'start_time' => $treatment->start_time,
            'end_time' => $treatment->end_time
        ]);
    }
ErrorException : Undefined array key "patient_id"

Without it the tests pass, is auth need to be validated too, maybe, I am not sure, this is my first time with php and laravel

tykus's avatar

@shivangi58 is the patient_id actually in the JSON response from the POST request; that would seem to be the failure.

Shivangi58's avatar

Yes, this is my die dump json treatment from the factory set up like this

return [

            //
            'title' => $this->faker->title,
            'notes' => $this->faker->sentence,
            'patient_id' => function() {

                return User::factory()->create()->id;
            },
            'start_date' => $this->faker->date,
            'end_date' => $this->faker->date,
            'start_time' => $this->faker->time,
            'end_time' => $this->faker->time,
        ];

And this is my controller

public function store(TreatmentRequest $request)
    {

        return auth()->user()
            ->treatments()
            ->create($request->validated());
        //return Treatment::create($request->all());

    }

And this is the $response die dumped version in the tests


]8;;file:///Users/shivangisharma/Documents/PhpstormProjects/PolymorphicTests/tests/Feature/TreatmentTest.php#L93\^]8;;\ array:9 [
  "title" => "Prof."
  "notes" => "Necessitatibus omnis quia adipisci itaque sit."
  "start_date" => "1983-06-18"
  "end_date" => "1992-10-12"
  "start_time" => "16:04:12"
  "end_time" => "07:40:11"
  "updated_at" => "2022-04-11T19:16:53.000000Z"
  "created_at" => "2022-04-11T19:16:53.000000Z"
  "id" => 2
]

Obviously in the above code, passing the patient id makes it fail so therefore I did not pass it

But that means patient id is not working correctly for Post request (I am building APIs)

tykus's avatar

@Shivangi58 if there is no patient_id in the response JSON payload, then this assertion will fail:

$this->assertEquals($treatment->patient_id, $response['patient_id']);

I don't see patient_id in the dumped response, so that would explain the failure!

You must have a patient_id key in the validation rules (even with empty rules array), to ensure you have patient_id in the $request->validated() array - though I suppose this is the case if the database assertion passes? So, is patient_id hidden in the Treatment model?

Shivangi58's avatar

@tykus Correct that is what I am trying to do, but how to code the validation rule for patient_id is throwing me off, in my tests for patient_id, I have to pass 3 assertions, written by me,

$response = $this->postJson(route('treatments.store'), [
            'title' => $treatment->title,
            'notes' => $treatment->notes,
            'start_date' => $treatment->start_date,
            'end_date' => $treatment->end_date,
            'patient_id' => $treatment->patient_id,      // this one 
            'start_time' => $treatment->start_time,
            'end_time' => $treatment->end_time
        ])
$this->assertEquals($treatment->patient_id, $response['patient_id']);
$this->assertDatabaseHas('treatments', [
            'title' => $treatment->title,
            'notes' => $treatment->notes,
            'start_date' => $treatment->start_date,
            'end_date' => $treatment->end_date,
            'patient_id' => $treatment->patient_id,              // this one 
            'start_time' => $treatment->start_time,
            'end_time' => $treatment->end_time
        ]);
tykus's avatar

@Shivangi58 so why are you sending a patient_id in the Request JSON payload?

You don't need patient_id in the JSON payload; and therefore, you don't need a validation rule.

Shivangi58's avatar

@tykus so I don't need to test patient_id is in the database for the treatment, or you mean something else is wrong?

tykus's avatar

@Shivangi58 I mean that your patient_id is being set by the relationship because you are using the relationship to create the Treatment; so, you don't need to pass a patient_id at all.

I assume your test signs in a particular User? But your Treatment factory creates another User and submits that as patient_id. So your test (and the validated data) is messing up your expectations.

1 like
Shivangi58's avatar

@tykus that makes sense I removed the patient_id tests and I die dump the controller code and this is what I get

#attributes: array:10 [
    "title" => "Mrs."
    "notes" => "Libero velit porro quis."
    "start_date" => "2018-05-19"
    "end_date" => "1972-09-16"
    "start_time" => "10:11:30"
    "end_time" => "08:44:03"
    "patient_id" => 1
    "updated_at" => "2022-04-11 19:50:47"
    "created_at" => "2022-04-11 19:50:47"
    "id" => 2
  ]

I think my controller store method should work fine now, do you think?

tykus's avatar

@Shivangi58 okay. So you have a patient_id that is (probably) matching the authenticated user's ID now? Is the patient_id in the response payload?

Ideally, you would not rely on the JSON representation of the Model for your response; rather you would use Eloquent API Resources so you could define a consistent structure independent of database schema changes

Shivangi58's avatar

@tykus I have this Unit test to test the relationship and this test passes


public function test_user_has_many_treatments()
    {

        $user = User::factory()->create();
        Sanctum::actingAs($user);
        $treatments = $this->createTreatment([
            'patient_id' => $user->id
        ]);

        $this->assertInstanceOf(Treatment::class, $user->treatments->first());
    }

Should that work then for my post request since the auth user and patient id relationship is tested? Yes, my next task was to change this into API resources

tykus's avatar

@Shivangi58 I wouldn't think so; createTreatment probably doesn't touch the Controller action. So it doesn't prove the functionality of that POST request made to that endpoint which hits that controller action, which creates that Treatment which responds with that payload.

In the feature test; I would be confirming that the Treatment being created belongs to the authenticated user!

Shivangi58's avatar

@tykus This is how I am creating the treatments in the feature test

public function setUp():void{

        parent::setUp();
        $user = User::factory()->create();
        Sanctum::actingAs($user);
        $this->treatment = $this->createTreatment([
            'title' => 'Insulin',
            'patient_id' => $user->id,
            'start_date' => '2022-03-28',
            'end_date' => '2022-06-28',
            'start_time' => '12:02:50',
        ]);

    }

    public function createTreatment($args = []) {

        return Treatment::factory()->create($args);

    }

And, yes, the next part was to change to API Resources

tykus's avatar

@Shivangi58 okay, but still that test_user_has_many_treatments doesn't prove the feature. And it doesn't prove that that Treatment will belong to the authenticated user. Testing the Unit, and testing the Feature are different considerations.

Shivangi58's avatar

@tykus So this latest one is a feature test, I pass the $user to Sanctum so what is your recommendation?

So I just passed this test in feature test


$this->assertDatabaseHas('treatments', [
            'title' => $treatment->title,
            'notes' => $treatment->notes,
            'start_date' => $treatment->start_date,
            'end_date' => $treatment->end_date,
            'patient_id' => $treatment->patient_id,
            'start_time' => $treatment->start_time,
            'end_time' => $treatment->end_time
        ]);

tykus's avatar

@Shivangi58 you should not be using the patient_id from the factory - it is a different User; you could have the authenticated User as a property of the Test class; then:

$this->assertDatabaseHas('treatments', [
    'title' => $treatment->title,
    'notes' => $treatment->notes,
    'start_date' => $treatment->start_date,
    'end_date' => $treatment->end_date,
    'patient_id' => $this->user->id,
    'start_time' => $treatment->start_time,
    'end_time' => $treatment->end_time
]);
Shivangi58's avatar

I am doing something like this from the documentation https://laravel.com/docs/9.x/sanctum

Sanctum::actingAs(
        User::factory()->create(),
        ['view-tasks']
    );

So, this does test authentication?

This is my factory

public function definition()
    {
        return [

            //
            'title' => $this->faker->title,
            'notes' => $this->faker->sentence,
            'patient_id' => function() {

                return User::factory()->create()->id;
            },
            'start_date' => $this->faker->date,
            'end_date' => $this->faker->date,
            'start_time' => $this->faker->time,
            'end_time' => $this->faker->time,
        ];
    }
tykus's avatar

@Shivangi58 yes it authenticates.

What problem are you left trying to solve with the test/implementation now?

Shivangi58's avatar

I only test now this. This test passes as you can see I create a Sanctum user, pass to treatment create, and then now I have commented out the postJson patient_id and I also comment out the assertion assertEqual patient_id but I test whether the database has patient_id, and this one passes. So, the question is, is this the right approach since I removed the patient_id from JsonResponse, like you said, that it would be assigned by the relationship, and therefore I believe the database check passes

public function store_treatment() {

        //$treatment = Treatment::factory()->make();

        $treatment = $this->createTreatment();
        //dd($treatment);

        $response = $this->postJson(route('treatments.store'), [
            'title' => $treatment->title,
            'notes' => $treatment->notes,
            'start_date' => $treatment->start_date,
            'end_date' => $treatment->end_date,
            //'patient_id' => $treatment->patient_id,
            'start_time' => $treatment->start_time,
            'end_time' => $treatment->end_time
        ])
        ->assertCreated()
        ->json();
        //dd($response);


        $this->assertEquals($treatment->title, $response['title']);
        $this->assertEquals($treatment->notes, $response['notes']);
        //$this->assertEquals($treatment->patient_id, $response['patient_id']);
        $this->assertEquals($treatment->start_date, $response['start_date']);
        $this->assertEquals($treatment->end_date, $response['end_date']);
        $this->assertEquals($treatment->start_time, $response['start_time']);
        $this->assertEquals($treatment->end_time, $response['end_time']);

        $this->assertDatabaseHas('treatments', [
            'title' => $treatment->title,
            'notes' => $treatment->notes,
            'start_date' => $treatment->start_date,
            'end_date' => $treatment->end_date,
            'patient_id' => $treatment->patient_id,
            'start_time' => $treatment->start_time,
            'end_time' => $treatment->end_time
        ]);
    }

Also, I don't see a validation rule for time , so pass it as nullable

tykus's avatar

@Shivangi58 this is wrong though; why do you create a Treatment before sending the Request; now there is a database record already? The problem with the $treatment variable is the User that it is associated with; which is not the authenticated user; it is another User created by the Factory.

All you care about is the raw values which would represent the JSON payload sent in the Request; you can get these from the Factory methods, make (Object) or raw (Array) - since you don't really need an Object; I am using the Array result below:

protected $user;

public function setUp():void
{
        parent::setUp();
        $this->user = User::factory()->create();
        Sanctum::actingAs($user);
		// ...
    }

Now, use $this->user in the assertions about which User is associated:

public function store_treatment()
{
    $treatment = Treatment::factory()->raw();
    
    $response = $this->postJson(route('treatments.store'), [
            'title' => $treatment['title'],
            'notes' => $treatment['notes'],
            'start_date' => $treatment['start_date'],
            'end_date' => $treatment['end_date'],
            'start_time' => $treatment['start_time'],
            'end_time' => $treatment['end_time'],
        ])
        ->assertCreated()
        ->json();

        $this->assertEquals($treatment['title'], $response['title']);
        $this->assertEquals($treatment['notes'], $response['notes']);
        $this->assertEquals($this->user->id, $response['patient_id']);
        $this->assertEquals($treatment['start_date'], $response['start_date']);
        $this->assertEquals($treatment['end_date'], $response['end_date']);
        $this->assertEquals($treatment['start_time'], $response['start_time']);
        $this->assertEquals($treatment['end_time'], $response['end_time']);

        $this->assertDatabaseHas('treatments', [
            'title' => $treatment['title'],
            'notes' => $treatment['notes'],
            'patient_id' => $this->user->id,
            'start_date' => $treatment['start_date'],
            'end_date' => $treatment['end_date'],
            'start_time' => $treatment['start_time'],
            'end_time' => $treatment['end_time'],
        ]);
    }

So in summary, your test is asserting

  • that you get a 201 response to the POST request
  • that the response JSON contains all of the values you specify (incl. the patient_id which was not in the original Request payload)
  • that there is a database record with the specified values (incl. the patient_id which matches the authenticated user)
Shivangi58's avatar

@tykus This gives the following error

ErrorException : Attempt to read property "title" on array
tykus's avatar

@Shivangi58 I was showing you how to use raw; which results in an array but you seem to still be using the Object operator ->

Shivangi58's avatar

@tykus My bad, rewrote everything but the same first error

ErrorException : Undefined array key "patient_id"
public function store_treatment() {

        $treatment = Treatment::factory()->raw();

        //$treatment = $this->createTreatment();
        //dd($treatment);

        $response = $this->postJson(route('treatments.store'), [
            'title' => $treatment['title'],
            'notes' => $treatment['notes'],
            'start_date' => $treatment['start_date'],
            'end_date' => $treatment['end_date'],
            //'patient_id' => $treatment->patient_id,
            'start_time' => $treatment['start_time'],
            'end_time' => $treatment['end_time']
        ])
            ->assertCreated()
            ->json();
        //dd($response);


        $this->assertEquals($treatment['title'], $response['title']);
        $this->assertEquals($treatment['notes'], $response['notes']);
        $this->assertEquals($this->user->id, $response['patient_id']);
        $this->assertEquals($treatment['start_date'], $response['start_date']);
        $this->assertEquals($treatment['end_date'], $response['end_date']);
        $this->assertEquals($treatment['start_time'], $response['start_time']);
        $this->assertEquals($treatment['end_time'], $response['end_time']);

        $this->assertDatabaseHas('treatments', [
            'title' => $treatment['title'],
            'notes' => $treatment['notes'],
            'start_date' => $treatment['start_date'],
            'end_date' => $treatment['end_date'],
            'patient_id' => $treatment['patient_id'],
            'start_time' => $treatment['start_time'],
            'end_time' => $treatment['end_time'],
        ]);
    }
public function setUp():void{

        parent::setUp();

        $this->user = User::factory()->create();
        Sanctum::actingAs($this->user);

        $this->treatment = $this->createTreatment([
            'title' => 'Insulin',
            'patient_id' => $this->user->id,
            'start_date' => '2022-03-28',
            'end_date' => '2022-06-28',
            'start_time' => '12:02:50',
        ]);
        //error_log('patient_id');

    }

tykus's avatar

@Shivangi58 nearly...

        $this->assertDatabaseHas('treatments', [
            'title' => $treatment['title'],
            'notes' => $treatment['notes'],
            'start_date' => $treatment['start_date'],
            'end_date' => $treatment['end_date'],
            'patient_id' => $this->user->id, // authenticated user!
            'start_time' => $treatment['start_time'],
            'end_time' => $treatment['end_time'],
        ]);
Shivangi58's avatar

@tykus Changed it

$this->assertDatabaseHas('treatments', [
            'title' => $treatment['title'],
            'notes' => $treatment['notes'],
            'start_date' => $treatment['start_date'],
            'end_date' => $treatment['end_date'],
            'patient_id' => $this->user->id,
            'start_time' => $treatment['start_time'],
            'end_time' => $treatment['end_time'],
        ]);

ErrorException : Undefined array key "patient_id"
/**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            //
            'title' => ['required'],
            'patient_id' => ['exists:users,id'],
            'notes' => ['nullable'],
            'start_date' => ['required'],
            'end_date' => 'nullable|date',
            'start_time' => 'nullable',
            'end_time' => 'nullable',
            'is_completed' => 'nullable|boolean'

        ];
    }

Same error I used to get for others, and I had fixed through validation except 'patient_id'

tykus's avatar

@Shivangi58 we're going around in circles... you don't need patient_id in the validation rules because you don't need patient_id in the Request payload - the Patient is always the authenticated user

From where are you getting the undefined index error - which class/method line?

Shivangi58's avatar

Commented it out in Validation rules, this is the line with error in FeatureTest/TreatmentTest

$this->assertEquals($this->user->id, $response['patient_id']);
tykus's avatar

@Shivangi58 okay; this is caused by returning the object from create In the controller.

Make the API Resource eventually, but for now:

public function store(TreatmentRequest $request)
{
    $treatment = auth()->user()
            ->treatments()
            ->create($request->validated());
    return $treatment->fresh();
}

Unless you have patient_id either hidden; or you have overridden toArray in Treatment, it must be in the response.

If patient_id is not in the Response; please post the Treatment model in full here.

Shivangi58's avatar

This is the error now

Expected response status code [201] but received 200.
Failed asserting that 201 is identical to 200.

For line in FeatureTest/TreatmentTest

$response = $this->postJson(route('treatments.store'), [
            'title' => $treatment['title'],
            //
        ])
            ->assertCreated()
            ->json();

Treatment model

class Treatment extends Model
{
    use HasFactory, RefreshDatabase, Notifiable;

    protected $hidden = [
        'patient_id'
    ];

    protected $guarded = [];

    public function path() {

        return "/treatments/{$this->id}";
    }

    public function patient() {

        return $this->belongsTo(User::class);
    }

    public function rule(): HasOne
    {
        return $this->hasOne(Rule::class);
    }


tykus's avatar

@Shivangi58 come on..... more that 2 hours ago I asked So, is patient_id hidden in the Treatment model?

    protected $hidden = [
        'patient_id'
    ];

Why do you think I asked that; what do you think $hidden does?

Shivangi58's avatar

Sorry, didn't recall I did that, came back to my code after 2 week of sickness, I apologise again, so we don't keep it hidden anymore ? Sorry again, And I think RefreshDatabase should also go?

tykus's avatar
tykus
Best Answer
Level 104

@Shivangi58 if you want patient_id in the Response; and you want to return the JSON representation of the Model; then it cannot be hidden.

RefreshDatabase has no place in the Model class - it is a Testing trait.

1 like
Shivangi58's avatar

Ok, commented that removed the hidden and RefreshDatabase in the Treatment model, changed the controller back. The tests pass. Thank you for showing the light, was really lost, thanks a ton!

1 like
Shivangi58's avatar

Ok got it will bring the RefreshDatabase back

Please or to participate in this conversation.