foundry
foundry copied to clipboard
Attribute "Lazy Values" instead of callables
Related: #244, #373.
Currently, if you want an attribute calculated for every object created by the factory you need to wrap the attribute array in a closure:
UserFactory::new(fn() => ['name' => $this->generateRandomName()]);
// instead of
UserFactory::new(['name' => $this->generateRandomName()]); // only ever calls generateRandomName once
I propose we change attributes to always be an array. To create lazy values, wrap the value in a new LazyValue object (with a helper function):
UserFactory::new(['name' => lazy(fn() => $this->generateRandomName())]);
The primary use-case for having attributes as callables is for faker. We'll need faker to use this lazy system. I'm thinking a new Fake object that's a mixin for Faker\Generator. All method calls wrap the actual method in a LazyValue:
UserFactory::new(['name' => $this->fake()->name())]); // equivalent to lazy(fn() => self::faker()->name())
// or can use a helper function
UserFactory::new(['name' => fake()->name())]); // equivalent to lazy(fn() => faker()->name())
The secondary use-case is to pass the index to the attribute callable during a createMany():
PostFactory::createMany(
5,
fn(int $i) => ['title' => "Title $i"]; // "Title 1", "Title 2", ... "Title 5"
);
This has the following benefits:
- Allows
ModelFactory::getDefaults()to have lazy values (#244) - Makes your code more explicit (lazy keyword).
- Help make using faker with data providers easier
- (long term) Helps with maintenance - attributes will only be arrays.
Todo:
- Add/implement
LazyValueconcept #427 - Add
Fakefaker mixin - Deprecate using a callable as attributes
- Deprecate
ModelFactory::faker()andfaker()function
UserFactory::new(['name' => $this->generateRandomName()]); // only ever calls generateRandomName once
I have been caught out by this more than once. When I forget that it only calls generateRandomName() a single time and I wonder why all my created users have the same name :sweat_smile:
One question I have is how this:
UserFactory::new(['name' => lazy(fn() => $this->generateRandomName())]);
differ to this:
UserFactory::new(['name' => fn() => $this->generateRandomName()]);
It's valid that a property could be/accept a closure. The lazy() wrapper ensures this is still possible.
Ah yes, that makes a lot of sense. Thanks for the explanation!
lazy value has been implemented since then!