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

automica's avatar

How to access passed in arguments in a Factory and determine if key is null or not present

Hi all, I have the following factory class

class PriceListFactory extends Factory
{
    /**
     * The name of the factory's corresponding model.
     *
     * @var string
     */
    protected $model = PriceList::class;

    /**
     * Define the model's default state.
     *
     * @return array
     */
    public function definition(): array
    {
        return [
            'version_id' => (Version::factory()->create())->id,
            'name' => $this->faker->text(255),
            'created_at' => now(),
            'updated_at' => now(),
        ];
    }
}

The model I am creating has an optional Version, and the version_id field is nullable.

What I want to do is the following:

if I pass In a version_id as int I want to set it as that int.

        $result = PriceList::factory()->create(['version_id' => 2]);

        $this->assertEquals([
            'id' => 1,
            'name' => 'foo',
            'version_id' => 2 // uses Existing Version
        ], $result->toArray());

if I dont pass in a version_id then I want it to create a new Version and get its id.

        $result = PriceList::factory()->create();

        $this->assertEquals([
            'id' => 1,
            'name' => 'foo',
            'version_id' => 1 // creates new Version
        ], $result->toArray());

if I pass in a version_id and it is null, I want to set version_id as null

 $result = PriceList::factory()->create(['version_id' => null]);
        
        $this->assertEquals([
            'id' => 1,
            'name' => 'foo',
            'version_id' => null // Creates no Version
        ], $result->toArray());

How would I go about modifying the factory to check for null and also for empty?

Thanks!

0 likes
4 replies
LaryAI's avatar
Level 58

To achieve the desired behavior in your factory, you can use the afterMaking and afterCreating state callbacks to check if a version_id has been passed and handle it accordingly. Here's how you can modify your PriceListFactory:

class PriceListFactory extends Factory
{
    /**
     * The name of the factory's corresponding model.
     *
     * @var string
     */
    protected $model = PriceList::class;

    /**
     * Define the model's default state.
     *
     * @return array
     */
    public function definition(): array
    {
        return [
            // Do not set 'version_id' here, handle it in the afterMaking callback
            'name' => $this->faker->text(255),
            'created_at' => now(),
            'updated_at' => now(),
        ];
    }

    /**
     * Configure the factory to handle version_id appropriately.
     *
     * @return $this
     */
    public function configure()
    {
        return $this->afterMaking(function (PriceList $priceList) {
            // If version_id is not set or is null, handle it accordingly
            if (!isset($priceList->version_id) || $priceList->version_id === null) {
                $priceList->version_id = $priceList->version_id ?? (Version::factory()->create())->id;
            }
            // If version_id is set, it will be used as is
        });
    }
}

With this setup, you can now create PriceList instances with the following scenarios:

  1. If you don't pass a version_id, it will create a new Version and use its id.
  2. If you pass a version_id as an integer, it will use that integer.
  3. If you pass a version_id as null, it will set version_id to null.

Here's how you can use the factory:

// Creates a new Version and uses its id
$result = PriceList::factory()->create();
$this->assertEquals(1, $result->version_id);

// Uses the existing Version with id 2
$result = PriceList::factory()->create(['version_id' => 2]);
$this->assertEquals(2, $result->version_id);

// Sets version_id to null
$result = PriceList::factory()->create(['version_id' => null]);
$this->assertNull($result->version_id);

This approach ensures that the version_id is handled correctly based on the input provided when creating a new PriceList instance.

automica's avatar

@LaryAI your approach didnt work as it couldn't determine difference between passed and null or not passed.

The following checks for key not being present.

    /**
     * Configure the factory to handle version_id appropriately.
     *
     * @return $this
     */
    public function configure(): PriceListFactory
    {
        return $this->afterMaking(function (PriceList $priceList) {

            if (!array_key_exists('version_id', $priceList->toArray())) {
                $priceList->version_id = (Version::where('is_default', true)->first() ?? Version::factory()->create())->id;
            }
        });
    }
1 like
Braunson's avatar

What you want is to look into states.

You'd define this in your PriceList factory:

use Illuminate\Database\Eloquent\Factories\Factory;
 
public function version($versionNum): Factory
{
    return $this->state(function (array $attributes) use ($versionNum) {
        return [
            'version_id' => Version::create(['version' => $versionNumber])->id,
        ];
    });
}

Then usage would look like this:

PriceList::factory()->version(1)->create();
automica's avatar

@Braunson thanks.

That looks a neat what to deal with setting the value. In my case I’d like to retain the standard way of passing arms via the create method to match the 20 or so factories we have in our application.

I think @laryai suggestion looks promising. ‘AfterMaking’ is a new one to me. Will give.it a try and report back.

Please or to participate in this conversation.