Foreign key mapping in Doctrine 2 - doctrine-orm

I have a table user with id as the primary key and user_type_id as the foreign key which is the primary key to table user_type.
Can someone spot the error in the mapping i have used in User entity?
/**
* Primary Identifier
* #OneToMany(targetEntity="user_type")
* #JoinColumn(name="user_type_id", referencedColumnName="id")
* #ORM\Column(name="user_type_id", type="integer")
* #var integer
* #access protected
*/
protected $userTypeId;
Also Should i give mapping in user_type table.

There appear to be quite a lot of errors actually. Firstly: in ZF2 all Annotations are required to be prepended by #ORM\ so #OneToMany => #ORM\OneToMany and #JoinColumn => #ORM\JoinColumn. The #ORM\Column-Part isn't even needed in this example, as this will automatically be defined by #ORM\JoinColumn
To find out more about precise errors, use the Doctrine-CLI-Tool to validate your Annotation-Schemata
./vendor/bin/doctrine-module orm:validate-schema

Related

Doctrine 2 flushes the wrong entity first

I have this database design and in some instances Doctrine 2 makes the correct decisions in insert order and sometimes not
I have an automated import process that imports and updates data as the data provider changes it. The field item.something_happened_item_history_id is the new field that causes issues
When starting the import
Step 1. No data in the database and I create one item and one item_history, where item_history.item_id is the item.id and item.something_happened_item_history_id is null. Now I do Flush and Doctrine can figure it out that it needs to insert item before item_history. All is good.
Step 2. Now a new import comes in, and some of the data already exists in the database from the Step 1. But in the new import I actually have a new unique item. So what I do is that I create the item and item_history exactly as in Step 1. But for some reason during the Flush Doctrine thinks that item_history needs to be saved before the item. Which can't be done, because of not nullable foreign key reference on item_history.
I understand that Doctrine makes decisions based on foreign key references and the field something_happened_item_history_id is causing trouble. But it doesn't cause any trouble in the step 1. The problems occur when there is already some data in the database that is loaded into the entityManager.
I haven't been able to figure out how to manipulate Doctrine 2 so that it would always save the item before item_history.
Any ideas how to solve it?
As I really do not want to change my database design, because Doctrine 2 can't figure out the insert order.
Also it is not an option to do more Flushes, as there is a lot of data validation done before the data goes to the database. And I do not want any corrupt data in the database.
Best Regards,
Hendrik
EDIT: Doctrine 2 mapping
/**
* #Table(name="item", uniqueConstraints={#UniqueConstraint(name="uc_something_happened_item_history", columns={"something_happened_item_history_id"})})
**/
class Item
{
/** #Id #Column(name="id", type="integer", options={"unsigned":true}) #GeneratedValue **/
protected $id;
/**
* #OneToMany(targetEntity="ItemHistory", mappedBy="item")
**/
protected $itemHistories;
/**
* #OneToOne(targetEntity="ItemHistory")
* #JoinColumn(name="something_happened_item_history_id", referencedColumnName="id", nullable=true, unique=true)
**/
protected $somethingHappenedItemHistory;
}
/**
* #Table(name="item_history")
**/
class ItemHistory
{
/** #Id #Column(name="id", type="integer", options={"unsigned":true}) #GeneratedValue **/
protected $id;
/**
* #ManyToOne(targetEntity="Item", inversedBy="itemHistories")
* #JoinColumn(name="item_id", referencedColumnName="id", nullable=false)
**/
protected $item;
}
I haven't resolved the problem the way I wanted.
But by making the item_id nullable, then the Doctrine is able to finish the flush.
Data is also correct in the database.

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 ManyToMany: removing an object

I have got problem with unidirectional ManyToMany relationship in Doctrine. The case is very easy: Product has many Tags. Tag can be attached to Product but also to any "taggable" entity in my model. Here is snippet of my code:
/**
* #Entity
* #Table(name="products")
**/
class Product {
/** some other fields here */
/**
* #ManyToMany(targetEntity="Tag")
* #JoinTable(name="products_tags",
* joinColumns={#JoinColumn(name="product_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="tag_id", referencedColumnName="id")}
* )
*/
protected $tags;
}
Since its unidirectional relation code of Tag class is omitted.
For such defined association Doctrine generated the following SQL code (SQL for products table and tags table is skipped):
CREATE TABLE `products_tags` (
`product_id` int(11) NOT NULL,
`tag_id` int(11) NOT NULL,
PRIMARY KEY (`product_id`,`tag_id`),
KEY `IDX_E3AB5A2C4584665A` (`product_id`),
KEY `IDX_E3AB5A2CBAD26311` (`tag_id`),
CONSTRAINT `FK_E3AB5A2CBAD26311` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`),
CONSTRAINT `FK_E3AB5A2C4584665A` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci |
I would like to remove product that has some tags attached to it.
/* $product is already persisted, $em is an Entity Manager */
$em->remove($product);
$em->flush();
It obviously fails due to integrity constraint violation ("Cannot delete or update a parent row: a foreign key constraint fails (products_tags, CONSTRAINT FK_E3AB5A2CBAD26311 FOREIGN KEY (tag_id) REFERENCES tags (id))'").
When I alter products_tags table adding ON DELETE CASCADE to foreign keys it works as I want. I can EASILY remove TAG ($em->remove($tag)) and PRODUCT ($em->remove($product) that automatically removes referenced rows from products_tags table.
How my code should look like to obtain products_tags table with ON CASCADE DELETE foreign keys? I've already tired with cascade={"all"} but it failed.
I know, I can remove all tag from product's tags collection, but as I mentioned I would like to achieve it in one step, just by calling remove method of entity manager object.
Does Doctrine really lack of that?
Ok, I managed myself by digging in Doctrine2 docs ;) Solution is to add onDelete="cascade" to #JoinColumn.
/**
* #Entity
* #Table(name="products")
**/
class Product {
/** some other fileds here */
/**
* #ManyToMany(targetEntity="Tag")
* #JoinTable(name="products_tags",
* joinColumns={#JoinColumn(name="product_id", referencedColumnName="id", onDelete="cascade")},
* inverseJoinColumns={#JoinColumn(name="tag_id", referencedColumnName="id", onDelete="cascade")}
* )
*/
protected $tags;
}
Note that, cascade={"all"} is managed on object level (in your app), while onDelete="cascade" is on database level.

Symfony2 OneToOne relationship becomes either a Unique index or a foreign key?

I'm taking my first steps in Symfony2 entity relations.
I have an entity installation, which each has one meter, and one monitor.
This translates to a uni-directional relationship, which I defined as such:
/**
*
* #ORM\OneToOne(targetEntity="InfoMeter")
* #ORM\JoinColumn(name="meterid", referencedColumnName="id")
*/
private $meter;
/**
*
* #ORM\OneToOne(targetEntity="InstallationsRtu")
* #ORM\JoinColumn(name="monitorid", referencedColumnName="id")
*/
private $monitor;
Each monitor can only be assigned to one installation.
Each meter can be assigned to multiple installations.
When I update my database (app/console doctrine:schema:update --force), there are two outcomes.
In case of the monitor:
Everything goes alright, schema gets updated with a keyname which is prefixed by 'UNIQ_'.
In case of the meter:
I get the following error
PDOException]
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '1' for key 'UNIQ_43D26968860BE41D'
When I look at the table structure after this error occured, I can't find that mentioned constraint, but I do find FK_43D26968860BE41D, i.e. the same, but prefixed with 'FK'.
In the installation table, I now have these listed:
Keyname Type Unique Packed Field Cardinality
PRIMARY BTREE Yes No id 2
UNIQ_43D26968701EC1AB BTREE Yes No monitorid 2
FK_43D26968860BE41D BTREE No No meterid 2
So one saying 'Unique=Yes', and the other saying 'Unique=No'.
To arrive at my question:
How can I decide whether it is a UNIQ index or a FK index?
I assume that Doctrine saw that currently each monitorid is unique in the installation table, but that each meterid occurs several times in the installation table.
Hence it went with UNIQ for the first, and FK for the latter. But can I control this somehow?
If one metter can be assigned to multiple installations, shouldn't you define a OneToMany relationship?
In your installation entity:
/**
* #ORM\ManyToOne(targetEntity="InfoMeter", inversedBy="installations")
* #ORM\JoinColumn(name="infometer_id", referencedColumnName="id")
*/
protected $info_meter;
And then in your InfoMeter entity:
/**
* #ORM\OneToMany(targetEntity="Installation",mappedBy="info_meter")
*/
protected $installations;
Besides, you should add the following to your InfoMeter class constructor:
function __construct() {
[...]
$this->installations = new \Doctrine\Common\Collections\ArrayCollection();
}
I'm sure this approach can be improved depending on how you want the relation between "installations" and "meters", but this should work.

How to deal with association on composite key entities with Doctrine2?

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.