Doctrine 2 multi-level OneToOne Cascade - php

I have three Doctrine entities: Device, which has a OneToOne relationship with Device\Status, which in turn has a OneToOne relationship with Device\Status\Battery.
I have {cascade="persist"} set between the related entities, and from what I've read, that should be all that is required for Doctrine to automatically persist each of the entities without having to do anything myself in the code.
Here's what I'm having problems with:
$device = new \Entities\Device();
$device->setId(100);
$status = $device->getStatus();
$status->setIpAddress('192.168.0.1');
$battery = $status->getBattery();
$battery->setInternalLevel(60);
$em->persist($device);
$em->flush();
After executing this code, I get the following error:
Entity of type Device\Status\Battery has identity through a foreign entity
Device\Status, however this entity has no identity itself. You have to call
EntityManager#persist() on the related entity and make sure that an identifier
was generated before trying to persist 'Device\Status\Battery'. In case of
Post Insert ID Generation (such as MySQL Auto-Increment or PostgreSQL SERIAL)
this means you have to call EntityManager#flush() between both persist
operations.
My question is: what is the correct way to setup my entities to ensure that they're persisted in the correct order?
The code for the entities can be found here: https://gist.github.com/1753524
All tests have been performed using the Doctrine 2.2 sandbox.

I think #CappY is right.
The problem is in the Status entity. when you do getBattery() and create a new Battery instance, it's related to the Status instance on which you called getBattery().
Since that instance hasn't been stored in the database yet, it's id hasn't been generated (because it's annotated as #GeneratedValue). you're almost right about cascade persist. except for that it's performed in memory.
So you need to persist and flush Status entity before doing getBattery() if you want to use that entity as id in Battery. Or else you could simple add an id field for Battery :)

You have to add cascade={"persist"} to your relation mapping. The answer you selected as correct is also correct but with that solution, if anything goes wrong after the parent data is inserted, there will be no transaction rollback. You have to set autocommit=false and perform commit transaction manually. With cascade={"persist"} you dont have to. Anything goes wrong during database action, everything will be rollbacked.

Related

Laravel polymorphic relations / model observers and database integrity

What is the best practice to keep database integrity while using laravel's polymorphic relations?
I'm using Model Observers to update/delete related models. So for example I delete related Documents on "deleted" event while deleting a Customer.
That means if an error occurs while deleting first document the rest will stay in the database... Or if I wrap documents deleting in a transaction all of them will stay in the database while parent object is deleted...
Unfortunately, there is no good answer for that problem. You can not keep your database integrity with foreign keys because of the polymorphic relation.
One way to try is to use database triggers. As Laravel polymorphic system keep a "type" column on the table that references the related model, you could put a "before delete" trigger on tables that can be morphed to delete all occurences that references the raw you want to delete.
For example, with the structure used in Laravel documentation :
staff
id - integer
name - string
orders
id - integer
price - integer
photos
id - integer
path - string
imageable_id - integer
imageable_type - string
You can put an "before delete" trigger on staff table that executes a query (PostgreSQL syntax here) like that :
DELETE FROM photos WHERE imageable_id = OLD.id AND imageable_type = 'staff'
That way, each time you delete a staff raw, all referenced photos will be deleted in the same transaction.
I really don't know if it's the best way to keep integrity with this kind of relation, but it seems to be an efficient one.

Symfony2 and Doctrine: One to Many relationship

I am writing an application in Symfony2 and Doctrine. Here's all the codes that might be necessary:
https://gist.github.com/3440325
This code block works fine and creates the relationship correctly:
$twitter->setUser($user);
$skype->setUser($user);
Working correctly means it creates a row in the users table and inserts the correct user id in the handles table.
Where as, this code block doesn't work as expected:
$user->addHandle($skype);
$user->addHandle($twitter);
It successfully inserts all the entries but can't insert the correct user id in the handles table. As a matter of fact, the user_id column remains empty.
What is going wrong here? Am I missing something? Is my expectation incorrect or there is some bug some where?
-- Masnun
Since you have a bidirectional one-to-many relationship, you need to set referenced entities on both sides synchronously.
public function addHandle(\WeCodePHP\HomeBundle\Entity\Handle $handles)
{
$this->handles[] = $handles;
$handles->setUser($this);
}
Otherwise doctrine won't guess what a handle belongs to.

Deleting table using Doctrine2 and Symfony2

How can I delete table using Doctrine2 and Symfony2? I've generated entities and updated schema, now I want to delete this structure.
Not sure if I understood your question correctly. You deleted an entity and want to delete also its generated table from database? If so:
You can't do it, because Doctrine2 only cares about tables it knows - that means the ones that are represented by Entities. Since you deleted some Entity from your application, Doctrine no longer thinks the table belongs to your application. There are situations when there are different tables of different applications in the same database. It wouldn't make sense if Doctrine deleted them just because it doesn't know anything about them. It would be racist... but against tables.
If you just want to drop tables programmatically, you can use raw query. As far as I know, Doctrine doesn't have any method for dropping tables. Or as an alternative, you can do it by hand.
You can do a raw sql.
For example in a Symfony2 controller:
$em = $this->getDoctrine()->getManager();
$sql = 'DROP TABLE hereYourTableName;';
$connection = $em->getConnection();
$stmt = $connection->prepare($sql);
$stmt->execute();
$stmt->closeCursor();
Just delete the tables which you are no longer using manually...Doctrine totally ignores unmapped tables.
In Doctrine 2.6.2 you can DROP table by deleting entity class, after migrations:diff you will get new file with DROP TABLE query, migrations:migrate will execute the command dropping desired table.
Class that was about to get deleted had relationships with other entities, so I had to brutally (manually) delete all mentions of old entity class in other entities.
Just tested.

Integrity constraint violation Doctrine 2 and Symfony

I'm having some sort of textbuilder where you can add blocks to build up your text. The building blocks are defined in the database and when you add 1 to your text, it gets saved in the session (how you build them up).
But now when I try to persist the session to the database I get an:
Integrity constraint violation: 1062 Duplicate entry 'text' for key 'UNIQ_AA5F49C77153098'
He wants to add the existing definition of the building block, but it is already defined. The session itself is serialized.
So the steps that I'm doing to save is:
1) deserialize (
I also tried to merge it, but that didn't work either.)
2) persist to database => error
Instead of persisting the unserialized object directly, do that:
- unserialize the object
- fetch the object from database
- update the retrieved object with the value of the unserialized one
- persist the retrieved object
Does it work ?
Maybe you're using the wrong association type for your model. I don't remember clearly but Doctrine adds a unicity constraints to unidirectional many to many or one to one associations.
I had a similar problem with FOSUserBundle: I am not sure how to override the mapping configuration of an extended entity (in my case the field email_canonical, which was not used, but two empty fields also triggered the integrity violation).
I ended up with manually deleting the constraint in the database (and with app/console doctrine:schema:update --dump-sql only updating the changes with the SQL command - except the UNIQUE Constraint)
As pointed out by futurecat: do you need your "text" field to be unique?

How to implement non explicit 1:1 relationship with Doctrine?

I have two tables, Inventory and, say, Stuff. Inventory is used to store data common to Stuff and other tables. The way the DBA envisioned this working would be with us inserting the Inventory table and then using the generated ID to insert the Stuff table.
How can I implement this scenario using Doctrine 2? I'm tempted to just add a 1:1 relationship on the model but I'm not sure I can convince the DBA to change the database.
With the workaround described here http://www.doctrine-project.org/docs/orm/2.0/en/reference/limitations-and-known-issues.html#foreign-keys-as-identifiers you should be able to get the DBAs schema working. With version 2.1 of Doctrine (or the current master) you can use the new foreign key as identifier feature to get it working.
However if you are not using Sequences of Oracle/Postgresql you need to flush operations for this (persist parent, flush, associate and persist child, flush)

Categories