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.
Related
Working with Symfony 3.x and Doctrine I have this problem:
Entity "Foo" is defined as follows:
/**
* #ORM\Entity
* #ORM\Table(name="foo")
*/
class Foo
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $incr_int;
...
}
Leaving aside that $id and $incr_int will (allways?) be the same value I get the following error when creating a new Entity of type Foo:
An exception occurred while executing 'INSERT INTO foo (incr_int) VALUES (?)' with params [null]:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'incr_int' cannot be null
While this seem to make sense looking at the error itself I dont get how to fix it when creating Foo like this (which I believe is the standard way in Symfony?):
$foo = new Foo();
$em->persist($foo); // $em is the entity manager
$em->flush();
Like I would expect it works if I delete the $incr_int field from Foo because the only remaining field $id is auto generated by increasing the last inserted id-value by 1. I was assuming this behaviour should be the same for the $incr_int field. Well... obviously it's not and I can't figure out why. Any help is highly appreciated.
There were 2 problems in my code (thanks to #nospor).
A table can only have one auto increment field (and a second one doesn't make much sense anyways).
When creating a new entity the id is generated when really updating the DB (flushing the entity manager).
I could solve this issue by removing the second auto increment condition and creating the Entity with the required fields (here only the auto generated id field) and then flushing it. After flushing I am able to retrieve the id and generate a second field's value holding a calculated value that depends on the id value as well.
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
I have a database with several one-to-many/many-to-one relationships. For example, I have table called Students, and a related table called StudentNotes. The StudentNotes table has a foreign key called student_id. I want the foreign key to have the constraint on delete = cascade.
I set up my Doctrine 2 entities with the property #JoinColumn(on="CASCADE") and updated the database schema. Unfortunately, whenever it does this, it sets the on delete to "restrict". What am I doing wrong?
Here's the relevant code from my Students entity:
/**
* #var Collection Notes
*
* #OneToMany(targetEntity="StudentNotes", mappedBy="student")
* #JoinColumn(onCascade="DELETE")
*/
protected $notes;
And from StudentNotes:
/**
* #var \Entities\Students Student
*
* #ManyToOne(targetEntity="Students", inversedBy="notes")
* #OrderBy({"datetime"="DESC"})
*/
protected $student;
I've even tried adding all of the column information (i.e., name="student_id", referencedColumnName="id"), but nothing changes.
EDIT
I messed up when I originally wrote this: I wrote #JoinColumn(onCascade="DELETE"), when I meant to write #JoinColumn(onDelete="CASCADE"). Either way, this is not working properly: validate-schema fails because the database is not in sync with the schema.
onCascade do not exists, you need instead onDelete="CASCADE".
#JoinColumn(onDelete="CASCADE")
OK, I figured out what I'd forgotten to do!
I had neglected to go back to the command line and run the following command:
php doctrine.php orm:schema-tool:update --force
This did the trick
I have a table product, the items in these table are referenced in tables such as cart_item and order_item as well as shipping_item etc.
All these references are optional (the product_id is set to nullable in those tables).
I need to have a way to delete a product and still keeping the other tables's records. One way I can think of is to go into all those tables, set the product_id to null, then go back to the product table to delete. However, since I may not know all the tables that are referencing to product (many other bundles can have entities that are referencing to this product), is there a way that I can know all these association to loop through and set null?
(Or perhaps there is a better way?)
PS: the idea that this is a shopping cart and the owner may want to remove expired products to clean up but for ordered, shipped items they still need to keep records.
Edit1:
This is the definition of the product reference in the OrderItem entity:
/**
* #var \Product
*
* #ORM\ManyToOne(targetEntity="Product")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="product_id", referencedColumnName="id")
* })
*/
private $product;
The error I'm getting:
PDOException: SQLSTATE[23000]: Integrity constraint violation: 1451
Cannot delete or update a parent row: a foreign key constraint fails
(test.order_item, C ONSTRAINT fk_order_item_product1 FOREIGN KEY
(product_id) REFERENCES product (id) ON DELETE NO ACTION ON
UPDATE NO ACTION)
Edit2:
I initially set onupdate="SET NULL" to the order_item entity and thought that was enough, it was not:
/**
* #var \Product
*
* #ORM\ManyToOne(targetEntity="Product")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="product_id", referencedColumnName="id", nullable=true, onDelete="SET NULL")
* })
*/
private $product;
After that, I had to update db schema as well.
Assuming you have the proper relations set up between the owning entity product and the other entities e.g. cart_item that should have a foreign_key, your wanted behaviour is the default for doctrine 2.
Take a look here in the manual
As an example they show the deletion of a User entity and its corresponding Comments
$user = $em->find('User', $deleteUserId);
foreach ($user->getAuthoredComments() AS $comment) {
$em->remove($comment);
}
$em->remove($user);
$em->flush();
The example states:
Without the loop over all the authored comments Doctrine would use an UPDATE statement only to set the foreign key to NULL and only the User would be deleted from the database during the flush()-Operation.
This suggests to me that in your case you actually want that behaviour. So just remove the product entity and doctrine 2 will automatically find all other entities with a foreign_key belonging to that product and will set it to NULL
Edit
Your error message suggests that upon attempted removal of the product entity there are still foreign_keys present, i.e. they have not been set to null properly by Doctrine.
You need to be sure to add the cascade property, specifically remove to your entity relationship. It would look something like the following:
<?php
class Product
{
//...
/**
* Bidirectional - One-To-Many (INVERSE SIDE)
*
* #OneToMany(targetEntity="Cart", mappedBy="product", cascade={"remove"})
*/
private $carts;
//...
}
The statement
I'm trying to reproduce the automatic Doctrine mechanism for handling Many-to-Many bidrectional relationships, but introducing a custom join table.
I've already digged into similar questions:
Joining-Table with Metadata Impairs Getters/Setters - Doctrine 2
but it doesn't really help me because it's absolutely unidirectional
doctrine2 many to many self referencing with intermediate details
but this one does not even talk about managing the relations
Doctrine2: Best way to handle many-to-many with extra columns in reference table
is very interesting. However, although the author mentions its bidirectional needs, he doesn't cover the case.
I'm aware that a join table with extra fields is not an association anymore, just a third entity that refers to the two other ones. And from that statement, it's obvious that one cannot expect it to work out-of-the-box as an implicit Many-to-Many association managed by Doctrine.
But i want to have this trio to work as a simple, straight, bidirectional Many-to-Many association, so that means using proxy methods and relying on a Logic class.
The code
There's a Category entity and a Product entity:
/**
* #ORM\Table(name="category")
* #ORM\Entity(repositoryClass="CategoryRepository")
*/
class Category
{
/**
...
*/
protected $id = null;
/**
* #ORM\OneToMany(targetEntity="CategoryProduct", mappedBy="category", fetch="LAZY", cascade={"persist"})
*/
protected $categoryProducts;
}
and
/**
* #ORM\Table(name="product")
* #ORM\Entity(repositoryClass="ProductRepository")
*/
class Product
{
/**
...
*/
protected $id = null;
/**
* #ORM\OneToMany(targetEntity="CategoryProduct", mappedBy="product", fetch="LAZY", cascade={"persist"})
*/
protected $categoryProducts;
}
and of course a join entity:
/**
* #ORM\Table(name="category_product")
* #ORM\Entity(repositoryClass="CategoryProductRepository")
*/
class CategoryProduct
{
/**
...
*/
protected $id = null;
/**
* #ORM\ManyToOne(targetEntity="Category", fetch="EAGER", inversedBy="categoryProducts")
* #ORM\JoinColumn(onDelete="CASCADE")
*/
protected $category;
/**
* #ORM\ManyToOne(targetEntity="Product", fetch="EAGER", inversedBy="categoryProducts")
* #ORM\JoinColumn(onDelete="CASCADE")
*/
protected $product;
/**
* #ORM\Column(type="boolean", nullable=true)
*/
protected $starred = false;
}
The problem
How to keep an up-to-date list of CategoryProduct entities available to both entities in a pure ORM-style way? In an ORM, everything is managed on the Object layer. Changes to DB are made only on user's request, but it's not compulsory as long as one only works from the ORM point of view. In other words:
$category->addProduct($product);
does not write anything to the DB, and does not even persist any object to the entity manager, but one can still retrieve or remove this product from the list as long as the script runs.
In the case of a custom join table, it's different, because when one wants to add a product, he must create and persist a CategoryProduct entity. So what if we need to retrieve this association from the inverse side?. Here is a code sample that demonstrates my problem:
$product->addCategory($category);
$category->addProduct($product);
In this bidirectional association, how can the $category::addProduct function know about the instance of CategoryProduct entity created by $product::addcategory? The risk is to create two similar join entities for the same association, and i don't know how to avoid it.