How to deal with association on composite key entities with Doctrine2? - doctrine-orm

Say I have an Offer, which can have 1-n Range.
Immediately you think, "put a offer_id inside Range".
But my Offer has a composite primary key (composed of two fields). There is no AUTOINCREMENT id column.
The Doctrine2 documentation doesn't say much about that particular case, here is my entities:
<?php
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table()
* #ORM\Entity
*/
class Offer
{
/**
* #var Site $site
* #ORM\Id
* #ORM\ManyToOne(targetEntity="Site")
* #ORM\JoinColumn(name="site_id", referencedColumnName="id")
*/
private $site;
/**
* #var string $pouet
* #ORM\Id
* #ORM\Column(name="pouet", type="string", length=255)
*/
private $pouet;
}
<?php
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table(name="RangeItem")
* #ORM\Entity
*/
class Range
{
/**
* #todo This is test code only do not push me :-)
* #var ArrayCollection
* #ORM\ManyToOne(targetEntity="Offer")
*/
private $offers;
}
I obtaind this error:
[Doctrine\ORM\ORMException]
Column name id referenced for relation from
Pouet\MyBundle\Entity\Range towards Pouet\MyBundle\Entity\Offer does
not exist.
That make sense, but how can I deal with this issue? Is a Table with composite primary key forbidden to have associations on it?

I believe the solution is to mirror the primary key (PK) for the foreign key (FK). I.E. for each column that makes up the PK (site, pouet) you need to have the same columns on the related entity.
You can do this by using the JoinColumns annotation (or the equivalent in YAML/XML) with a JoinColumn for each part of the composite FK:
/**
* #ORM\Table(name="RangeItem")
* #ORM\Entity
*/
class Range
{
/**
* #todo This is test code only do not push me :-)
* #var ArrayCollection
* #ORM\ManyToOne(targetEntity="Offer")
* #ORM\JoinColumns(
* #ORM\JoinColumn(name="site_id", referencedColumnName="site_id"),
* #ORM\JoinColumn(name="pouet", referencedColumnName="pouet")
* )
*/
private $offers;
}
I hope this might help somebody who is still struggling with this issue.

You should be able to use a #JoinColumn annotation in the Range class to specify which Id to use:
/**
* #ORM\ManyToOne(targetEntity="Offer")
* #ORM\JoinColumn(name="offer_pouet", referencedColumnName="pouet")
*/
private $offers;
Because the defaults for #JoinColumn, if you do not specify them, would be offer_id and id, respectively, you need to manually specify (I'm making a bit of an assumption here that pouet is a unique value for your Offer class).
EDIT: based on your comment, I found a tutorial on the Doctrine Project site for Composite Primary Key. The entity relationship has mappedBy for one key and indexBy for the other. Hope that helps.

Related

What is the right way to define relation one field to multiple tables (by condition)

I have two Doctrine entities Order and Item. For both of these entities some related files (one or more) can be stored in database. File information is stored in File entity. I need to define relations within the File entity considering that sometimes file_owner_id can refer to record from Order table and in other cases it can refer to record from Item table. File owner is defined by the owner_type field value.
I tried to do like this
/**
* UploadedFile
*
* #ORM\Table(name="uploaded_file", indexes={#ORM\Index(name="file", columns={"file"})})
* #ORM\Entity(repositoryClass="App\Repository\Item\FileRepository")
*/
class File
{
/**
* #var string
*
* #ORM\Column(name="owner_type", type="string", length=0, nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="NONE")
*/
private $ownerType;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Item", inversedBy="files")
* #ORM\JoinColumn(name="owner_id", referencedColumnName="id", nullable=false)
*/
private $itemId;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Order", inversedBy="files")
* #ORM\JoinColumn(name="owner_id", referencedColumnName="id", nullable=false)
*/
private $orderId;
...
}
but it does not work. I've got an errors when trying to persist. So the question is if Doctrine have some way to define this type of entity relation?

ManyToOne from multiple entities into one

I have 3 entities:
client
company
contact
client entity can have multiple contact entities, also company can have multiple contact entities.
Contact entity looks like this:
class ContactDetail
{
use Timestampable;
use Bleamable;
CONST TYPE_EMAIL = 'EMAIL';
CONST TYPE_PHONE = 'PHONE';
CONST TYPE_FAX = 'FAX';
CONST TYPE_MOBILE = 'MOBILE';
/**
* #ORM\Id
* #ORM\Column(type="integer", options={"unsigned"=true})
* #ORM\GeneratedValue(strategy="AUTO")
* #Expose
* #JMS\Groups({"ROLE_USER","ROLE_ADMIN"})
*/
private $id;
/**
* #var string
* #Assert\NotNull()
* #ORM\Column(type="string", columnDefinition="ENUM('EMAIL', 'PHONE', 'FAX', 'MOBILE')", nullable=false)
* #Expose
* #JMS\Groups({"ROLE_USER","ROLE_ADMIN"})
*/
private $type;
/**
* #var string
* #ORM\Column(type="string", nullable=false)
* #Assert\NotBlank(message="Please enter proper value")
* #Expose
* #JMS\Groups({"ROLE_USER","ROLE_ADMIN"})
*/
private $value;
(...)
}
The question is:
Should I do it in this way:
client entity:
/**
* #ORM\OneToMany(targetEntity="CoreBundle\Entity\ContactDetail", mappedBy="client")
* #ORM\JoinColumn(name="contactDetail", referencedColumnName="id")
*/
protected $contactDetails;
company entity:
/**
* #ORM\OneToMany(targetEntity="CoreBundle\Entity\ContactDetail", mappedBy="company")
* #ORM\JoinColumn(name="contactDetail", referencedColumnName="id")
*/
protected $contactDetails;
Which will creates two additional columns (company and client) in the contact entity or should I do it in some other way?
Ex.
Create two columns: entityName and entityId and bind it somehow to client and company?
If second is proper way, please tell me how to achieve this.
Consider to solve this using a unidirectional map from Client and Company to Contact. In this case is a little trick because there is no way to create a OneToMany relationship without using mappedBy and map this bidirectional so Contact need to have theses two columns one for client other for company.
Looking after doctrine documentation, the solution may be a little odd here. It's use a ManyToMany relationship unidirectional from Client and Company. And you don't need to have foreign keys in Client nor Company netheir in Contact. But will have to create a new table to intermediate this relationship.
The simple way to go is create a table for each relationship on table "Client_Contact" and other "Company_Contact" each on with to columns, one for client_id/company_id other for contact_id.col

Doctrine2 - two fields linked with one entity

I have a table where two fields must be linked with Product entity. But Doctrine recognizes only one of them, processing second as usual integer.
I've read an almost similar question
Doctrine2 Mapping: 2 fields mapped to one field (ManyToOne)
but as far as I can see, my annotations must be correct.
Any ideas what am I doing wrong?
class ProductProduct:
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="NONE")
*/
private $id;
/**
* #ORM\Column(name="product_id", type="integer")
* #ORM\ManyToOne(targetEntity="Product", inversedBy="productProduct")
* #ORM\JoinColumn(name="product_id", referencedColumnName="id")
*/
private $product;
/**
* #ORM\Column(name="product_replace_id", type="integer")
* #ORM\ManyToOne(targetEntity="Product", inversedBy="productProductReplace")
* #ORM\JoinColumn(name="product_replace_id", referencedColumnName="id")
*/
private $productIdReplace;
class Product:
/**
* #ORM\OneToMany(targetEntity="ProductProduct", mappedBy="product")
**/
private $productProduct;
/**
* #ORM\OneToMany(targetEntity="ProductProduct", mappedBy="productIdReplace")
**/
private $productProductReplace;
Damn, the problem was in the following lines:
#ORM\Column(name="product_replace_id", type="integer")
Seems like that annotation was dominating, overwriting #JoinColumn one. I deleted those lines in both entities and all seems to work now.
Hope this question will help someone to avoid such simple bugs :)

How can I implement a OneToMany association with a separate link table?

I'm implementing a new application on top of an existing database. The existing database is being used by a mobile application and because the mobile app is being developed by a different team I am not allowed to change the structure of existing tables.
The existing database has a user table and for my own application's users I created my own table and Doctrine entity called PortalUser (table portal_user).
The PortalUser entity is going to have a OneToMany association called $children which refers to the existing User entity. In other words each PortalUser has zero or more child User entities.
The most natural way to implement this is to have something like this (simplified):
User (the existing entity):
class User
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var PortalUser
*
* #ORM\ManyToOne(targetEntity="PortalUser", inversedBy="children")
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id")
*/
private $parent;
}
PortalUser entity:
class PortalUser
{
/**
* #var int
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var array
* #ORM\OneToMany(targetEntity="User", mappedBy="parent")
*/
protected $children;
}
This will create a new column "parent_id" in the existing user table which isn't allowed. So would it be possible to get a separate link table with parent_id and child_id columns, equivalent to a regular ManyToMany link table? And if so what annotations would result in such a structure?
Okay this is embarrassing. Turns out it's in the Doctrine documentation:
A unidirectional one-to-many association can be mapped through a join
table. From Doctrine’s point of view, it is simply mapped as a
unidirectional many-to-many whereby a unique constraint on one of the
join columns enforces the one-to-many cardinality.
Doctrine manual

Doctrine 2 self-referenced entity without unnecessary queries

I'm trying that a self-referenced entity stop from querying the database everytime I fetch the children of one object, and get the whole tree in one query.
This is my entity:
/**
* #ORM\Entity(repositoryClass="ExampleRep")
* #ORM\Table(name="example_table")
*/
class Example {
/**
* #ORM\Id
* #ORM\Column(type="integer", nullable=false);
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Example", inversedBy="children")
* #ORM\JoinColumn(name="parent", referencedColumnName="id", onDelete="SET NULL")
*/
private $parent = null;
/**
* #ORM\OneToMany(targetEntity="Example", mappedBy="parent")
*/
private $children;
}
And i'm calling my date using queryBuilder like:
$query = $this->createQueryBuilder('e');
$query->orderBy('e.parent', 'ASC');
$example_data = $query->getQuery()->getResult();
When I cycle my example_data and call getChildren, another query is made, even if that same object, was already called in the query.
I've followed the example here: Doctrine - self-referencing entity - disable fetching of children but when i do it, my getChildren returns nothing.
Is there a way to fetch my data without overloading the database with multiple requests?
If you know the depth of your tree, you can just do a custom dql query and do:
return $this->createQueryBuilder('example')
->addSelect('children')
->leftJoin('example.children', 'children')
->addSelect('subChildren')
->leftJoin('children.children', 'subChildren')
;
Otherwise, and as stated here, you can generate a flat resultset, and then construct the tree from it.
I made this kind of implementation using materialized paths, but nothing forbids you to do it with foreign keys comparison:
https://github.com/KnpLabs/DoctrineBehaviors/blob/master/src/Knp/DoctrineBehaviors/ORM/Tree/Tree.php#L119
https://github.com/KnpLabs/DoctrineBehaviors/blob/master/src/Knp/DoctrineBehaviors/Model/Tree/Node.php#L219