2
votes

I have Product.php entity with self-referencing ManyToMany relationship:

<?php

namespace AppBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity(repositoryClass="AppBundle\Repository\Product")
 * @ORM\Table(name="`Product`")
 *
 * @UniqueEntity(fields={"relatedProducts", "productsInRelation"}, message="Product cannot be in relation with itself.")
 */
class Product
{
    /**
     * @ORM\ManyToMany(targetEntity="AppBundle\Entity\Product", inversedBy="productsInRelation")
     * @ORM\JoinTable(name="product_related_products",
     *     joinColumns={@ORM\JoinColumn(name="product_source_id", referencedColumnName="id")},
     *     inverseJoinColumns={@ORM\JoinColumn(name="product_target_id", referencedColumnName="id")}
     * )
     * @Assert\Count(
     *      max = 3,
     *      maxMessage = "You cannot specify more than {{ limit }} related Products"
     * )
     *
     * @var Product[]|ArrayCollection
     */
    protected $relatedProducts;

    /**
     * @ORM\ManyToMany(targetEntity="AppBundle\Entity\Product", mappedBy="relatedProducts")
     *
     * @var Product[]|ArrayCollection
     */
    protected $productsInRelation;

    /**
     * Add relatedProducts.
     *
     * @param Product $relatedProduct
     *
     * @return Product
     */
    public function addRelatedProduct(Product $relatedProduct)
    {
        $this->relatedProducts[] = $relatedProduct;

        return $this;
    }

    /**
     * Remove relatedProduct.
     *
     * @param Product $relatedProduct
     */
    public function removeRelatedProduct(Product $relatedProduct)
    {
        $this->relatedProducts->removeElement($relatedProduct);
    }

    /**
     * Get relatedProducts.
     *
     * @return Product[]|ArrayCollection
     */
    public function getRelatedProducts()
    {
        return $this->relatedProducts;
    }

    /**
     * Add ProductsInRelation.
     *
     * @param ArrayCollection $productsInRelation
     *
     * @return Product
     */
    public function addProductsInRelation(ArrayCollection $productsInRelation)
    {
        $this->productsInRelation[] = $productsInRelation;

        return $this;
    }

    /**
     * Remove ProductsInRelation.
     *
     * @param Product $productsInRelation
     */
    public function removeProductsInRelation(Product $productsInRelation)
    {
        $this->productsInRelation->removeElement($productsInRelation);
    }

    /**
     * Get ProductsInRelation.
     *
     * @return Product[]|ArrayCollection
     */
    public function getProductsInRelation()
    {
        return $this->productsInRelation;
    }
}

When I am adding @UniqueEntity constraint I am not able to persist the entity because of following error.

Type error: Argument 1 passed to Doctrine\ORM\Mapping\DefaultQuoteStrategy::getJoinTableName() must be of the type array, null given, called in /var/www//vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php on line 1669

  1. in DefaultQuoteStrategy.php line 97
  2. at DefaultQuoteStrategy->getJoinTableName(null, object(ClassMetadata), object(PostgreSQL92Platform)) in BasicEntityPersister.php line 1669
  3. at BasicEntityPersister->getSelectConditionStatementColumnSQL('productsInRelation', null) in BasicEntityPersister.php line 1582
  4. at BasicEntityPersister->getSelectConditionStatementSQL('productsInRelation', object(PersistentCollection), null) in BasicEntityPersister.php line 1724
  5. at BasicEntityPersister->getSelectConditionSQL(array('relatedProducts' => object(PersistentCollection), 'productsInRelation' => object(PersistentCollection)), null) in BasicEntityPersister.php line 1058
  6. at BasicEntityPersister->getSelectSQL(array('relatedProducts' => object(PersistentCollection), 'productsInRelation' => object(PersistentCollection)), null, null, null, null, null) in BasicEntityPersister.php line 882 7 .at BasicEntityPersister->loadAll(array('relatedProducts' => object(PersistentCollection), 'productsInRelation' => object(PersistentCollection)), null, null, null) in EntityRepository.php line 181
  7. at EntityRepository->findBy(array('relatedProducts' => object(PersistentCollection), 'productsInRelation' => object(PersistentCollection))) in UniqueEntityValidator.php line 117
  8. at UniqueEntityValidator->validate(object(Collection), object(UniqueEntity)) in RecursiveContextualValidator.php line 852
1

1 Answers

3
votes
@UniqueEntity(fields={"relatedProducts", "productsInRelation"}...

This means: make sure that there aren't two entities in my repository with the same [ relatedProducts, productsInRelation ] pair. First of all, according to your validation message this is not what you want to do (message="Product cannot be in relation with itself"), and second I'm not sure the UniqueEntity constraint can validate such cases.

If you want to use a Symfony constraint, Expression could be a solution:

/**
 * @Assert\Expression(
 *     "this not in this.getRelatedProducts().toArray()",
 *     message="Product cannot be in relation with itself"
 * )
 */
protected $relatedProducts;

/**
 * @Assert\Expression(
 *     "this not in this.getProductsInRelation().toArray()",
 *     message="Product cannot be in relation with itself"
 * )
 */
protected $productsInRelation;

Callback should work too if you prefer plain old PHP.