Criteria in Many-To-Many relations broken if not manually accessing data before or EAGER used #5127

Closed
opened 2026-01-22 14:59:06 +01:00 by admin · 1 comment
Owner

Originally created by @enekochan on GitHub (May 16, 2016).

I updated doctrine/orm and doctrine/dbal to dev-master some time ago to be able to use Criteria in Many-To-Many relations (with https://github.com/doctrine/doctrine2/pull/885). Suddenly today (now I'm not sure if it ever worked but I would say it did when using the same function in other places) I wasn't getting any objects in the collections of any of the sides. The only way it worked was If I added a bogus loop before the function with Criteria code was called (I suppose because the objects were being loaded beforehand and then they were available) or if I added fetch="EAGER" in the ManyToMany relation.

I'll try to resume my code bellow as best as I can. The function with the Criteria is getActiveServices inside the Resource class. It's used to get the Services from the Resource that are available depending on their start/end date (both can have a value or be null at the same time: no dates, only start date, only end date or both dates specified).

The action in the controller:

    public function servicesLoadAction(Request $request, Business $business)
    {
        $params = $request->getMethod() === 'POST' ? $request->request->all() : $request->query->all();
        $resource = array_key_exists('resource', $params) ? $params['resource'] : null;

        if (!is_null($resource)) {
            $resource = $this->getDoctrine()->getRepository('AppBundle:Resource')->find($resource);
        }
/*
        // The bogus loop that make this work without using fetch="EAGER"
        foreach ($business->getResources() as $resource) {
            $i = $resource->getServices()->count();
        }
*/
        $scheduleManager = $this->get('app.model.schedule');
        if (!is_null($resource)) {
            $services = $scheduleManager->getAvailableResourceServices($business, $resource);
        } else {
            $services = $scheduleManager->getAvailableServices($business);
        }

        return new JsonResponse($this->json_decode_group($services, 'service_minimum'));
    }

The model:

    public function getAvailableResourceServices(Business $business, \AppBundle\Entity\Resource $resource)
    {
        if ($business->getEnabledHumanResources()->contains($resource)) {
            return $resource->getActiveServices(true); // THIS IS THE FUNCTION WITH Criteria
        }

        return array();
    }

Resource class:

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


    /**
     * Add service
     *
     * @param \AppBundle\Entity\Service $service
     * @return Resource
     */
    public function addService(Service $service)
    {
        $service->addResource($this);
        if (!$this->services->contains($service)) {
            $this->services[] = $service;
        }

        return $this;
    }

    /**
     * Remove service
     *
     * @param \AppBundle\Entity\Service $service
     */
    public function removeService(Service $service)
    {
        $service->getResources()->removeElement($this);
        $this->services->removeElement($service);
    }

    /**
     * Get services
     *
     * @return \Doctrine\Common\Collections\ArrayCollection
     */
    public function getServices()
    {
        return $this->services;
    }

    /**
     * Get active services
     *
     * @param boolean $onlyVisible
     * @return \Doctrine\Common\Collections\ArrayCollection
     */
    public function getActiveServices($onlyVisible = true)
    {
        $now = new DateTime();

        $criteria = Criteria::create()
            ->where(Criteria::expr()->eq('status', Service::STATUS_ENABLED))
            ->andWhere(Criteria::expr()->orX(
                Criteria::expr()->andX(
                    Criteria::expr()->isNull('dateStart'),
                    Criteria::expr()->isNull('dateEnd')
                ),
                Criteria::expr()->andX(
                    Criteria::expr()->lt('dateStart', $now),
                    Criteria::expr()->isNull('dateEnd')
                ),
                Criteria::expr()->andX(
                    Criteria::expr()->isNull('dateStart'),
                    Criteria::expr()->gt('dateEnd', $now)
                ),
                Criteria::expr()->andX(
                    Criteria::expr()->lt('dateStart', $now),
                    Criteria::expr()->gt('dateEnd', $now)
                )
            ))
        ;

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

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

    /**
     * Set services
     *
     * @param array
     * @return Resource
     */
    public function setServices($services)
    {
        $this->services = new ArrayCollection();

        if (!is_null($services)) {
            /** @var \AppBundle\Entity\Service $service */
            foreach ($services as $service) {
                $this->addService($service);
            }
        }

        return $this;
    }
}

Service class:

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

    /**
     * Add resource
     *
     * @param \AppBundle\Entity\Resource $resource
     * @return Service
     */
    public function addResource(\AppBundle\Entity\Resource $resource)
    {
        $resource->addService($this);
        if (!$this->resources->contains($resource)) {
            $this->resources[] = $resource;
        }

        return $this;
    }

    /**
     * Remove resource
     *
     * @param \AppBundle\Entity\Resource $resource
     */
    public function removeResource(\AppBundle\Entity\Resource $resource)
    {
        $resource->getServices()->removeElement($this);
        $this->resources->removeElement($resource);
    }

    /**
     * Get resources
     *
     * @return \Doctrine\Common\Collections\ArrayCollection
     */
    public function getResources()
    {
        return $this->resources;
    }

    /**
     * Get enabled human resources
     *
     * @param boolean $onlyVisible
     * @return \Doctrine\Common\Collections\ArrayCollection
     */
    public function getEnabledHumanResources($onlyVisible = true)
    {
        $criteria = Criteria::create()
            ->where(Criteria::expr()->eq('status', Resource::STATUS_ENABLED))
            ->andWhere(Criteria::expr()->eq('type', Resource::TYPE_HUMAN))
            ->orderBy(array('name' => Criteria::ASC))
        ;

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

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

    /**
     * Get enabled resources
     *
     * @param boolean $onlyVisible
     * @return \Doctrine\Common\Collections\ArrayCollection
     */
    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);
    }

    /**
     * Set resources
     *
     * @param array
     * @return Service
     */
    public function setResources($resources)
    {
        $this->resources = new ArrayCollection();

        if (!is_null($resources)) {
            /** @var \AppBundle\Entity\Resource $resource */
            foreach ($resources as $resource) {
                $resource->addService($this);
                $this->addResource($resource);
            }
        }

        return $this;
    }
}
Originally created by @enekochan on GitHub (May 16, 2016). I updated `doctrine/orm` and `doctrine/dbal` to `dev-master` some time ago to be able to use Criteria in Many-To-Many relations (with https://github.com/doctrine/doctrine2/pull/885). Suddenly today (now I'm not sure if it ever worked but I would say it did when using the same function in other places) I wasn't getting any objects in the collections of any of the sides. The only way it worked was If I added a bogus loop before the function with Criteria code was called (I suppose because the objects were being loaded beforehand and then they were available) or if I added `fetch="EAGER"` in the ManyToMany relation. I'll try to resume my code bellow as best as I can. The function with the Criteria is `getActiveServices` inside the `Resource` class. It's used to get the Services from the Resource that are available depending on their start/end date (both can have a value or be null at the same time: no dates, only start date, only end date or both dates specified). The action in the controller: ``` public function servicesLoadAction(Request $request, Business $business) { $params = $request->getMethod() === 'POST' ? $request->request->all() : $request->query->all(); $resource = array_key_exists('resource', $params) ? $params['resource'] : null; if (!is_null($resource)) { $resource = $this->getDoctrine()->getRepository('AppBundle:Resource')->find($resource); } /* // The bogus loop that make this work without using fetch="EAGER" foreach ($business->getResources() as $resource) { $i = $resource->getServices()->count(); } */ $scheduleManager = $this->get('app.model.schedule'); if (!is_null($resource)) { $services = $scheduleManager->getAvailableResourceServices($business, $resource); } else { $services = $scheduleManager->getAvailableServices($business); } return new JsonResponse($this->json_decode_group($services, 'service_minimum')); } ``` The model: ``` public function getAvailableResourceServices(Business $business, \AppBundle\Entity\Resource $resource) { if ($business->getEnabledHumanResources()->contains($resource)) { return $resource->getActiveServices(true); // THIS IS THE FUNCTION WITH Criteria } return array(); } ``` Resource class: ``` /** * @ORM\Entity(repositoryClass="AppBundle\Entity\ResourceRepository") * @ORM\Table(name="resources") */ class Resource { /** * @ORM\ManyToMany(targetEntity="\AppBundle\Entity\Service", mappedBy="resources", cascade={"persist","remove"}) */ protected $services; /** * Add service * * @param \AppBundle\Entity\Service $service * @return Resource */ public function addService(Service $service) { $service->addResource($this); if (!$this->services->contains($service)) { $this->services[] = $service; } return $this; } /** * Remove service * * @param \AppBundle\Entity\Service $service */ public function removeService(Service $service) { $service->getResources()->removeElement($this); $this->services->removeElement($service); } /** * Get services * * @return \Doctrine\Common\Collections\ArrayCollection */ public function getServices() { return $this->services; } /** * Get active services * * @param boolean $onlyVisible * @return \Doctrine\Common\Collections\ArrayCollection */ public function getActiveServices($onlyVisible = true) { $now = new DateTime(); $criteria = Criteria::create() ->where(Criteria::expr()->eq('status', Service::STATUS_ENABLED)) ->andWhere(Criteria::expr()->orX( Criteria::expr()->andX( Criteria::expr()->isNull('dateStart'), Criteria::expr()->isNull('dateEnd') ), Criteria::expr()->andX( Criteria::expr()->lt('dateStart', $now), Criteria::expr()->isNull('dateEnd') ), Criteria::expr()->andX( Criteria::expr()->isNull('dateStart'), Criteria::expr()->gt('dateEnd', $now) ), Criteria::expr()->andX( Criteria::expr()->lt('dateStart', $now), Criteria::expr()->gt('dateEnd', $now) ) )) ; if ($onlyVisible) { $criteria->andWhere(Criteria::expr()->eq('visible', true)); } return $this->getServices()->matching($criteria); } /** * Set services * * @param array * @return Resource */ public function setServices($services) { $this->services = new ArrayCollection(); if (!is_null($services)) { /** @var \AppBundle\Entity\Service $service */ foreach ($services as $service) { $this->addService($service); } } return $this; } } ``` Service class: ``` /** * @ORM\Entity(repositoryClass="AppBundle\Entity\ServiceRepository") * @ORM\Table(name="services") */ class Service { /** * @ORM\ManyToMany(targetEntity="\AppBundle\Entity\Resource", inversedBy="services", cascade={"persist","remove"}) * @ORM\JoinTable(name="resources_services") */ protected $resources; /** * Add resource * * @param \AppBundle\Entity\Resource $resource * @return Service */ public function addResource(\AppBundle\Entity\Resource $resource) { $resource->addService($this); if (!$this->resources->contains($resource)) { $this->resources[] = $resource; } return $this; } /** * Remove resource * * @param \AppBundle\Entity\Resource $resource */ public function removeResource(\AppBundle\Entity\Resource $resource) { $resource->getServices()->removeElement($this); $this->resources->removeElement($resource); } /** * Get resources * * @return \Doctrine\Common\Collections\ArrayCollection */ public function getResources() { return $this->resources; } /** * Get enabled human resources * * @param boolean $onlyVisible * @return \Doctrine\Common\Collections\ArrayCollection */ public function getEnabledHumanResources($onlyVisible = true) { $criteria = Criteria::create() ->where(Criteria::expr()->eq('status', Resource::STATUS_ENABLED)) ->andWhere(Criteria::expr()->eq('type', Resource::TYPE_HUMAN)) ->orderBy(array('name' => Criteria::ASC)) ; if ($onlyVisible) { $criteria->andWhere(Criteria::expr()->eq('visible', true)); } return $this->getResources()->matching($criteria); } /** * Get enabled resources * * @param boolean $onlyVisible * @return \Doctrine\Common\Collections\ArrayCollection */ 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); } /** * Set resources * * @param array * @return Service */ public function setResources($resources) { $this->resources = new ArrayCollection(); if (!is_null($resources)) { /** @var \AppBundle\Entity\Resource $resource */ foreach ($resources as $resource) { $resource->addService($this); $this->addResource($resource); } } return $this; } } ```
admin closed this issue 2026-01-22 14:59:07 +01:00
Author
Owner

@jaikdean commented on GitHub (May 17, 2016):

Criteria is broken in so many ways, I'm not sure why it's included in stable releases, it's totally unfinished.

@jaikdean commented on GitHub (May 17, 2016): `Criteria` is broken in so many ways, I'm not sure why it's included in stable releases, it's totally unfinished.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#5127