[Repository] implement a negation operator
Hi :),
I would like to optimize the code below, especially the $users[$i - 1] part.
- The idea behind my optimization is to randomly get a user, excluding the user
$userTest. Perhaps adding a function likeexclude(['email' => '[email protected]'])by adding attributes would be good. - It's also important that when I get the users, they all have to be unique. I have a uniqueness index on the
visitor_idandvisited_idfields. Is this already implemented as a feature?
// src/Entity/Visit.php
#[ORM\UniqueConstraint(fields: ['visitor', 'visited'])]
// src/DataFixtures/AppFixtures.php
$users = UserFactory::createMany(100);
$userTest = UserFactory::createOne([
'email' => '[email protected]',
]);
VisitFactory::createMany(100, fn ($i) => [
'visitor' => $users[$i - 1],
'visited' => $userTest,
]);
VisitFactory::createMany(100, fn ($i) => [
'visitor' => $userTest,
'visited' => $users[$i - 1],
]);
Hi @seb-jean
there are two different topics here
about an exclude() method
I'm a bit torn about this: supporting a not operator (as well as a or operator) would be nice, but on the other hand, I don't see any easy way to do this: we're relying on doctrine/persistence for all Factory::findXXX() methods, and it does not allow this. And I don't think this feature is worth the complexity it would involve (I think that we don't want to rely on ORM query builder stuff in Foundry)
But I'm not reluctant to add this feature if a good idea comes up.
By the way, you can still use a custom method from your repo:
UserFactory::createMany(100);
$userNotKevin = UserFactory::repository() // gives you a `\Zenstruck\Foundry\Persistence\RepositoryDecorator`
->inner() // gives you the repository defined in `#[ORM\Entity(repository="..."]`
->randomButExclude('[email protected]'); // custom method in your app
about the "uniqueness"
This sounds even more challenging 🙃
Maybe a method PersistentObjectFactory::unique() (or PersistentObjectFactory::repository()->unique() ?) could be possible? but this would definitely require to support a not() operator 🤔 Moreover that random with doctrine and/or SQL is not a so simple problem (by the way, Foundry has a performance problem with random).
And, this would mean that in your case, you'd make 100 requests to the db, which maybe isn't what you want to do.
Still, as for the "not" stuff, if a good idea pops up, there is no problem to add the feature.
Note that in Foundry 2.4, thanks to the new distribute() method, you'll be able to do something like this:
$users = UserFactory::createMany(100);
$userTest = UserFactory::createOne([
'email' => '[email protected]',
]);
VisitFactory::new(['visited' => $userTest])
->many(100)
->distribute('visitor', $users)
->create();
VisitFactory::new(['visitor' => $userTest])
->many(100)
->distribute('visited', $users)
->create();
on more thing: you should also use flush_after helper, this would improve the performances of your queries.
ping @kbond for some insights
Thank you very much for your reply.
about the uniqueness, maybe something like this would be ok?
VisitFactory::createMany(100, [
'visitor' => $userTest,
'visited' => unique(fn() => UserFactory::createMany(100)),
]);
the unique() helper could take a callable (which it would call only once) or an array, and it would return only unique values. But TBH I don't see the real advantage compared to using distrubute() 🤔
Indeed, distribute() could be used to get unique users.
However, is it possible to use distribute() with createMany()?
not directly with createMany(), but this method exists (or actually, it WILL exist, not released yet) on FactoryCollection class:
// both of these lines are equivalent:
VisitFactory::createMany();
VisitFactory::new()->many()->create();
// and `->many()` returns a `FactoryCollection`, so you can call:
VisitFactory::new()->many()->distribute($var)->create();
Ok, thanks for the information!
I looked for several solutions and found a solution that is interesting:
// src/DataFixtures/AppFixtures.php
$users = UserFactory::createMany(100);
$userTest = UserFactory::createOne([
'email' => '[email protected]',
]);
VisitFactory::createSequence(
function () use ($users, $userTest) {
foreach ($users as $user) {
yield ['visitor' => $userTest, 'visited' => $user];
yield ['visitor' => $user, 'visited' => $userTest];
}
}
);
This allows me to no longer use the index system: $users[$i - 1] but also to bring the 2 createMany blocks together.
haha I did not thought about yielding twice in a sequence, that's clever!
I'm closing this, since I don't think we will ever implement a way to have a negatable operator, if doctrine does not provide it out of the box