Reputation: 2808
I'm building a system that has an update wall, where you only see Friends posts. A "Friend" here is classified by a set of two users who have each added eachother to their friends list.
The friend association is handled very simply by a Doctrine ManyToMany
association, like this:
User.php
//.. other declarations
/**
* @var ArrayCollection
* @ORM\ManyToMany(targetEntity="User")
* @ORM\JoinTable(name="user_friends")
*/
protected $friends;
//.. more unrelated stuff
and in my query to get a users friends updates (including themselves)
UpdateRepository.php
/**
* Get aggregated list of updates for a user and their friends in chronological order
* @param User $user
* @return array
*/
public function getWallUpdates(User $user)
{
$qb = $this->getEntityManager()->createQueryBuilder('p');
$qb->select(['p'])->from($this->getEntityName(), 'p');
$criteria = $qb->expr()->andX();
// Filter based on Friends
$friends = [$user];
foreach($user->getFriends() as $friend) // collect eligible friends
if($friend->getFriends()->contains($user))
$friends[] = $friend;
$criteria->add('p.account IN (:friends)');
$qb->setParameter('friends', $friends);
// Filter based on Chronology
// Filter based on Privacy
$criteria->add('p.privacy IN (:privacy)');
$qb->setParameter('privacy', [1, 3]);
$qb->where($criteria);
$query = $qb->getQuery();
$result = $query->getResult();
return $result;
}
Now this works but the problem is that the line if($friend->getFriends()->contains($user))
while necessary to know if $user
exists in another users friends list, generates a new query for every single person in $user
s friends list.
I don't mind an extra query or two to accomplish this, but I need to talk about scale here. Potentially a person on this application could have hundreds of friends. I can't have hundreds of queries (tiny as they may be) executing each time this page loads, which could potentially be a lot.
What's the best possible solution for making this better?
For now I can continue development, as it does function correctly.
EDIT
for sake of brevity, I did in fact try going the EXTRA_LAZY
route of adding EXTRA_LAZY
to $friends ManyToMany declaration in my User entity. According to the documentation, if using the EXTRA_LAZY
fetch mode, using Collection#contains(entity)
wouldn't trigger a query.
CLEARLY the problem here stems from the fact that $user
s friends each need to have their friends list collected in order to check. And that makes sense me.
Still is there a way to bring this all into a single operation?
Upvotes: 3
Views: 798
Reputation: 556
If you add joins for a user's friends and their friends when you fetch the $user object then it should cut out any lazy loading. So for example you could have a custom repository for the user as follows:
use Doctrine\ORM\EntityRepository;
class UserRepository extends EntityRepository {
public function fetchFriendsOfFriends($userId) {
return $this->_em->createQuery('SELECT u, f, ff FROM User u '
. 'LEFT JOIN u.friends f '
. 'LEFT JOIN f.friends ff '
. 'WHERE u.id = :userId')
->setParameter('userId', $userId)
->getSingleResult();
}
}
The get your user entity to use this repository:
/**
* @ORM\Entity(repositoryClass="UserRepository")
*/
class User {
...
Then when you fetch your $user use the new fetch method:
$user = $em->getRepository('User')->fetchFriendsOfFriends($userId);
I've not tested it out but I would assume it would cut out the need for lazy loading.
Upvotes: 1