Doctrine requires mappedBy in a OneToMany unidirectional association - symfony

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.

Related

How to generalize the mappping of a symfony entity that is potentially linked to many entities types

In a previous question i wanted to know how to prevent a user to edit a form if another user was already using it.
Since i'm using SF 2.8, i can't use the lock component (> SF3.4) so i was thinking about doing it manually, with an entity managing the locks.
for my entity, i need :
user_id (the user that edit the form, create the lock)
entity_id (the id of the edited entity)
entity_class (FQCN of the entityType)
createdAt (date of the lock)
moreover, i need a UniqueEntity constraint on (user_id, entity_id and entity_class)
This is where i have a problem of mapping : the entity (id) can be of different type (i have Profession, Module, Institution, User...)
So from a Doctrine point of view, i don"t see how i can do it.
maybe i can use the entity id, but loosing the very power of docrine/symfony relationships.
/**
* Lockit.
*
* #ORM\Table(name="lockit")
*
* #UniqueEntity(
* fields={"entityClass", "entityId", "user"}
* )
*/
class Lockit
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* FQCN of the entity associated with the form to be locked.
*
* #var string
* #ORM\Column(name="entity_class", type="string")
*/
private $entityClass;
/**
* Entity id associated with the form to be locked.
* #ORM\Column(name="entity_id", type="integer")
*/
private $entityId;
/**
* #var \Simusante\SimustoryBundle\Entity\User
*
* #ORM\ManyToOne(targetEntity="Simusante\SimustoryUserBundle\Entity\User")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="userId", referencedColumnName="id", nullable=true)
* })
*/
private $user;
/**
* Date of the lock creation.
*
* #var \DateTime
* #ORM\Column(name="createdAt", type="datetime", nullable=true)
* #Assert\Date()
*/
private $createdAt;
Another solution would be to create as many lockEntities as i can lock entity with.
i would create a base Lock, and then a ProfessionLock, a InstitutionLock... where i could use the "correct" mapping.
/**
* #ORM\ManyToOne(targetEntity="Institution")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="institutionId", referencedColumnName="id", nullable=true)
* })
*/
private $user;
it would work, but this doesn't feel as "optimized" as it could.
maybe there's another way to to it, where i don't have to create as many entities as i have form type to lock.
Thank you in advance
This is where i have a problem of mapping : the entity (id) can be of different type (i have Profession, Module, Institution, User...)
As I can see, just mapping the entityId field as a text field instead of integer should solve your issue.
Your UniqueEntity constraint would still be relevant, and you would still be able to recover any locked entity Lockit instance via a simple entity repository method or whatever query method you'd like.

OneToOne Unidirectional cascade={"persist", "remove"} not working

On my symfony project I have a problem with 2 entities linked by a unidirectionnal OneToOne relationship. My entities are : Club and Address, a Club can have an address. See entity declaration bellow :
Club Entity
class Club{
/**
* #ORM\OneToOne(targetEntity="FFPM\MainBundle\Entity\Address", cascade={"persist", "remove"}, orphanRemoval=true)
* #ORM\JoinColumn(name="address_id", referencedColumnName="id", nullable=true)
*/
protected $address;
...
}
Address Entity
class Address{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
...
}
For some reason when I remove a Club the Address stays in database. I tried with orphanRemoval and cascade{"remove"} and I can't get it to work even if I'm pretty sure it's some simple mistake.
Try use this construction:
/**
* #ORM\OneToOne(targetEntity="FFPM\MainBundle\Entity\Address", mappedBy="entidad", cascade={"persist", "remove"})
* #ORM\JoinColumn(name="address_id", referencedColumnName="id", onDelete="CASCADE", nullable=true)
**/
private $personaFisica;
The relation OneToOne unidirectionnal in doctrine manage only the one side since you don't complete the relation. so there is two way you can persist or remove address entity, is that you use the domain Event listener or you manage your entity manually. And unfortunately both cases are not good practice.

Issue with ManyToMany relationship

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);
}
}
}

Symfony2 and Doctrine2: relation on 2 fields between 4 tables

I have got 4 entities (Address, User, Contact, Account). Every record in User, Contact and Account can have many Addresses. What I have done is:
/**
* Address
*
* #ORM\Table(name="address")
* #ORM\Entity
*/
class Address
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var integer
*
* #ORM\Column(name="join_id", type="integer")
*/
private $joinId;
/**
* #var string
*
* #ORM\Column(name="join_type", type="string", length=16)
*/
private $joinType;
........
}
So as join_type I am saving USER, CONTACT or ACCOUNT and as join_id I am saving the ID of related record in User, Contact and Account entity.
Is there a way to do this somehow using relations, so I don't need to run extra queries to get Address and it would be easier to saving this?
I guess the Doctrine Single Table Inheritance (http://doctrine-orm.readthedocs.org/en/latest/reference/inheritance-mapping.html#single-table-inheritance) is exactly what you need. So, you'll have one top-hierarchy entity - Address & 3 extending entities for each of your relation - UserAddress, ContactAddress, AccountAddress. Just define all common properties at Address entity, relations definitions move to inheriting entities.

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;

Resources