Cannot delete a records (OneToMany) with Symfony - php

I have 2 entities linked together PICTURE <-(OneToMany)-> NOTE (see class details below). When I tried to delete a picture record I got the following message from Symfony
An exception occurred while executing 'DELETE FROM Picture WHERE id =
?' with params [118]: SQLSTATE[23000]: Integrity constraint violation:
1451 Cannot delete or update a parent row: a foreign key constraint
fails (symfony.note, CONSTRAINT FK_6F8F552A8671F084 FOREIGN KEY
(picture_id) REFERENCES Picture (id))
I find it strange because if the note.author_id is the same as the current user, then I can delete the picture (and the note associated to the picture) without any problem.
here some details of my class picture and note:
Class Picture
{
/**
* #ORM\OneToMany(targetEntity="XXX\XXXBundle\Entity\Note", mappedBy="picture", cascade={"persist", "remove"})
* #ORM\JoinColumn(name="note_id", referencedColumnName="picture_id", onDelete="CASCADE")
**/
private $notes;
}
Class Note
{
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="XXX\XXXBundle\Entity\Picture", inversedBy="notes")
**/
private $picture;
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="Sdz\UserBundle\Entity\User", cascade={"persist"})
*/
private $user;
}
Here my controller to delete the picture. FYI picture is a collection in my form. I set the option allow_delete to true
foreach($pictures as $picture) // $pictures is the initial list of picture
{
if(false === $data_form->getPictures()->contains($picture))
{
$em->remove($picture);
}
}

You have constraints on database that prevents you from cascaded deleting. I would suggest to use phpMyAdmin tool (mysql command line would be horrible to use for checking constraints): open both tables structure tab, go into relational view, and here check how constraints are set.
You may have different constraints set inside doctrine mappings and inside database, and it may work fine, until you try to perform an action on database, that collides with database constraints, like here.

Related

Disable Doctrine foreign key constraint

I have a relationship on one of my models:
/**
* #ORM\ManyToOne(targetEntity="Page", cascade="persist")
* #ORM\JoinColumn(name="page_id", referencedColumnName="id")
*/
private $parentPage;
And when I delete the parent page, I get this error:
Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails
Basically my models are a page, and page revision. When I delete the page I don't want to delete the revisions. I also want to keep the page_id on the page revisions (i.e. not set it to null).
How can I do this with Doctrine?
By definition you cannot delete the record that the foreign key is pointing at without setting the key to null (onDelete="SET NULL") or cascading the delete operation (There are two options - ORM Level: cascade={"remove"} | database level: onDelete="CASCADE"). There is the alternative of setting a default value of a still existing record, but you have to do that manually, I don't think Doctrine supports this "out-of-the-box" (please correct me if I am wrong, but in this case setting a default value is not desired anyway).
This strictness is reflecting the concept of having foreign key constraints; like #Théo said:
a FK is to ensure data consistency.
Soft delete (already mentioned) is one solution, but what you could also do is add an additional removed_page_id column that you sync with the page_id just before you delete it in a preRemove event handler (life cycle callback). Whether such information has any value I wonder but I guess you have some use for it, otherwise you wouldn't ask this question.
I am definitely not claiming this is good practice, but it is at least something that you can use for your edge case. So something in the line of:
In your Revision:
/**
* #ORM\ManyToOne(targetEntity="Page", cascade="persist")
* #ORM\JoinColumn(name="page_id", referencedColumnName="id", onDelete="SET NULL")
*/
private $parentPage;
/**
* #var int
* #ORM\Column(type="integer", name="removed_page_id", nullable=true)
*/
protected $removedPageId;
And then in your Page:
/**
* #ORM\PreRemove
*/
public function preRemovePageHandler(LifecycleEventArgs $args)
{
$entityManager = $args->getEntityManager();
$page = $args->getEntity();
$revisions = $page->getRevisions();
foreach($revisions as $revision){
$revision->setRemovedPageId($page->getId());
$entityManager->persist($revision);
}
$entityManager->flush();
}
Alternatively you could of course already set the correct $removedPageId value during construction of your Revision, then you don't even need to execute a life cycle callback on remove.
I solved this by overriding one doctrine class in symfony 4.3, it looks like this for me:
<?php declare(strict_types=1);
namespace App\DBAL;
use Doctrine\DBAL\Platforms\MySQLPlatform;
/**
* Class MySQLPlatformService
* #package App\DBAL
*/
class MySQLPlatformService extends MySQLPlatform
{
/**
* Disabling the creation of foreign keys in the database (partitioning is used)
* #return false
*/
public function supportsForeignKeyConstraints(): bool
{
return false;
}
/**
* Disabling the creation of foreign keys in the database (partitioning is used)
* #return false
*/
public function supportsForeignKeyOnUpdate(): bool
{
return false;
}
}
You can disable the exporting of foreign keys for specific models:
User:
attributes:
export: tables
columns:
Now it will only export the table definition and none of the foreign keys. You can use: none, tables, constraints, plugins, or all.
You are explicitly asking for data inconsistency, but I'm pretty sure you really don't want that. I can't think of a situation where this would be defensible. It is a bad practice and definitely will cause problems. For example: what is the expected result of $revision->getPage()?
There is a very simple and elegant solution: softdeletable. It basically adds an attribute to your entity (in other words: adds column to your table) named deletedAt to store if (or better: when) that entity is deleted. So if that attribute is null, the entity isn't deleted.
The only thing you have to do is add this bundle, add a trait to your entity (Gedmo\SoftDeleteable\Traits\SoftDeleteableEntity) and update your database. It is very simple to implement: this package will do the work for you. Read the documentation to understand this extension.
Alternatively, you can add an 'enabled' boolean attribute or a status field (for example 'published', 'draft', 'deleted').
When I delete the page I don't want to delete the revisions. I also want to keep the page_id on the page revisions (i.e. not set it to null).
I think you already got your answer: Doctrine won't do that, simply because it's alien to the notion of Foreign Keys. The principle of a FK is to ensure data consistency, so if you have a FK, it must refer to an existing ID. On delete, some DB engine such as InnoDB for MySQL allow you to put an FK to NULL (assuming you did made the FK column nullable). But referring to an inexistent ID is not doable, or it's not a FK.
If you really want to do it, don't use Doctrine for this specific case, it doesn't prevent you to use Doctrine elsewhere in your codebase. Another solution is to just drop the FK constraint manually behind or use a DB statement before your query to skip the FK checks.

Doctrine's "options" property in #UniqueConstraint not working

Context
I'm using Doctrine version 2.5.0 and I have two entities: Group and Item. I'm trying to create an unique constraint so that Items cannot have the same position in a Group:
/**
* #Entity
* #Table(uniqueConstraints={
* #UniqueConstraint(name="position", columns={"group_id", "position"})
* )
*/
class Item {
...
}
This works well.
Problem
I added an active field to the Item entity, so instead of deleting an Item, I 'inactivate' it. But now the unique constraint doesn't work anymore, since the Item stays in the database with his Group reference and his position.
Attempts
Looking at the Doctrine docs, I've discovered that I can use the options property with a where clause in the #UniqueConstraint:
/**
* #Entity
* #Table(uniqueConstraints={
* #UniqueConstraint(name="position", columns={"group_id", "position"},
* options={"where":"(active = 1)"})}
* )
*/
class Item {
...
}
But the I get the same error as before:
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry
'1-1' for key 'position'
Here is my deletion code:
$item->setActive(false);
$this->_em->persist($item);
$this->_em->flush();
foreach ($item->getNextItems() as $nextItem) {
$nextItem->setPosition($nextItem->getPosition() - 1);
$this->_em->persist($nextItem);
}
$this->_em->flush();
Any idea why the options property is not working?
Update
I realised a strange behaviour. Every time I run the command ./doctrine orm:schema-tool:update --force it recreates the index:
DROP INDEX position ON Item;
CREATE UNIQUE INDEX position ON Item (group_id, position);
But once I remove the options property and run the command, I get:
Nothing to update - your database is already in sync with the current
entity metadata.
It seems I read over this from the Doctrine docs:
SQL WHERE condition to be used for partial indexes. It will only have effect on supported platforms.
After research:
MySQL does not support partial indexes of this nature
My solution is to check if the Item is really unique in a LifeCycleEvent inside the entity:
/**
* #PostPersist #PostUpdate
*/
public function checkUnicity(LifecycleEventArgs $eventArgs)
{
$entity = $eventArgs->getEntity();
$em = $eventArgs->getEntityManager();
// checking unicity here
// throw exception if not
}
if you have data already in database and then after you are adding unique constraint first please check in database that you have unique values or not.

Doctrine's JoinColumns not working properly

I'm trying to make a ManyToOne association to work in doctrine, but no luck, here's the code of my relationship :
// the entity : Product
/**
* #Id
* #ManyToOne(targetEntity="ProductsModule\Entity\Configuration", inversedBy="product")
* #JoinColumns={#JoinColumn(name="id_type", referencedColumnName="id"),
* #JoinColumn(name="id_site", referencedColumnName="id")}
*/
private $configurations;
/**
* #Id
* #ManyToOne(targetEntity="ProductsModule\Entity\Picture", inversedBy="product")
* #JoinColumn(name="id_picture", referencedColumnName="id")
*/
private $picture;
and the inverse of the association is :
// the entity : Configuration
/**
* #OneToMany(targetEntity="ProductsModule\Entity\Product", mappedBy="configurations")
*/
private $product;
// the code in the controller
$product = new Product;
$product->setConfiguration($configuration); // the configuration object is retrieved from DB
$product->setPicture($picture);
$this->em->persist($product);
try
{
$this->em->flush();
}
catch (\Exception $e)
{
var_dump($e->getMessage());die();
}
The problem is that Doctrine tries to set the first column of JoinColumn (in the example above : id_type) but it ignores the second column, which throws the following error :
An exception occurred while executing 'INSERT INTO tableX (id_picture, id_type) VALUES (?, ?)' with params [1, 1]:SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (`db`.`tableX`, CONSTRAINT `fk2` FOREIGN KEY (`id_site`) REFERENCES `sites` (`id`))
the product entity in itself is an entity that was born out of a ManyToMany relationship (that's why it has two primary keys), I just changed the names of the entities to keep the code private, because I'm not allowed to share it.
thank you.
It seems that you have with a foreign key related to site property try to update your schema with :
app/console doctrine:schema:update --force
If you got the same error try to drop the table or the database if this will not create any problem and make sure to export the sql file of the database and remove all foreign key , import your data then re-excute the commande :
app/console doctrine:schema:update --force

Symfony2 and Doctrine2 - Set onDelete

I have two entities:
Manuscript and Tasks.
In the tasks table, I have the manuscript id and some extra data.
Also, in the tasks entity I have
/**
* #ORM\OneToOne(targetEntity="Manuscript")
* #ORM\JoinColumn(name="manuscript_id", referencedColumnName="id", onDelete="CASCADE")
**/
private $manuscript;
But I would like to delete the specific task when deleting the menuscript.
When trying to delete the manuscript, I am getting:
SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails
IS it possible to do this with the onDelete, or should I delete the task manually?
Thank you.
Done by adding:
/**
* #ORM\OneToOne(targetEntity="Task", mappedBy="manuscript", cascade={"remove"})
*/
private $task;
In the manuscript entity.

Doctrine2 inheritance - 1452 : Integrity constraint violation

I deployed my projet on a new server and the error "SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails" shows up when i try to INSERT a class with inheritance :
/**
* #ORM\Table(name="prosante")
* #ORM\Entity
*/
class ProSante extends Base
{
/**
* #ORM\Column(type="string")
*/
protected $firstName;
}
/**
* #ORM\Table(name="base")
* #ORM\Entity(repositoryClass="Test\MyBundle\Entity\BaseRepository")
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="discr", type="string")
* #ORM\DiscriminatorMap({"base" = "Base",
* "prosante" = "ProSante",
* "pharmacie" = "Pharmacie"})
*/
abstract class Base
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
}
Log :
[2012-06-11 15:42:46] doctrine.DEBUG: INSERT INTO base (name) VALUES (?) ({"1":"Blabla"}) [] []
[2012-06-11 15:42:46] doctrine.DEBUG: INSERT INTO prosante (id, firstname) VALUES (?, ?) ({"1":"0","2":"PATRICK"}) [] []
I don't know what to do since the error doesn't show up when i do the same INSERT on localhost.
EDIT
I check "SELECT LAST_INSERT_ID();" after manualy insert a new "Base" into my database, its doesn't return 0, i don't understand.
SOLUTION It was a driver problem, i change it and it works.
It appears doctrine is interpreting "Base" as its own table. I think what you want to achieve here is to have your Base class as a mapped super class.
To do this simply annotate it as:
#MappedSuperclass
And then you can safely extend it into your other Entities..
http://docs.doctrine-project.org/projects/doctrine-orm/en/2.0.x/reference/inheritance-mapping.html#mapped-superclasses
I experienced quite a similar problem and spent a lot of time searching for its cause, so I allow myself to share my solution here as this is the closest question I found on SO. Hope this may help some people :)
In my case
Doctrine was throwing the same foreign key constraint violation exception, although the auto-generated key in the first query (for base class) was successfully passed on to the second query (for subclass). Second query was still failing with this constraint violation and transaction was rolled back.
Digging into logs (I'm using MariaDB on CentOS), I found this error:
[ERROR] Transaction not registered for MariaDB 2PC, but transaction is active
My solution
It turned out my MariaDB service was improperly installed or updated (I don't know exactly, someone else did it).
Running mysql_upgrade solved my issue.

Categories