Search code examples
doctrine-ormmany-to-onearraycollectionfindby

Doctrine2 findby on a Many-to-One mapping


I have two entities with a Unidirectional Many-to-One mapping.

Here's Product:

use Doctrine\Common\Collections\ArrayCollection;

/**
 * @Entity
 * @Table(name="Product")
 * @gedmo:TranslationEntity(class="GPos_Model_Translation_ProductTranslation")
 */
class GPos_Model_Product extends GPos_Doctrine_ActiveEntity {
    /**
     * @Id @Column(type="integer")
     * @GeneratedValue
     */
    protected $id;

    /**
     * @ManyToMany(targetEntity="GPos_Model_Category")
     * @JoinTable(name="products_categories",
     *      joinColumns={@JoinColumn(name="product_id", referencedColumnName="id")},
     *      inverseJoinColumns={@JoinColumn(name="category_id", referencedColumnName="id")}
     *      )
     */
    protected $categories;

    public function __construct() {
        $this->categories = new ArrayCollection();
    }

    public function addCategory(GPos_Model_Category $category) {
        if (!$this->categories->contains($category))
            $this->categories->add($category);
    }
}

As you can see, $categories is an ArrayCollection of GPos_Model_Category entities.

Now what? Well now I'd like to retrive all products that are in a given category and also all products that are NOT in a given category.

I've tried $products = GPos_Model_Product::findByCategories($category->getId()); but that only gave me
SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '= '1'' at line 1 and $category's ID is 1 so I guess it's not the way to go. Anyone knows how to deal with that ?

Thank you!


Solution

  • I finally found out how to select all products that are in a category thanks to https://stackoverflow.com/a/9808277/1300454.

    I tweaked his solution a bit so I could pass an array of Category entities and it would find all products that are within these categories. If you give more than one entity it will return any product that is in at least one of the given categories.

    Here's my tweak (I located this function in my Product entity):

    /**
     *
     * Takes an array of GPos_Model_Category entities as parameter and returns all products in these categories
     * @param array $categories
     */
    public static function findByCategories($categories) {
        $categoryArray = array();
        foreach ($categories as $category) {
            array_push($categoryArray, $category->getId());
        }
        $qb = Zend_Registry::get('entityManager')->createQueryBuilder();
        $qb ->select('p')
        ->from('GPos_Model_Product', 'p')
        ->leftJoin('p.categories', 'c')
        ->andWhere($qb->expr()->in('c.id', $categoryArray));
    
        return $qb->getQuery()->execute();;
    }
    

    Here's how you call it:

    $products_cat = GPos_Model_Product::findByCategories(array($category));
    

    In this case $category is an entity alone that's why I put it in an array before giving it to the function.

    And here is the way you find products that are not in a given category or list of category:

    /**
     *
     * Takes an array of GPos_Model_Category entities as parameter and returns all products not in these categories
     * @param array $categories
     */
    public static function findByNotCategories($categories) {
        $categoryArray = array();
        foreach ($categories as $category) {
            array_push($categoryArray, $category->getId());
        }
        $qb = Zend_Registry::get('entityManager')->createQueryBuilder();
        $qb2 = Zend_Registry::get('entityManager')->createQueryBuilder();
        $qb->select('p')
        ->from('GPos_Model_Product', 'p')
        ->where($qb->expr()->notIn('p.id',
            $qb2->select('p2.id')
            ->from('GPos_Model_Product', 'p2')
            ->leftJoin('p2.categories', 'c')
            ->andWhere($qb->expr()->in('c.id', $categoryArray))
            ->getDQL()
        ));
    
        return $qb->getQuery()->execute();
    }
    

    This is actually using a subselect. I'm selecting all products id that are in the given category (that's the subselect) then I'm selecting all products that are not in the result of the subselect. My job here is done!