Symfony: ManyToMany table extra columns - symfony

I have a many to many table for User and House, called user_house. Instead of just two columns: user_id and house_id, i want to add 3 more: eg action, created_at, updated_at. How can I do this?
I cannot find any relevant docs on this.
The following just creates a separate table with two columns in it.
class User extends EntityBase
{
...
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\House")
*/
protected $action;
Basically, what I want to achieve is:
in the user_house table the combination of user_id, house_id, action should be unique.
when a user clicks a "view" on a house, user_house table gets updated with some user_id, some house_id, view, now(), now()
when a user clicks a "like" on a house, user_house table gets updated with some user_id, some house_id, like, now(), now()
when a user clicks a "request a call" on a house, user_house table gets updated with some user_id, some house_id, contact, now(), now()
Could someone point me in the right direction? Thanks!

You need to break your ManyToMany relation to OneToMany and ManyToOne by introducing a junction entity called as UserHasHouses, This way you could add multiple columns to your junction table user_house
User Entity
/**
* User
* #ORM\Table(name="user")
* #ORM\Entity
*/
class User
{
/**
* #ORM\OneToMany(targetEntity="NameSpace\YourBundle\Entity\UserHasHouses", mappedBy="users",cascade={"persist","remove"} )
*/
protected $hasHouses;
}
House Entity
/**
* Group
* #ORM\Table(name="house")
* #ORM\Entity
*/
class House
{
/**
* #ORM\OneToMany(targetEntity="NameSpace\YourBundle\Entity\UserHasHouses", mappedBy="houses",cascade={"persist","remove"} )
*/
protected $hasUsers;
}
UserHasHouses Entity
/**
* UserHasHouses
* #ORM\Table(name="user_house")
* #ORM\Entity
*/
class UserHasHouses
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="NameSpace\YourBundle\Entity\House", cascade={"persist"}, fetch="LAZY")
* #ORM\JoinColumn(name="house_id", referencedColumnName="id")
*/
protected $houses;
/**
* #ORM\ManyToOne(targetEntity="NameSpace\YourBundle\Entity\User", cascade={"persist","remove"}, fetch="LAZY" )
* #ORM\JoinColumn(name="user_id", referencedColumnName="id",nullable=true)
*/
protected $users;
/**
* #var \DateTime
* #ORM\Column(name="created_at", type="datetime")
*/
protected $createdAt;
/**
* #var \DateTime
* #ORM\Column(name="updated_at", type="datetime")
*/
protected $updatedAt;
//... add other properties
public function __construct()
{
$this->createdAt= new \DateTime('now');
}
}
have additional column in ManyToMany join table in Doctrine (Symfony2)

Related

doctrine composite key and one to many

I use Symfony 2.8. I have two table and in both the primary key is composed by 3 columns:
id, tipo_corso, comune
02, it, devi
01, en, capi
09, es, file
Obviously the two table have other different columns. I can't change the primary key by use only one or two columns. For one record in StranieriCRS table there are many record in EsoneroLingua table (OneToMany):
First entity:
class StranieriCRS
{
/**
* #ORM\Column(type="string")
* #ORM\Id
*/
private $id;
/**
* #ORM\Column(type="string")
* #ORM\Id
*/
private $tipo_corso;
/**
* #ORM\Column(type="string")
* #ORM\Id
*/
private $comune;
public function __construct($id, $tipo_corso, $comune)
{
$this->id = $id;
$this->tipo_corso = $tipo_corso;
$this->comune = $comune;
$this->esonerolingua = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* #ORM\OneToMany(targetEntity="EsoneroLingua", mappedBy="stranieriCRS", fetch="EAGER")
*/
private $esonerolingua;
/**
* Get esonerolingua
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getEsonerolingua()
{
return $this->esonerolingua;
}
Second entity:
class EsoneroLingua
{
/**
* #ORM\Column(type="string")
* #ORM\Id
*/
private $id;
/**
* #ORM\Column(type="string")
* #ORM\Id
*/
private $tipo_corso;
/**
* #ORM\Column(type="string")
* #ORM\Id
*/
private $comune;
public function __construct($id, $tipo_corso, $comune)
{
$this->id = $id;
$this->tipo_corso = $tipo_corso;
$this->comune = $comune;
}
/**
* #ORM\ManyToOne(targetEntity="StranieriCRS", inversedBy="esonerolingua")
* #ORM\JoinColumns(
* #ORM\JoinColumn(name="id", referencedColumnName="id"),
* #ORM\JoinColumn(name="tipo_corso", referencedColumnName="tipo_corso"),
* #ORM\JoinColumn(name="comune", referencedColumnName="comune"),
* )
*/
private $stranieriCRS;
The problem occur when I want get the StranieriCRS object because he give me as result only one result...seems like a OneToOne relation.
My Controller:
$sql = $entityManager->createQuery("
SELECT c
FROM AppBundle:EsoneroLingua c
WHERE c.id = '1546871' and c.tipo_corso = 'C' and c.comune = '7868'
");
$test = $sql->getResult();
In $test I was expect N record of EsoneroLingua with the same record StranieriCRS but I get only one EsoneroLingua with the correct StranieriCRS object. Seems work like OneToOne relation...why? Plus if I made dump($sql->getSql()); I obtain the raw sql...I try to use it directly in my db and he give me the right result. Is it a Doctrine bug?
To make a bidirectionnal One-To-Many, specify the JoinColumns only in the Many-To-One side.
So, in StranieriCRS, remove the following lines :
* #ORM\JoinColumns(
* #ORM\JoinColumn(name="id", referencedColumnName="id"),
* #ORM\JoinColumn(name="tipo_corso", referencedColumnName="tipo_corso"),
* #ORM\JoinColumn(name="comune", referencedColumnName="comune"),
* )
And just let Doctrine guess the columns with the inversedBy and mappedBy attributes.
For more information on the mappings, see this page.

Join columns by referencedColumnName that is not primary key - id

I am rookie in Symfony Doctrine and need some help with Join entities.
Normally Column are joins by primary key ID
/**
* User
*
* #ORM\Table(name="users")
* #ORM\Entity(repositoryClass="MainBundle\Repository\UserRepository")
* UniqueEntity("email", message="Account with email already exists.")
*/
class User implements AdvancedUserInterface, \Serializable
{
/**
* #var \MainBundle\Entity\PersonDetails
*
* #ORM\ManyToOne(targetEntity="MainBundle\Entity\Person")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="person_details_id", referencedColumnName="id", nullable=true)
* })
*/
private $personDetails = null;
This is ok.
But problem is that I want to Join two columns in Relation OneToOne by id field in User Entity
/**
* User
*
* #ORM\Table(name="users")
* #ORM\Entity(repositoryClass="MainBundle\Repository\UserRepository")
* UniqueEntity("email", message="Account with email already exists.")
*/
class User implements AdvancedUserInterface, \Serializable
{
/**
* #var \MainBundle\Entity\PersonDetails
*
* #ORM\ManyToOne(targetEntity="MainBundle\Entity\Person")
* #ORM\JoinColumn(name="id", referencedColumnName="user_id", nullable=true)
* })
*/
private $personDetails = null;
When I try to join columns on this way I get error
Missing value for primary key id on MainBundle\Entity\PersonDetails
Is it possible to index other field than id or what I trying to do is impossible?
Thanks guys.
You have mixed up the column-name and the field-name that shall be referenced in your #JoinColumn declaration.
#JoinColumn(name="id", referencedColumnName="user_id")
This way Doctrine looks for a field/property named user_id on your User entity. I guess you want the column in the join-table to be named user_id and the entries being id's of the User entity.
UserDetail
/**
* #ORM\Entity
*/
class UserDetail
{
/**
* #ORM\ManyToOne(
* targetEntity="User",
* inversedBy="details"
* )
* #ORM\JoinColumn(
* name="user_id",
* referencedColumnName="id"
* )
*/
protected $user;
public function setUser(User $user)
{
$this->user = $user;
return $this;
}
/** #ORM\Column() */
protected $key;
/** #ORM\Column() */
protected $value;
public function __construct($key, $value)
{
$this->key = $key;
$this->value = $value;
}
User
class User
{
/**
* #ORM\Id()
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #ORM\OneToMany(
* targetEntity="UserDetail",
* mappedBy="user",
* cascade={
* "persist",
* "remove",
* "merge"
* },
* orphanRemoval=true
* )
*/
protected $details;
public function __construct()
{
$this->details = new ArrayCollection();
}
public function addDetail(UserDetail $detail)
{
$detail->setUser($this);
$this->details->add($detail);
return $this;
}
Now if you add a detail to your User like this and persist/flush afterwards:
$user->addDetail(new UserDetail('Height', '173cm'));
This will result in a join-colum in the user_detail table that looks like this:
| key | value | user_id |
|---------------|-----------|---------|
| Height | 173cm | 1 |
Citing Doctrine documentation:
It is not possible to use join columns pointing to non-primary keys.
Doctrine will think these are the primary keys and create lazy-loading
proxies with the data, which can lead to unexpected results. Doctrine
can for performance reasons not validate the correctness of this
settings at runtime but only through the Validate Schema command.
I had the same problem, I solved it by performing the mapping only to fields that are primary key. If I needed to get the related entities by other fields, I implemented methods in the Entity repository.

Symfony Doctrine ManyToMany add custom join field

I have two entites : Cart and Item, the relation is configured with ManyToMany because a cart can have multiple items, and a items can be in multiple carts.
So I have a link table item_cart with item_id and cart_id.
How can I work with quantity with this ? For example if I need to add 800 items with id = 2 to the cart with id = 5 ?
Is this possible to add a field quantity in the link table ?
Thanks for help.
You can do this by making the relationship itself to an entity. This entity would be called CartItem or CartItemLink.
The association changes from ManyToMany between Cart and Item to two associations ManyToOne and OneToMany:
Cart - ManyToOne - CartItem - OneToMany - Item
Now you can add additional fields to your CartItem, like a $quantity field as mentioned in your question.
So this would look something like this:
The CartItem:
class CartItem {
/** MANY-TO-ONE BIDIRECTIONAL, OWNING SIDE
* #var Cart
* #ORM\ManyToOne(targetEntity="Application\Entity\Cart", inversedBy="cartItems")
* #ORM\JoinColumn(name="cart_id", referencedColumnName="id")
*/
private $cart;
/** MANY-TO-ONE BIDIRECTIONAL, OWNING SIDE
* #var Item
* #ORM\ManyToOne(targetEntity="Application\Entity\Item", inversedBy="cartItems")
* #ORM\JoinColumn(name="item_id", referencedColumnName="id")
*/
private $item;
/**
* #var int
* #ORM\Column(type="integer", nullable=false)
*/
private $quantity;
//.. setters + getters
}
The Cart:
class Cart {
/**
* #var integer
* #ORM\Id
* #ORM\Column(type="integer", nullable=false)
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/** ONE-TO-MANY BIDIRECTIONAL, INVERSE SIDE
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="Application\Entity\CartItem", mappedBy="cart")
*/
private $cartItems;
//.. setters + getters
}
The Item:
class Item {
/**
* #var integer
* #ORM\Id
* #ORM\Column(type="integer", nullable=false)
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/** ONE-TO-MANY BIDIRECTIONAL, INVERSE SIDE
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="Application\Entity\CartItem", mappedBy="item")
*/
private $cartItems;
//.. setters + getters
}
I didn't add an id to CartItem because it can have either a composite key ($item_id + $cart_id) or a natural key and that I leave up to you.
Don't forget to initialize your $cartItems ArrayCollection inside the constructor of Item and Cart.

Doctrine2 How to check if there is the related record in

I have a bidirectional many-to-many between theese two entities:
Position
/**
* Position
*
* #ORM\Table(name="applypie_position")
* #ORM\Entity(repositoryClass="Applypie\Bundle\PositionBundle\Entity\PositionRepository")
*/
class Position
{
const IS_ACTIVE = true;
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToMany(targetEntity="Applypie\Bundle\UserBundle\Entity\Applicant", mappedBy="bookmarks")
*/
private $bookmarkedApplicants;
Applicant
/**
* Applicant
*
* #ORM\Table(name="applypie_applicant")
* #ORM\Entity
*/
class Applicant
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToMany(targetEntity="Applypie\Bundle\PositionBundle\Entity\Position", inversedBy="bookmarkedApplicants")
* #ORM\JoinTable(name="applypie_user_job_bookmarks",
* joinColumns={#ORM\JoinColumn(name="applicant_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="position_id", referencedColumnName="id")}
* )
*/
private $bookmarks;
My Problem is: In an PositionControllers Action which easily shows an position by ID i need to know if there the current Applicant whichs wants to see the position has an bookmark for the current position.
I first thought of get all the Bookmarks with $applicant->getBookmarks() and run in within a forearch, checking all the applicants bookmarks against the current position, but i think there must be an easier way?
thank you
If you want to stay object oriented you can do it this way:
class Applicant
{
// fields and ORM annotations
public function hasBookmark(Bookmark $bookmark) {
return $this->bookmarks->contains($bookmark);
}
class MyController
{
public function testAction() {
$applicant = $this->getUser(); // or however you fetch the applicant object
$bookmark = $bookmarkRepository->find($bookmarkId); // again, however you get the bookmark object
// #var boolean $applicantHasBookmark
$applicantHasBookmark = $applicant->hasBookmark($bookmark);
// other controller code
}

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