Chainable fixture factory scenarios?

I use the Fixture Factories all the time. A lot of my data is quite hierarchically nested, with hasMany relations, and I need to make deeply nested collections of records, like an A with 6 B’s, each of which have 1 or 2 C’s, which all have a D. Trying to set all of that up with arrays of data gets tedious, especially when most of the C data is replicated for a given test, but different for different tests, so default data isn’t the solution.

I have created a number of scenarios that help, with parameters passed to tell them how do to all of this. I am finding some situations where they get quite complicated, and it feels that this structure falls a bit short from a design and usability perspective.

I’ve been thinking about creating a scenario that returns an object (maybe the scenario, maybe some other test-specific service class) with chainable methods that could add the deep-level data, sort of mini-scenarios if you will. Anybody done something like this and have pointers of how to proceed, or pitfalls to avoid? Am I over-engineering this, and should just make the mini-scenarios directly instead of hiding them away behind interfaces? Any other thoughts, apart from “simplify your data”? :wink:

Hi. Could you write an example with ABCD fake data of the chaining syntax you imagine please ?

I haven’t really gotten as far as planning specific syntax, but if the data were league → games → score entries → allstars (where a league has lots of games, each game has a score entry from each team, and each score entry might optionally include an allstar nomination), then something very vaguely along the lines of

$league = $this->loadFixtureScenario(LeagueScheduleScenario::class, [
    // league details might go here, e.g. start and end dates
])
    ->addGame($team1, $team2, $date1, [ // details of scores and allstars, somehow ])
    ->addGame($team3, $team4, $date1, [ ... ])
    ->addGame($team5, $team6, $date1, [ ... ])
    ->addGame($team1, $team3, $date2, [ ... ])
    ->addGame($team2, $team6, $date2, [ ... ])
    ->addGame($team3, $team5, $date2, [ ... ])
    ->persist(); // or ->getEntity()

So, the LeagueScheduleScenario::load function would return an instance of itself, and that class would include an addGame function. Internally, it would be adding to a factory, and eventually a call to persist and getEntity functions in the scenario would simply forward those calls to the factory methods of the same names, returning the result. There could be other functions than addGame, to help with other common operations. addPlayoffGame, for example, or addBlankGame for creating games that have no teams assigned yet.

The main thing I don’t like about this is that it’s not deeply chainable; I can’t call addGame and then methods on what that returns to set the specifics of the game, e.g. addScoreEntry. The class could “remember” what the last game added was, and provide such functions that operate on the last game, but this quickly gets out of control as the nesting gets deeper; it would also need to remember the last score entry for adding allstars, and presumably clear that when another game is added, and so on.

Regular fixture factories do something vaguely like this by taking another factory as an argument, so you can do things like:

$league = LeagueFactory::make([...])
	->with('Games', GameFactory::make([...])->with('ScoreEntries', [...]))

which is great to a point, but doesn’t (to my knowledge) have a good counterpart when using scenarios, which I guess is what I’m looking for.

Isnt that normal “normalized” DB schema, so tables and relations. no need for Tree or anything else. Just a schema of x hasMany y etc, and then calculate based on the data on the hasMany.
Dont see whats the problem here?

But also, please do NOT hijack a totally different topic here, this must be its own topic.
So best to not answer and go back to thinking about it more, and if needed open up a a dedicated topic.