Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get only list of ID's from ManyToMany with Doctrine

I have a ManyToMany associated field, and I'm in a situation where I'm only trying to get all the ID's without hydrating any of the sub-entities in the field.

I understand there will be a query to aquire the entity references the moment I access the field, and that's fine/expected. But I need to loop through the ID's, but I don't quite know how to get them without doing

$ids = [];
foreach($mainEntity->getSubEntities() as $subentity) {
   $ids[] = $subentity->getId();
}

This also seems to hydrate the sub entity automatically, im assuming because of the foreach loop. This results in a lot of unnecessary queries and impacts page load time.

I have the subentity field marked with EXTRALAZY as well.

/**
 * @var ArrayCollection
 * @ORM\ManyToMany(targetEntity="User", fetch="EXTRA_LAZY")
 * @ORM\JoinTable(name="user_friends")
 */
protected $friends;
like image 384
RedactedProfile Avatar asked Jul 14 '15 07:07

RedactedProfile


2 Answers

As getKeys() returns only the sequential indexes of a PersistentCollection, I found this oneliner as a solution:

$myCollectionIds = $myCollection->map(function($obj){return $obj->getId();})->getValues();

Works smoothly in IN contexts.

like image 178
spackmat Avatar answered Nov 20 '22 10:11

spackmat


I don't believe it's possible to do what you with the entities defined the way they are. The Doctrine PersistentCollection class will always initialise itself, loading all the entities from the DB when you iterate or map over it. It does that before it runs the code in the foreach loop or closure, as you can see in the implementations of map and getIterator.

Even when the collection is marked as Extra Lazy the contents are all loaded from the DB as soon as you use any method other than contains, containsKey, count, get or slice.

However, if you don't mind adjusting your entity structure a little you can get the behaviour you want.

Reify the ManyToMany association into an entity.

Your relation is called friend, so you might make a friendship entity:

/** @Entity */
class Frendship
{
    /**
     * @var int
     * @Id @Column(type="integer") @GeneratedValue 
     **/
    protected $id;

    /**
     * @var User
     * @ORM\@ManyToOne(targetEntity="User", inversedBy="friendships")
     **/
    protected $friendA

    /**
     * @var User
     * @ORM\@ManyToOne(targetEntity="User", inversedBy="friendships")
     **/
    protected $friendB

    public function getOtherFriend(User $friend){
        if ($this->friendA === $friend)) {
            return $this->friendB; 
        } else if ($this->friendB === $friend)) {
            return $this->friendA
        } else {
            throw new \Exception("$friend is not part of this friendship");
        }
    }
}

Then you can replace your friends collection with a frendships collection and iterate through that. The friendships will be fully hydrated but that's OK because they only contain two integers.

You can get the other friend from each friendship, and it will be a proxy object, unless it happens to already be loaded. Since you only want to call getId() on it doctrine won't need to retrieve the full entity from the database.

like image 2
bdsl Avatar answered Nov 20 '22 09:11

bdsl