The statement
I'm trying to reproduce the automatic Doctrine mechanism for handling Many-to-Many bidrectional relationships, but introducing a custom join table.
I've already digged into similar questions:
- Joining-Table with Metadata Impairs Getters/Setters - Doctrine 2 but it doesn't really help me because it's absolutely unidirectional
- doctrine2 many to many self referencing with intermediate details but this one does not even talk about managing the relations
- Doctrine2: Best way to handle many-to-many with extra columns in reference table is very interesting. However, although the author mentions its bidirectional needs, he doesn't cover the case.
I'm aware that a join table with extra fields is not an association anymore, just a third entity that refers to the two other ones. And from that statement, it's obvious that one cannot expect it to work out-of-the-box as an implicit Many-to-Many association managed by Doctrine.
But i want to have this trio to work as a simple, straight, bidirectional Many-to-Many association, so that means using proxy methods and relying on a Logic class.
The code
There's a Category entity and a Product entity:
/**
* @ORM\Table(name="category")
* @ORM\Entity(repositoryClass="CategoryRepository")
*/
class Category
{
/**
...
*/
protected $id = null;
/**
* @ORM\OneToMany(targetEntity="CategoryProduct", mappedBy="category", fetch="LAZY", cascade={"persist"})
*/
protected $categoryProducts;
}
and
/**
* @ORM\Table(name="product")
* @ORM\Entity(repositoryClass="ProductRepository")
*/
class Product
{
/**
...
*/
protected $id = null;
/**
* @ORM\OneToMany(targetEntity="CategoryProduct", mappedBy="product", fetch="LAZY", cascade={"persist"})
*/
protected $categoryProducts;
}
and of course a join entity:
/**
* @ORM\Table(name="category_product")
* @ORM\Entity(repositoryClass="CategoryProductRepository")
*/
class CategoryProduct
{
/**
...
*/
protected $id = null;
/**
* @ORM\ManyToOne(targetEntity="Category", fetch="EAGER", inversedBy="categoryProducts")
* @ORM\JoinColumn(onDelete="CASCADE")
*/
protected $category;
/**
* @ORM\ManyToOne(targetEntity="Product", fetch="EAGER", inversedBy="categoryProducts")
* @ORM\JoinColumn(onDelete="CASCADE")
*/
protected $product;
/**
* @ORM\Column(type="boolean", nullable=true)
*/
protected $starred = false;
}
The problem
How to keep an up-to-date list of CategoryProduct entities available to both entities in a pure ORM-style way? In an ORM, everything is managed on the Object layer. Changes to DB are made only on user's request, but it's not compulsory as long as one only works from the ORM point of view. In other words:
$category->addProduct($product);
does not write anything to the DB, and does not even persist any object to the entity manager, but one can still retrieve or remove this product from the list as long as the script runs.
In the case of a custom join table, it's different, because when one wants to add a product, he must create and persist a CategoryProduct entity. So what if we need to retrieve this association from the inverse side?. Here is a code sample that demonstrates my problem:
$product->addCategory($category);
$category->addProduct($product);
In this bidirectional association, how can the $category::addProduct
function know about the instance of CategoryProduct entity created by $product::addcategory
? The risk is to create two similar join entities for the same association, and i don't know how to avoid it.