Doctrine: ManyToMany Relationship with Extra Fields

5

I have a many-to-many relationship between two tables: Products and Orders.

Doctrine then generates a third table. Until then calm down.

But I wanted to generate in this third table more fields (qtde and unit value at the time of purchase).

The question is: How do I make this relationship work in Doctrine (with annotations right in the Entities) and how do I pick up the quantity and value of a given product table)?

The closest of an explanation I found was this:

But there is no explanation of how the ArrayCollection works for cases like this and how to get or set the extra fields in the third table.

Thanks in advance for all the help you can give.

Table Products (Table 1)

Id           (pk)
Name
Value

Orders table (Table 2)

Id           (pk)
TotalValue

Table ProductsOrders (Insetting Table)

Product      (fk)
Order        (fk)
Quantity
UnityValue

-

Entities

Below are the entities made according to instructions. Note, however, that the correct method of working with the extra - i columns is missing. and how to get EntityManager to take the extra columns of the ProductOrder entity (through of course the link fields in Product entities and < strong> Order ).

It is also important information on how to proceed to make the relationship of this third table with another, since it does not have a proper ID, since the ID is formed by the set of Product ID + Order ID.

<?php

/**
 * @Entity(repositoryClass="Repositories\ProductRepository")
 * @Table(name="Products")
 */
class Product
{
    /**
     * @Id
     * @Column(type="integer")
     * @GeneratedValue
     */
    protected $id;

    /**
     * @Column(length=100)
     */
    protected $name;

    /**
     * @Column(type="decimal", precision=10, scale=2)
     */
    protected $value;

    /**
     * @OneToMany(targetEntity="ProductOrder", mappedBy="product")
     */
    protected $orders;

    public function __construct()
    {
        $this->orders = new \Doctrine\Common\Collections\ArrayCollection();
    }

    public function getId(): int
    {
        return $this->id;
    }

    public function setId(int $id): Product
    {
        $this->id = $id;
        return $this;
    }

    public function getName(): string
    {
        return $this->name;
    }

    public function setName(string $name): Product
    {
        $this->name = $name;
        return $this;
    }

    public function getValue(): float
    {
        return $this->value;
    }

    public function setValue(float $value): Product
    {
        $this->value = $value;
        return $this;
    }

    public function getOrders()
    {
        return $this->orders;
    }

    public function addOrder(Order $order)
    {
        if ($this->getOrders()->contains($order)) {
            return $this;
        }

        $this->getOrders()->add($order);
        $order->addProduct($this);

        return $this;
    }

    public function removeOrder(Order $order)
    {
        if (!$this->getOrders()->contains($order)) {
            return $this;
        }

        $this->getOrders()->removeElement($order);
        $order->removeProduct($this);

        return $this;
    }
}

/**
 * @Entity(repositoryClass="Repositories\OrderRepository")
 * @Table(name="Orders")
 */
class Order
{
    /**
     * @Id
     * @Column(type="integer")
     * @GeneratedValue
     */
    protected $id;

    /**
     * @Column(type="decimal", precision=10, scale=2)
     */
    protected $totalValue;

    /**
     * @OneToMany(targetEntity="ProductOrder", mappedBy="order")
     */
    protected $products;

    public function __construct()
    {
        $this->products = new \Doctrine\Common\Collections\ArrayCollection();
    }

    public function getId(): int
    {
        return $this->id;
    }

    public function setId(int $id): Order
    {
        $this->id = $id;
        return $this;
    }

    public function getTotalValue(): float
    {
        return $this->totalValue;
    }

    public function setTotalValue(float $totalValue): Order
    {
        $this->totalValue = $totalValue;
        return $this;
    }

    public function getProducts()
    {
        return $this->products;
    }

    public function addProduct(Product $product)
    {
        if ($this->getProducts()->contains($product)) {
            return $this;
        }

        $this->getProducts()->add($product);
        $product->addOrder($this);

        return $this;
    }

    public function removeProduct(Product $product)
    {
        if (!$this->getProducts()->contains($product)) {
            return $this;
        }

        $this->getProducts()->removeElement($product);
        $product->removeOrder($this);

        return $this;
    }
}

/**
 * @Entity(repositoryClass="Repositories\ProductOrderRepository")
 * @Table(name="ProductsOrders")
 */
class ProductOrder
{
    /**
     * @Id
     * @ManyToOne(targetEntity="Order", inversedBy="products")
     * @JoinColumn(name="order", referencedColumnName="id")
     */
    protected $order;

    /**
     * @Id
     * @ManyToOne(targetEntity="Product", inversedBy="orders")
     * @JoinColumn(name="product", referencedColumnName="id")
     */
    protected $product;

    /**
     * @Column(type="integer")
     */
    protected $quantity;

    /**
     * @Column(type="decimal", precision=10, scale=2)
     */
    protected $unityValue;

    public function getOrder()
    {
        return $this->order;
    }

    public function setOrder($order)
    {
        $this->order = $order;
        return $this;
    }

    public function getProduct()
    {
        return $this->product;
    }

    public function setProduct($product)
    {
        $this->product = $product;
        return $this;
    }

    public function getQuantity()
    {
        return $this->quantity;
    }

    public function setQuantity($quantity)
    {
        $this->quantity = $quantity;
        return $this;
    }

    public function getUnityValue()
    {
        return $this->unityValue;
    }

    public function setUnityValue($unityValue)
    {
        $this->unityValue = $unityValue;
        return $this;
    }
}

Example of using EM

<?php

$product = $em->getReference(User::class, 1);
$order = $em->getReference(User::class, 1);

$productOrder = new ProductOrder();
$productOrder
    ->setOrder($order)
    ->setProduct($product)
    ->setQuantity(5)
    ->setUnityValue(99.9);

// Isso está cadastrando corretamente.
$em->getRepository(ProductOrder::class)->create($productOrder);

// Porém, isso não está funcionando:
echo $order->getProducts()[0]->getQuantity();
    
asked by anonymous 26.05.2017 / 15:34

2 answers

3

One way to do this is to treat the third table ( PorductsOrders ) as a normal entity.

In this case you will not use ArrayCollections (at least not in entities) and will do searches using only the intersection table.

To do this remove the InversedBy option from the anotions of the ProductOrder entity and remove the Store # products and Product # stores properties.

/**
 * @Entity(repositoryClass="Repositories\ProductOrderRepository")
 * @Table(name="ProductsOrders")
 */
class ProductOrder
{
    /**
     * @Id
     * @ManyToOne(targetEntity="Order")
     * @JoinColumn(name="order", referencedColumnName="id")
     */
    protected $order;

    /**
     * @Id
     * @ManyToOne(targetEntity="Product")
     * @JoinColumn(name="product", referencedColumnName="id")
     */
    protected $product;

    /**
     * @Column(type="integer")
     */
    protected $quantity;

    /**
     * @Column(type="decimal", precision=10, scale=2)
     */
    protected $unityValue;

    public function getOrder()
    {
        return $this->order;
    }

    public function setOrder($order)
    {
        $this->order = $order;
        return $this;
    }

    public function getProduct()
    {
        return $this->product;
    }

    public function setProduct($product)
    {
        $this->product = $product;
        return $this;
    }

    public function getQuantity()
    {
        return $this->quantity;
    }

    public function setQuantity($quantity)
    {
        $this->quantity = $quantity;
        return $this;
    }

    public function getUnityValue()
    {
        return $this->unityValue;
    }

    public function setUnityValue($unityValue)
    {
        $this->unityValue = $unityValue;
        return $this;
    }
}

You can make use of the intersection entity like this:

<?php

$product = $em->getReference(User::class, 1);
$order = $em->getReference(User::class, 1);

$productOrder = new ProductOrder();
$productOrder
    ->setOrder($order)
    ->setProduct($product)
    ->setQuantity(5)
    ->setUnityValue(99.9);

// Isso está cadastrando corretamente.
$em->getRepository(ProductOrder::class)->create($productOrder);

To search, it can be done like this:

<?php 

// Pesquisando:
$productOrder = $em->findBy([
    'order' => 1,
    'product' => 1
]);

Obs : Optionally, in your ProductOrderRepository repository, you can create method findByIds($orderId, $productId) , method findOrderProducts($orderId) and method findProductOrders($productId)

    
29.05.2017 / 21:22
-1

According to this post The solution is to consider the join table as a separate entity and map relationships as One-To-Many.

In the implementation that showed the mapping of Product.orders and Orders.product should already return a list of ProductOrders , so you already have access to the extra columns.

You will need to add a constructor in ProductOrders that receives Product and Order :

ProductOrder{ 
    ...
    public function __construct(Order $order, Product $product, $quantity= 0, $unitValue=0) 
    { 
        $this->product= $product; 
        $this->order= $order; 
        $this->quantity= $quantity; 
        $this->unitValue= $unitValue; 
    } 
}

You can also add getters and setters in the same way you did in other entities.

    
29.05.2017 / 15:44