PersistentCollection:matching() does not initialize the collection #5432

Open
opened 2026-01-22 15:07:40 +01:00 by admin · 4 comments
Owner

Originally created by @solor2001 on GitHub (Feb 27, 2017).

I'm using OneToMany relation with fetch="LAZY".
Using matching(Criteria) with where() expression works on collection PK only. Other fields of collection are null, because the flag isDirty == false.

To get correct behavior I have to call initialize() first before using matching().

    /**
     * @ORM\OneToMany(
     *     targetEntity="MyBundle\Entity\Units",
     *      mappedBy="...",
     *      cascade={"persist"},
     *      fetch="LAZY"
     * )
     * @ORM\OrderBy({"createdAt" = "DESC"})
     * @var PersistentCollection
     */
    private $collection;

public function getCollectionItemsByKind(unitId, string $status = null)
    {
        // my fix
        $this->collection->initialize();

        $criteria = Criteria::create()
            // unitId is PK of Units entity, thus it's always present in collection
            ->where(Criteria::expr()->eq("unitId", $unitId))
            ->orderBy(['createdAt' => Criteria::ASC]);


        if (isset($status)) {
            // workd wrong without initialize()
            $criteria->andWhere(Criteria::expr()->eq('status', $status));
        }
        return $this->collection->matching($criteria)->toArray();
    }
Originally created by @solor2001 on GitHub (Feb 27, 2017). I'm using OneToMany relation with fetch="LAZY". Using matching(Criteria) with where() expression works on collection PK only. Other fields of collection are null, because the flag isDirty == false. To get correct behavior I have to call initialize() first before using matching(). ``` /** * @ORM\OneToMany( * targetEntity="MyBundle\Entity\Units", * mappedBy="...", * cascade={"persist"}, * fetch="LAZY" * ) * @ORM\OrderBy({"createdAt" = "DESC"}) * @var PersistentCollection */ private $collection; public function getCollectionItemsByKind(unitId, string $status = null) { // my fix $this->collection->initialize(); $criteria = Criteria::create() // unitId is PK of Units entity, thus it's always present in collection ->where(Criteria::expr()->eq("unitId", $unitId)) ->orderBy(['createdAt' => Criteria::ASC]); if (isset($status)) { // workd wrong without initialize() $criteria->andWhere(Criteria::expr()->eq('status', $status)); } return $this->collection->matching($criteria)->toArray(); } ```
admin added the BugMissing Tests labels 2026-01-22 15:07:40 +01:00
Author
Owner

@lcobucci commented on GitHub (Feb 27, 2017):

@solor2001 could you please reproduce this as a failing functional test (you can use e4704beaf9/tests/Doctrine/Tests/ORM/Functional/Ticket/GH5562Test.php as example).

@lcobucci commented on GitHub (Feb 27, 2017): @solor2001 could you please reproduce this as a failing functional test (you can use https://github.com/doctrine/doctrine2/blob/e4704beaf9fad5a521fe7592dec382ae209b3cc1/tests/Doctrine/Tests/ORM/Functional/Ticket/GH5562Test.php as example).
Author
Owner

@ihorsamusenko commented on GitHub (Jun 29, 2017):

@lcobucci although this is not a failing functional test but at least it explains the nature of the issue.

Consider the following case:
We have two rows in database, say orders with status unpaid.
We also have a user which owns those orders. User entity has the following method:

public function getOrders(string $status): array
{
   $criteria = Criteria::create()->where(Criteria::expr()->eq('status', $status));
   return $this->orders->matching(criteria)->toArray();
}

In some part of the app we retrieve one of those orders from entityManager and set the status to paid. Now, we have two orders with status unpaid in db and we have one paid and one unpaid in memory.

If the $user->orders is instance of AbstractLazyCollection and has not been initialized yet, then calling $user->getOrders('unpaid') will return two orders (including the one which has paid status in memory but unpaid in db)

I can think of two workarounds:

  1. pull the whole collection into memory calling $this->orders->initialize() or $this->orders->toArray() so the method becomes like the following:
{
   $this->orders->toArray();
   $criteria = Criteria::create()->where(Criteria::expr()->eq('status', $status));
   return $this->orders->matching(criteria)->toArray();
}

Which can be something not affordable (in case the collection is too big)

  1. call $em->flush() before calling $user->getOrders(). Which is something I'd like to avoid.

Are there others?

@ihorsamusenko commented on GitHub (Jun 29, 2017): @lcobucci although this is not a failing functional test but at least it explains the nature of the issue. Consider the following case: We have two rows in database, say orders with status `unpaid`. We also have a user which owns those orders. User entity has the following method: ``` public function getOrders(string $status): array { $criteria = Criteria::create()->where(Criteria::expr()->eq('status', $status)); return $this->orders->matching(criteria)->toArray(); } ``` In some part of the app we retrieve one of those orders from `entityManager` and set the status to `paid`. Now, we have two orders with status `unpaid` in db and we have one `paid` and one `unpaid` in memory. If the `$user->orders` is instance of AbstractLazyCollection and has not been initialized yet, then calling `$user->getOrders('unpaid')` will return two orders (including the one which has `paid` status in memory but `unpaid` in db) I can think of two workarounds: 1. pull the whole collection into memory calling `$this->orders->initialize()` or `$this->orders->toArray()` so the method becomes like the following: ``` { $this->orders->toArray(); $criteria = Criteria::create()->where(Criteria::expr()->eq('status', $status)); return $this->orders->matching(criteria)->toArray(); } ``` Which can be something not affordable (in case the collection is too big) 2. call `$em->flush() ` before calling `$user->getOrders()`. Which is something I'd like to avoid. Are there others?
Author
Owner

@lcobucci commented on GitHub (Jul 23, 2017):

@samusenkoiv to be honest I wouldn't have a bidirectional association on your example, a repository (or even a query) seems much more appropriated IMO.

We do need a failing test case to understand it better and fix (if we agree it's something to be fixed).

@lcobucci commented on GitHub (Jul 23, 2017): @samusenkoiv to be honest I wouldn't have a bidirectional association on your example, a repository (or even a query) seems much more appropriated IMO. We do need a failing test case to understand it better and fix (if we agree it's something to be fixed).
Author
Owner

@anokhinAleksey commented on GitHub (Jul 18, 2018):

I have the same issue whith Persistent Collection. In my case there are two entities User and UserRole with ManyToMany association:

class User extends Entity\AbstractEntity 
{
...
  /**
  * @var Collections\Collection
  *
  * @ORM\ManyToMany(targetEntity="UserRole", inversedBy="user")
  * @ORM\JoinTable(name="user_role_ref",
  *   joinColumns={
  *     @ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")
  *   },
  *   inverseJoinColumns={
  *     @ORM\JoinColumn(name="role_id", referencedColumnName="id", onDelete="CASCADE")
  *   }
  * )
  */
  protected $roles;
...
}

I need to find out if user has role matching some array of roles:

...
$roles = $this->getRoles();

$topLevelRolesCriteria = new Collections\Criteria(Collections\Criteria::expr()->in('id', UserRole::TOP_LEVEL_ROLES));
$topLevelRolesCollection = $roles->matching($topLevelRolesCriteria);
...

If collection of roles is not initialized at the moment when I call matching(), I`ll get an Doctrine\DBAL\Exception\SyntaxErrorException while executing sql query. So I have to initialize collection explicitly, like this:

...
$roles = $this->getRoles();

if (!$roles->isInitialized()) {
  $roles->initialize();
}

$topLevelRolesCriteria = new Collections\Criteria(Collections\Criteria::expr()->in('id', UserRole::TOP_LEVEL_ROLES));
$topLevelRolesCollection = $roles->matching($topLevelRolesCriteria);
...
@anokhinAleksey commented on GitHub (Jul 18, 2018): I have the same issue whith Persistent Collection. In my case there are two entities User and UserRole with ManyToMany association: ```php class User extends Entity\AbstractEntity { ... /** * @var Collections\Collection * * @ORM\ManyToMany(targetEntity="UserRole", inversedBy="user") * @ORM\JoinTable(name="user_role_ref", * joinColumns={ * @ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE") * }, * inverseJoinColumns={ * @ORM\JoinColumn(name="role_id", referencedColumnName="id", onDelete="CASCADE") * } * ) */ protected $roles; ... } ``` I need to find out if user has role matching some array of roles: ```php ... $roles = $this->getRoles(); $topLevelRolesCriteria = new Collections\Criteria(Collections\Criteria::expr()->in('id', UserRole::TOP_LEVEL_ROLES)); $topLevelRolesCollection = $roles->matching($topLevelRolesCriteria); ... ``` If collection of roles is not initialized at the moment when I call matching(), I`ll get an Doctrine\DBAL\Exception\SyntaxErrorException while executing sql query. So I have to initialize collection explicitly, like this: ```php ... $roles = $this->getRoles(); if (!$roles->isInitialized()) { $roles->initialize(); } $topLevelRolesCriteria = new Collections\Criteria(Collections\Criteria::expr()->in('id', UserRole::TOP_LEVEL_ROLES)); $topLevelRolesCollection = $roles->matching($topLevelRolesCriteria); ... ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#5432