Symfony2 and Doctrine2: relation on 2 fields between 4 tables - symfony

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.

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.

Symfony2 Swap 2 objects primary key Doctrine

I've got myself an entity
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var integer
*
* #ORM\Column(name="position", type="integer")
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $position;
The Id is the primary key but I'll sort the array with position. I want to make functions to swap 2 of the items when sorting or move them up and down.
How can I make a constructor that will increment each new object I create automatically?
I've tried:
/**
* Constructor
*/
public function __construct()
{
$this->position = $this->id+1;
}
But the Id is assigned after persisting the object so each one has position set to 1. Do I need to use Life Cycle Callbacks?
Lifecycle callbacks could work to do what you want but you have to be aware that if you modify an entity after flushing to the database you're gonna have to flush again to save the new information.

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.

Symfony + Doctrine - UnitOfWork commit order

I have a form that shows entity:
class Event
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
*
* #ORM\OneToMany(targetEntity="EventAttendee", mappedBy="event", cascade={"all"})
*/
private $attendees;
}
and a collection within it:
class EventAttendee
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
*
* #ORM\ManyToOne(targetEntity="Event", inversedBy="attendees")
* #ORM\JoinColumn(name="event_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $event;
/**
*
* #ORM\OneToOne(targetEntity="Employee")
* #ORM\JoinColumn(name="employee_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $employee;
}
If I delete an employee from the collection and add it again, I'm getting integrity constraint violation. This is because Doctrine's UnitOfWork first executes Inserts and then Deletes. Therefore, when it inserts a new record db still has the old one with the same employee.
Doctrine2 developers did not provide any working solution for Symfony2 users (here is the thread: http://www.doctrine-project.org/jira/browse/DDC-601).
And thus, I'm asking the question here: is it anyhow possible to avoid this issue?
EDIT:
My current workaround is:
find all not-persisted colletion items ready to insert
remove them from the collection and save to a variable
remove all the items that were really deleted in the form
call flush()
add all the items for insert back to the collection
call flush()
This works for me, however doesn't look good. Maybe someone has a better solution.

Entity containing other entities without having a table

I have an Entity ( Invoice ) which is purely for calculation purposes and has no table, that associates with two other entities having relations by tables. (Although there are so many other entities involved ).
class Row{
/**
* #var integer
*
* #ORM\Column(name="row_id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="File")
* #ORM\JoinColumn(name="file_id", referencedColumnName="file_id")
*/
protected $file;
/**
* #var \DateTime
*
* #ORM\Column(name="date", type="date")
*/
private $date;
}
class File
{
/**
* #var integer
*
* #ORM\Column(name="file_id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
}
class Invoice
{
/**
* #ORM\Id
* #ORM\Column(name="invoice_id", type="integer")
* #ORM\GeneratedValue
*/
protected $id = null;
/**
* #ORM\OneToMany(targetEntity="Row", mappedBy="row_id")
*/
protected $row;
/**
* #ORM\OneToMany(targetEntity="File", mappedBy="file_id")
*/
protected $file;
}
I want to be able to query for Invoices :
$sDate = //Some date
$this->getEntityManager()
->createQuery("SELECT Invoice, Row, File
FROM
ReportsEntitiesBundle:Invoice Invoice
LEFT JOIN
Row.row Row
LEFT JOIN
Row.file File
WHERE date=:date"
)
->setParaMeter(':date', $sDate)
->setFirstResult($iPage*$iLimit)
->setMaxResults($iLimit)
->getResult();
The questions :
# Doctrine tries to query the database, how can I prevent that and have it find the relevant entities?
# How can I relate the date ( which is in Row entity and cannot be in Invoice ) to the query?
Later this Invoice will become a part of another big entity for calculating/search purposes.
Thank you
Short Answer: You can't
Long Answer : You can't because an entity with #ORM annotations means its persisted to a database - querying that entity relates to querying a database table. Why not just create the table ?!?!?
You need somewhere to persist the association between file and row - a database table is a perfect place !!!!
Update
Just to clarify ... an Entity is just a standard class - it has properties and methods ... just like any other class - When you issue doctrine based commands it uses the annotations within the entities to configure the tables / columns / relationships etc if remove those you can use it however you like ... but you will need to populate the values to use it and you wont be able to use it in a Doctrine query and it obviously wont be persisted !
You can use a read-only entity. It's contents are backed by a view which you create manually in SQL.
PHP:
/** #ORM\Entity(readOnly =true) */
class InvoiceView
{ ...
SQL:
CREATE VIEW invoice_view AS (
SELECT ...

Resources