Issue with ManyToMany relationship - symfony

I have 2 enteties.
A package, and a StockItem.
The Package can have many StockItems, and the StockItem can belong to Many packages. A ManyToMany seems most appropriate.
The issue appears when i attempt to add 2 of the same stockitems to a package, we get an Integraty violation:
{"code":500,"message":"An exception occurred while executing 'INSERT INTO StockItem_In_Package (package_id, stockitem_id) VALUES (?, ?)' with params [4, 1]:\n\nSQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '4-1' for key 'PRIMARY'"}
Since Package(id 4), creates 2 relationships with stockItem 1.
4-1 and 4-1
Is it possible to get around this in any way?
Is it possible to add a third column named id to the ManyToMany table, or add one named ItemCountInPackage, and just increment that by one when the same is added to a package? What would be the best solution.
Package Entity, only inserting relevant code:
/**
* Package
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="IHEnterprise\LogisticsBundle\Entity\PackageRepository")
* #ORM\HasLifecycleCallbacks
*
* #ExclusionPolicy("all")
*
*/
class Package
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #Expose
*/
private $id;
/**
* #ORM\ManyToMany(targetEntity="IHEnterprise\LogisticsBundle\Entity\StockItem", cascade={"all"}, indexBy="package_id")
* #ORM\JoinTable(name="StockItem_In_Package",
* joinColumns={#ORM\JoinColumn(name="package_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="stockitem_id", referencedColumnName="id")}
* )
* #Expose
* #Assert\NotNull()
**/
private $stockItems;
}
StockItem Entity, only inserting relevant code:
/**
* StockItem
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="IHEnterprise\LogisticsBundle\Entity\StockItemRepository")
* #ORM\HasLifecycleCallbacks
*
* #ExclusionPolicy("all")
*
*/
class StockItem
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #Expose
*/
private $id;
}
I do not need to keep track of what StockItems belong to which packages, only what packages contain which stockitems.

You can't add a third column to your ManyToMany relationship. So you can't have the same StockItem twice in one package.
To have more than one piece of the same StockItem in one package, you have to create a third entity with an unique id, and a ManyToOne relationship to StockItem and Package (and a OneToMany relationship in StockItem & Package to your third entity).

It looks like you are trying to insert an item that was already in the database as a new record. To prevent such an error do the following :
/**
* #desc Add only new items to a single package
* #param \AcmeBundle\Entity\Package $package
* #param ArrayCollection<StockItem> $stockItems
*/
public function addStockItemsAction(Package $package, $stockItems) {
foreach ($stockItems as $stockItem) {
if ($package->contains($stockItem) === false) {
$package->addStockItem($stockItem);
}
}
}

Related

How to make two manyToMany relations with the same two entities in Symfony

How I can make two different manyToMany relations in Symfony between the same entities?
If I create it by entity generator, I am getting the error:
In SchemaException.php line 112:
The table with name db.table1_table2' already exists.
The generator doesn't manage the relation table properties. You have to write it on your own and declare the #JoinTable.
/**
* #var OtherEntity[]
*
* #ORM\ManyToMany(targetEntity="OtherEntity")
* #ORM\JoinTable(
* name="this_entity_other_entity",
* joinColumns={
* #ORM\JoinColumn(name="this_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="other_entity_id", referencedColumnName="id")
* }
* )
*/
private $otherEntities;

Doctrine requires mappedBy in a OneToMany unidirectional association

When I try to make a OneToMany unidirectional association between this two entities i get this error when i try to update the database schema:
$ app/console doctrine:schema:update --dump-sql
[Doctrine\ORM\Mapping\MappingException]
OneToMany mapping on field 'address' requires the 'mappedBy'
attribute.
/**
* User
*
* #ORM\Table()
* #ORM\Entity
*/
class User
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="Address")
* #ORM\JoinTable(name="users_address",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="address_id", referencedColumnName="id", unique=true)}
* )
*/
private $address;
//...
}
/**
* Address
*
* #ORM\Table()
* #ORM\Entity
*/
class Address
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
// ...
}
Why is "mappedBy" required if the association is unidirectional?
Thanks in advance.
UPDATE: just as mentioned in the comment by #tchap an unidirectional OneToMany can be mapped with a #ManyToMany and a unique constraint on one of the join columns to enforce the onetomany cardinality. Just as the documentation says, but it was a bit confusing for me because there is already a #OneToMay annotation. So I just have to change the above code to this (by only changing the #OneToMany to #ManyToMany):
/**
* #ORM\ManyToMany(targetEntity="Address")
* #ORM\JoinTable(name="users_address",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="address_id", referencedColumnName="id", unique=true)}
* )
*/
private $address;
A OneToMany has to be bi-directional and is always the inverse side of a relationship and if you define the inverse side you need to point at the owning side of the relationship with a mappedBy attribute. The owning side is ManyToOne. In your case this would look like this:
In User your association has a mappedBy="user" attribute and points to the owning side Address:
/** ONE-TO-MANY BIDIRECTIONAL, INVERSE SIDE
* #var Collection
* #ORM\OneToMany(targetEntity="Address", mappedBy="user")
*/
protected $addresses;
In Address your association has a inversedBy="addresses" attribute and points to the inverse side User:
/** MANY-TO-ONE BIDIRECTIONAL, OWNING SIDE
* #var User
* #ORM\ManyToOne(targetEntity="User", inversedBy="addresses")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
The advantage of this solution is that you can find which user owns the Address by doing: $address->getUser(); and that you skip adding an additional join table to your database.
If you want the relationship to be uni-directional you can do as you did in your update; define a ManyToMany relationship with a join table and add a unique constraint on the address_id column.
/**
* #ORM\ManyToMany(targetEntity="Address")
* #ORM\JoinTable(name="user_address",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="address_id", referencedColumnName="id", unique=true)}
* )
*/
The disadvantage of this solution is that you cannot find out which User owns the address from the Address resource (the Address is not aware of the User). Such example can also be found here in the Doctrine documentation chapter 5.6. One-To-Many, Unidirectional with Join Table.

Symfony2 Doctrine2 ManyToMany Composite key Column name referenced does not exist

I have a ManyToMany relation with a composite key on the reverse side.
When I use the console command doctrine:schema:update I have the following error:
[Doctrine\ORM\ORMException]
Column name `keyword` referenced for relation from Map\MapBundle\Entity\
Student towards Map\MapBundle\Entity\SkillType does not exist.
I have an entity student (unique key) with a ManyToMany relation with an entity skill (composite key) which has a ManyToOne relation with skillType (unique key).
Here is the different class mapping I have:
Class Student
<?php
namespace Map\MapBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Student
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="Map\MapBundle\Entity\StudentRepository")
*/
class Student {
/**
*
* #var integer #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToMany(targetEntity="Map\MapBundle\Entity\SkillType")
* #ORM\JoinTable(name="students_skills",
* joinColumns={
* #ORM\JoinColumn(name="keyword", referencedColumnName="keyword"),
* #ORM\JoinColumn(name="attribut", referencedColumnName="attribut")
* })
*/
private $skills;
}
Class skill
<?php
namespace Map\MapBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Skill
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="Map\MapBundle\Entity\SkillRepository")
*/
class Skill {
/**
* #ORM\ManyToOne(targetEntity="Map\MapBundle\Entity\skillType")
* #ORM\JoinColumn(name="keyword", referencedColumnName="keyword")
* #ORM\Id
*/
private $keyword;
}
Classe skillType
<?php
namespace Map\MapBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* SkillType
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="Map\MapBundle\Entity\SkillTypeRepository")
*/
class SkillType {
/**
* #var string
*
* #ORM\Column(name="keyword", type="string", length=255)
* #ORM\Id
*/
private $keyword;
}
I tried to exchange the keyword and attribut #joinColumn lines, but I have the same error message with attribut instead of keyword.
I can't see what's wrong with my mapping. The table skill exists and has columns named keyword and attribut.
I hope that somebody will see where I made a mistake (probably a typo error like a missing character or a case mistake).
Thank you for your answer. It helped me a lot and i succeded doing the schema update.
Here is the code I finaly used
/**
* #ORM\ManyToMany(targetEntity="Carte\CarteBundle\Entity\Skill")
* #ORM\JoinTable(name="students_skills",
* joinColumns={#ORM\JoinColumn(name="student_id", referencedColumnName="id")},
* inverseJoinColumns={
* #ORM\JoinColumn(name="keyword", referencedColumnName="keyword"),
* #ORM\JoinColumn(name="attribut", referencedColumnName="attribut")
* })
*/
private $skills;
You write that you want Student to have the many-to-many relation with Skill, but you connected it with SkillType instead. And you're missing the inverseJoinColumns property and you didn't referenced Student properly.
Try the following annotation (untested and after looking at the documentation):
/**
* #ORM\ManyToMany(targetEntity="Map\MapBundle\Entity\Skill")
* #ORM\JoinTable(name="students_skills",
* joinColumns={#ORM\JoinColumn(name="student_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="skill_keyword", referencedColumnName="keyword")}
* )
*/
private $skills;

Doctrine OneToMany relationship error

I am trying to set up some ManyToOne/OneToMany relationships on objects in my database using Doctrine (2.2.3+) via Symfony2 (2.3.0) and am getting a strange error. Here are the relevant parts of the objects (many attributes to one product):
/**
* Product
*
* #ORM\Table(name="product")
* #ORM\Entity
*/
class Product
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
...
/**
*
* #OneToMany(targetEntity="ProductAttributes", mappedBy="product")
*/
protected $product_attributes;
public function __construct() {
$this->product_attributes = new \Doctrine\Common\Collections\ArrayCollection();
}
}
/**
* ProductAttributes
*
* #ORM\Table(name="product_attributes")
* #ORM\Entity
*/
class ProductAttributes
{
/**
* #var integer
*
* #ORM\Column(name="pa_id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $pa_id;
/**
* #var integer
*
* #ORM\Column(name="product_id", type="integer")
*/
protected $product_id;
...
/**
*
* #ManyToOne(targetEntity="Product", inversedBy="product_attributes")
* #JoinColumn(name="product_id", referencedColumnName="id")
*/
protected $product;
}
When I run the
php app/console doctrine:generate:entities BundleName
command I get the following error:
[Doctrine\Common\Annotations\AnnotationException]
[Semantical Error] The annotation "#OneToMany" in property LVMount\LVMBundle\Entity\Product::$product_attributes was never imported. Did you maybe forget to add a "use" statement for this annotation?
I have looked through the Doctrine docs and don't see any reference to a "use" statement for the ManyToOne/OneToMany pairings. What is going on?
Your annotations' syntax aren't complete.
You can see the proper syntax for any doctrine annotation below.
/**
* #ORM\********
*/
So, in your case it should look like the following.
/**
* #ORM\OneToMany(targetEntity="ProductAttributes", mappedBy="product")
*/
You will also want to fix the annotations in the ProductAttributes entity.

Doctrine2: how do I link entities using two columns (or composite keys)?

I've been trying to find an answer to this question relating to composite from both Doctrine2 documentation and across the internet, but so far have had no firm leads.
To keep it brief, essentially I need to link 3 entities: Object, Grid and GriddingSquare. An object is found in a Grid and, more specifically, within one GriddingSquare. A Grid is composed of 1 or more GriddingSquares.
In the source database (which cannot easily be modified), the GriddingSquare is uniquely identified by 'GridID' (integer) and 'GridSquareLabel' (string). These two columns are thus both in the Object table (& entity) and GriddingSquare table (& entity).
I tried the following, but can't seem to get it work:
Object entity:
/**
* #var \Grid
*
* #ORM\ManyToOne(targetEntity="Grid")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="GridID", referencedColumnName="GridID")
* })
*/
private $gridid;
/**
* #var string
*
* #ORM\Column(name="GridSquareLabel", type="string", length=20, nullable=true)
*/
private $gridsquarelabel;
/**
* #var \CdbGriddingsquares
*
* #ORM\ManyToOne(targetEntity="CdbGriddingsquares")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="GridID", referencedColumnName="GridID"),
* #ORM\JoinColumn(name="GridSquareLabel", referencedColumnName="GridSquareLabel")
* })
*/
private $griddingsquare;
GriddingSquare entity:
/**
* #var \Grid
*
* #ORM\ManyToOne(targetEntity="Grid")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="GridID", referencedColumnName="GridID")
* })
*/
private $gridid;
/**
* #var string
* #ORM\Column(name="GridSquareLabel", type="string", nullable=false)
*/
private $gridsquarelabel;
Tried to use the following suggestions from Doctrine2 documentation, but although it is technically a 'composite key' question, I can't seem to find an example which helps me understand what I need to do http://docs.doctrine-project.org/en/latest/reference/annotations-reference.html#annref-joincolumn, and http://docs.doctrine-project.org/en/2.0.x/tutorials/composite-primary-keys.html

Resources