doctrine / orm

Doctrine Object Relational Mapper (ORM)
https://www.doctrine-project.org/projects/orm.html
MIT License
9.87k stars 2.5k forks source link

'contains' returns false even the element is contained #6127

Open enekochan opened 7 years ago

enekochan commented 7 years ago

I have 2 entities, Service and Resource, with a ManyToMany relation between them. Service has the usual getResources() method and Resource the getServices() method. The Service class also has a getEnabledResources method that uses Criteria to get all Resource objects that have a status attribute to a certain value (enabled). I just wrote a repository method findEnabledResources for Service that achieves the same result as the getEnabledResources method with Criteria. My intention is to remove the use of Criteria in the app.

There's a point I store all the enabled resources for a service calling the repository method findEnabledResources and then check with contains if each one of those resources are contained in a Service object with something like this:

$enabledResources = $serviceRespository->findEnabledResources($service);
...
foreach ($enabledResources as $resource) {
    if (!$service->getResources()->contains($resource)) {
        //
    }
}

I know for sure that a resource object is contained (I can see it while debugging) but contains still returns false, thus entering in the if block.

If I use getEnabledResources (with Criteria) the contains method works just fine!

$enabledResources = $service->getEnabledResources();
...
foreach ($enabledResources as $resource) {
    if (!$service->getResources()->contains($resource)) {
        //
    }
}

What I've been able to see while debugging is that $service->getResources() returns a Doctrine\ORM\PersistentCollection with the collection having proxie objects with class Proxies\__CG__\AppBundle\Entity\Resource. In findEnabledResources I'm returning an ArrayCollection of actual AppBundle\Entity\Resource objects. I've tried using EAGER fetch but still get the same problem.

I'm using doctrine/orm and doctrine/dbal on dev-master".

Resumed entity classes and repository:

class Service
{
    ...

    /**
     * @ORM\ManyToMany(targetEntity="\AppBundle\Entity\Resource", inversedBy="services", cascade={"persist","remove"})
     * @ORM\JoinTable(name="resources_services")
     */
    protected $resources;

    public function getResources()
    {
        return $this->resources;
    }

    public function getEnabledResources($onlyVisible = true)
    {
        $criteria = Criteria::create()
            ->where(Criteria::expr()->eq('status', Resource::STATUS_ENABLED))
            ->orderBy(array('name' => Criteria::ASC))
        ;

        if ($onlyVisible) {
            $criteria->andWhere(Criteria::expr()->eq('visible', true));
        }

        return $this->getResources()->matching($criteria);
    }

    ...
}
class Resource
{
    ...

    /**
     * @ORM\ManyToMany(targetEntity="\AppBundle\Entity\Service", mappedBy="resources", cascade={"persist","remove"})
     */
    protected $services;

    public function getServices()
    {
        return $this->services;
    }

    ...
}
class ServiceRepository extends EntityRepository
{
    public function findEnabledResources(Service $service, $onlyVisible = true)
    {
        $qb = $this->getEntityManager()->createQueryBuilder()
            ->select('r')
            ->from('AppBundle:Resource', 'r')
            ->innerJoin('r.services', 's', 'WITH', 's.id = :service')
            ->andWhere('r.status = :status')
            ->setParameter('service', $service)
            ->setParameter('status', Resource::STATUS_ENABLED)
            ->orderBy('r.name', 'ASC');

        if ($onlyVisible) {
            $qb
                ->andWhere('r.visible = :visible')
                ->setParameter('visible', true);
        }

        return new ArrayCollection($qb->getQuery()->getResult());
    }
}
mpdude commented 6 years ago

So, your Collection contains Proxies, but you're passing a real entity into the contains() method?

Ocramius commented 6 years ago

The bug here is possibly that the private properties are not lazily loaded. Can you try changing the properties to public, re-generating the proxies and re-running this?

Also, I think this is fixed in master (3.x)

TheCelavi commented 6 years ago

I do not know if this helps, gives additional clues, or it is totally unrelated, we just had similar issue, everything worked fine, until Symfony upgrade from 3.4.2 to 3.4.11, and s*** hits the fan.

https://github.com/FriendsOfSymfony/FOSMessageBundle/issues/319#issuecomment-394083941

Downgrade fixed issue. I will probably investigate which symfony 3.4.x version broke a thing, if I get anything useful, will share....

kyeno commented 5 years ago

I've just hit the very same issue on symfony 3.4.23 and doctrine/orm 2.6.3

SenseException commented 5 years ago

Please try to recreate this behaviour on Doctrine ORM only without Symfony in a test.

quazardous commented 5 years ago

Hi same here with SF v4.2.4 + doctrine/orm 2.6.3

EDIT Found what broke it down !!!

services:
...
    App\EventSubscriber\EntitiesLifecycleSubscriber:
        tags: [doctrine.event_subscriber]

<?php
// src/EventSubscriber/EntitiesLifecycleSubscriber.php
namespace App\EventSubscriber;

use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;
use App\Entity\Game;
use Doctrine\ORM\Events;
use App\Service\XXX;

class EntitiesLifecycleSubscriber implements EventSubscriber
{
    protected $XXX;
    public function __construct(XXX $XXX)
    {
        $this->XXX = $XXX;
    }

    public function getSubscribedEvents()
    {
        return [
            Events::prePersist,
            Events::preUpdate,
        ];
    }

    public function preUpdate(LifecycleEventArgs $args)
    {
        $entity = $args->getObject();
        if ($entity instanceof Game) {
            $this->setGameIssue($entity);
        }
    }

    public function prePersist(LifecycleEventArgs $args)
    {
        $entity = $args->getObject();
        if ($entity instanceof Game) {
            $this->setGameIssue($entity);
        }
    }

    protected function setGameIssue(Game $game)
    {
        if ($game->getPublished() && is_null($game->getIssue())) {
            $game->setIssue($this->XXX->getNextIssue());
        }
    }
}

but my problem is on User / Group ManyToMany relation so it's some kind of side effect of plugin a event_subscriber...

EDIT2:

using doctrine entity listener is a NO GO too

https://symfony.com/doc/master/bundles/DoctrineBundle/entity-listeners.html

yellow1912 commented 5 years ago

I think I'm running into this issue or at least something very similar. I'm on Doctrine ORM 2.6.3 and running into this very weird case where an entity object is queried via the repository just 1 line above, then if I use the objectManager to check for "contains" it returns false.

Edit: actually my issue is something different, I have noticed it just now. Going to delete this message.

AntoniusGolly commented 5 years ago

@yellow1912 can you tell me anyways? I just need a hint. I have the exact same issue. I can see the entity in the debugger just fine, but still contains() returns false. Maybe your solution gives me a hint to investigate...

lcobucci commented 4 years ago

It would be extremely helpful if someone could provide a PR with a failing functional that reproduces this behaviour. Any volunteer?

hubertnnn commented 4 years ago

I am not sure if its the same problem, but I got here trying to figure out similar issue. What I found is that if you use a service inside doctrine eventSubscriber then the service will receive a broken EntityManager.

Here is some code:

class DoctrineSubscriber implements EventSubscriber
{
    /** @var CategoryService */
    protected $categoryService;

    /** @required */
    public function setCategoryService(CategoryService $categoryService)
    {
        $this->categoryService = $categoryService;
    }

    public function getSubscribedEvents()
    {
        return [
            // Irrelevant
        ];
    }
}

class CategoryService
{
    /** @var EntityManagerInterface */
    protected $doctrine;

    /** @required */
    public function setDoctrineService(EntityManagerInterface $doctrine)
    {
        $this->doctrine = $doctrine;
    }

    public function foo()
    {
        $categories = $this->doctrine->getRepository(Category::class)->findAll();
        foreach ($categories as $category) {
            dump($this->doctrine->contains($category));
        }
    }
}

Calling $categoryService->foo() will dump false in this case, but if I remove the EventSubscriber (or even remove the dependency on the service) it will show true instead.

SenseException commented 4 years ago

Please try to recreate this behaviour on Doctrine ORM only without Symfony in a test.

RafaelKr commented 1 week ago

I had a similar problem and want to leave this here just in case someone stumbles upon this.

In my Symfony project I manually created a Proxy Object by using $this->entityManager->getProxyFactory()->getProxy($entityClass, $id). But this doesn't attach the object to the EntityManager. After some debugging I found out I need to use $this->entityManager->getReference($entityClass, $id) instead.