With Laravel model factories, how do for(), has() and hasAttached() build related records, and what do magic calls like hasAppointments(3) resolve to?
answer
- parent side vs child side
- for() resolves one parent per chain
- has() runs after the parent's save()
- hasAttached() second argument = pivot data
- hasX / forX read the relation method
basics
~20 sfor() gives created models a belongsTo parent, from a factory or an existing model; has() creates hasMany or belongsToMany children after the parent is saved; hasAttached() attaches belongsToMany models with pivot values. hasAppointments(3) is has() driven by the appointments() relation.
solid answer
~40 s`for()` works on the child: `Appointment::factory()->count(4)->for(Doctor::factory())->create()` creates one doctor and uses it for all four, while `for($doctor)` reuses an existing model. `has()` works on the parent: `Doctor::factory()->has(Appointment::factory()->count(3))->create()` saves the doctor, then creates three appointments through its relation so `doctor_id` is filled. The relation name is guessed from the related model (`appointments`, else `appointment`) or passed as the second argument. `hasAttached(Clinic::factory()->count(2), ['room' => '3B'])` creates clinics and attaches them with pivot values; given models or a collection it attaches existing rows. Magic methods read the relation: `hasAppointments(3, [...])` means `has()` with a count and a state, `forDoctor([...])` means `for()` with a state. Children are built only by `create()`, never by `make()`.
code
php · 16 lines<?php
use App\Models\Appointment;
use App\Models\Clinic;
use App\Models\Doctor;
// One doctor, four appointments that belong to it
Appointment::factory()->count(4)->for(Doctor::factory())->create();
// Two doctors, each with three appointments (6 in total)
Doctor::factory()->count(2)->hasAppointments(3)->create();
// Each doctor attached to two new clinics, pivot room = 3B
Doctor::factory()
->hasAttached(Clinic::factory()->count(2), ['room' => '3B'])
->create();go deeper
Recall that for() sets a parent, has() creates children, and hasAttached() handles many-to-many with pivot values.
Explain when each helper touches the database, how the relation name is guessed, and what a magic hasX or forX call expands to.
Show that you choose for(), a definition() nested factory or existing models deliberately, because that choice sets how many parent rows a seeder writes.
Judge how much relationship wiring belongs in shared factories versus explicit seeder code, trading brevity against surprise inserts.
## Two directions Laravel's **model factories** can build related records in both directions of a relationship. The helpers only use relationships you already defined on the models (`belongsTo`, `hasMany`, `belongsToMany`); they do not define any. - **`for()`**: the model being built is the **child**, and `for()` names its `belongsTo` parent. - **`has()`**: the model being built is the **parent**, and `has()` describes children to create through a `hasMany`, `hasOne`, `morphMany` or `belongsToMany` relation. - **`hasAttached()`**: like `has()` for `belongsToMany`, but lets you set **pivot columns** and attach models that already exist. ## for(): attach to a parent `for()` accepts either a factory or a model instance, plus an optional relation name (by default the camel-cased model name, so `Doctor` means `doctor()`). 1. With a model, `for($doctor)` fills the foreign key from that model for every record. 2. With a factory, `for(Doctor::factory())` creates the parent **once** for the whole chain and reuses its key, so `count(4)` yields four appointments and one doctor. 3. For a `morphTo` relation it fills both the type and id columns. Contrast this with `'doctor_id' => Doctor::factory()` inside `definition()`, which creates a new doctor for **every** appointment. ## has(): create children after the parent `has(Appointment::factory()->count(3))` is evaluated only when the parent is persisted. After each parent `save()`, the factory calls the relation (`$doctor->appointments()`) to set the foreign key and creates the children, so `Doctor::factory()->count(2)->has(Appointment::factory()->count(3))->create()` inserts two doctors and six appointments. The relation name is guessed as the camel-cased plural of the related model (`appointments`); if the parent has no such method, the singular (`appointment`) is used, which suits `hasOne`. Pass the name as the second argument when it differs. A child state closure receives the parent as its second argument, so children can copy a value from it. ## hasAttached(): many-to-many with pivot data For a `Doctor` that `belongsToMany` `Clinic` through a pivot with a `room` column: - `hasAttached(Clinic::factory()->count(2), ['room' => '3B'])` creates two clinics per doctor and attaches each with `room = 3B`; - the pivot argument may be a closure that receives the parent, or a list of arrays applied in sequence, one per attached clinic; - passing a model or collection, `hasAttached($clinics, [...])`, attaches those existing rows to every doctor instead of creating new ones. Plain `has(Clinic::factory())` on a `belongsToMany` also works; it attaches with no pivot values. ## Magic methods The factory's `__call` turns `for…` and `has…` into relationship calls: | Call | Meaning | |---|---| | `hasAppointments(3)` | `has(AppointmentFactory->count(3), 'appointments')` | | `hasAppointments(3, ['status' => 'booked'])` | the same, with a state on the children | | `hasAppointments(['slot' => 1], ['slot' => 2])` | one child per array, in sequence | | `forDoctor(['name' => 'Dr Okafor'])` | `for(DoctorFactory->state([...]), 'doctor')` | | `hasClinics(2)` | `has()` on a `belongsToMany`: attach, no pivot values | The name after `has` or `for` is camel-cased and called on the model to find the related class, then that model's factory is resolved. Two limits: an array passed to a magic `has` call is a **state for the related models**, never pivot data; and a relation method that does not exist fails at call time. ## A worked hospital example Suppose `Department` `hasMany` `Doctor`, `Doctor` `hasMany` `Appointment`, and `Doctor` `belongsToMany` `Clinic` with a `room` pivot column. One chain can build a small, coherent slice of data: 1. `Department::factory()` creates the department. 2. `->has(Doctor::factory()->count(3)->hasAppointments(5)->hasAttached(Clinic::factory(), ['room' => '3B']))` adds three doctors to it, each with five appointments and one attached clinic. 3. `->create()` saves the department first, then each doctor, then each doctor's appointments and clinic attachment. Assuming the appointment factory references no other parent factory, that single call writes 1 department, 3 doctors, 15 appointments, 3 clinics and 3 pivot rows. Reading the chain inside out tells you the counts, which is exactly what an interviewer will ask you to do with a snippet like this. The order also shows why `has()` needs `create()`: every child waits for its parent's primary key. ## Traps - `make()` does not build `has()` children; only `create()` does. - A factory in `for()` creates one shared parent, while a nested factory in `definition()` creates one per record. - Putting pivot values in a magic call writes them to the related table, not the pivot.
- Why does hasClinics(2, ['room' => '3B']) not set the pivot column?Magic `has` calls map to `has()`, and an array argument there is a state for the related `Clinic` models, so the factory tries to write `room` on the clinics table. Pivot values need `hasAttached()`, whose second argument is the pivot data.
- How do you give each attached clinic a different pivot value?Pass a list of arrays as the pivot argument: `hasAttached(Clinic::factory(), [['room' => '1A'], ['room' => '2C']])`. The factory turns the list into a sequence and, if the related factory has no count, sets it to the number of arrays.
saying these in an interview costs you the question
- for(Doctor::factory()) with count(4) creates four doctors
- has() children are built by make() as well as create()
- The array in hasClinics(2, [...]) fills the pivot table
- has() defines the relationship if the model lacks one
- hasAttached() can only attach newly created models