In my project I have two entities: planifications and selections.
There is a relation between these two objects: A planification MUST contain ONE selection. The same selection can be used by multiple planifications.
The generated code looks like this:
// Planification.php - class Planification
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Selection", inversedBy="planifications")
* #ORM\JoinColumn(name="selection_id", referencedColumnName="id")
*/
private $selection;
// Selection.php - class Selection
/**
* #ORM\OneToMany(targetEntity="App\Entity\Planification", mappedBy="selection")
*/
private $planifications;
What I would like to do is not allow a selection to be deleted if it is referenced by a planification. In other words, if a planification contains a selection - that selection can not be deleted. What happens to me is if I try to delete a selection that is in a planification, the operation completes successfully, and the $selection member in the Planification class contains NULL.
Would fixing this be possible in doctrine? I have tried adding nullable=false (on the $selection member) and onDelete="NO ACTION", and both solutions don't work.
The correct Doctrine annotation to disallow Planification::$selection to be null, would be:
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Selection", inversedBy="planifications")
* #ORM\JoinColumn(name="selection_id", nullable=false)
*/
private $selection;
(You do not need the referencedColumnName setting, since it defaults to id, and nullable=false goes in the #JoinColumn annotation).
Having the annotation will not update the DB to fit this particular definition.
Execute bin/console doctrine:schema:update --dump-sql to see the needed SQL to update your table definition, and run the resultant appropriate SQL statements against your DB to update the DB schema.
Related
I am working in a project in symfony 4 and the database is in postgresql. There are two methods that update the database:
Method 1. through a python script with plain sql inserts
Method 2. through symfony forms
Both these methods write to the same table.
Scenario A:
Method 2. adds a row to the table using symfony form (pid - 1)
Method 1. uses python script to add 500 rows (pid sequence reaches 501)
Method 2. Another user tries to add a row and doctrine tries to insert with pid = 2 but it should be "502"
I tried using GeneratedValue(strategy="AUTO") and "IDENTITY" in the entity definition but that doesn't solve the problem
/* Entity Class definition for the field */
/**
* #ORM\Id()
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/* Form submission handler */
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()){
$data =$form->getData();
$property = new Property();
$property->setTitle($data['title']);
$property->setDescription($data['description']);
$property->setPrice($data['price']);
$em->persist($property);
$em->flush();
return $this->redirectToRoute('property');
}
Possible solutions
Define the field in entity class in such a way that it automatically gets the next auto_increment value
Get the next auto_increment value before inserting from form
Replace postgresql with mysql (if that helps)
Replace auto_increment with UUID
Please suggest which is the best solution to go with if exists
Looking at your entity class I think the declaration is not complete for setting automatic value for id from the sequence table. Try this declaration instead. It worked for me
/**
* #ORM\Id()
* #ORM\Column(type="integer", options={"default"="nextval('property_id_seq'::regclass)"})
* #ORM\GeneratedValue(strategy="SEQUENCE")
*/
private $id;
Drop your existing table and update schema from bin/console
First off, I use Doctrine v2.6.2 with Symfony v4.1.7.
I have an entity Product which (among others) has a unidirectional one-to-many relation with another entity AlternativeDuration. Following Doctrine's documentation, the mapping in my product class looks like this:
/**
* #ORM\ManyToMany(
* targetEntity="AlternativeDuration",
* cascade={"persist", "merge", "remove"},
* orphanRemoval=true
* )
* #ORM\JoinTable(name="ProductAlternativeDurations",
* joinColumns={#ORM\JoinColumn(name="product_id", referencedColumnName="id", onDelete="CASCADE", nullable=false)},
* inverseJoinColumns={#ORM\JoinColumn(name="alternative_duration_id", referencedColumnName="id", unique=true, onDelete="CASCADE", nullable=false)}
* )
*/
protected $alternativeDurations;
My application recently started using React, this means I now submit a JSON representation of my product (along with an array of alternative durations) which I need to deserialize into the Product entity in the back-end. I use the JMS serializer with default configuration for this.
Now the problem I'm having happens when editing an existing product, the product already has an alternative duration which I delete. The submitted JSON looks like this:
{
"id": 1, # the ID of the existing product here
"alternativeDurations": [] # empty array because the entry is removed
}
In the back-end I successfully deserialize the JSON string:
$serializedProduct = $this->serializer->deserialize($jsonString, Product::class, 'json');
I verified here that the $serializedProduct has no alternative durations. Then I follow with a merge + flush. I expect the merge to fetch the existing product and supplement it with the $serializedProduct.
$em->merge($serializedProduct); # $em being the EntityManager.
$em->flush();
Now I would expect the AlternativeDuration entry, along with the ProductAlternativeDurations join table entry being removed. The result, however, is that the entry in ProductAlternativeDurations is removed but the AlternativeDuration is still there.
I'm at a loss now, anyone can give some pointers on why the AlternativeDuration entry is not deleted?
EDIT 19-11-2018:
It seems this is a known bug in Doctrine: #2542
Also merge will be removed in Doctrine3 so I will probably need to rethink this approach in general.
I have the following OneToOne relation inside MyEntity:
/**
* #ORM\OneToOne(targetEntity="StatusHistory")
* #ORM\JoinColumn(name="lastest_status_id", referencedColumnName="id")
*/
protected $lastestStatus;
The entity StatusHistory works. The migrations:diff command creates the files prefectly, and the database have the correct column.
The problem is, when I do die("c: " . $this->lastestStatus) inside MyEntity (trying to debug a getter), it returns Namespace\MyEntity rather than Namespace\StatusHistory.
What am I doing wrong?
Turns out I forgot to add the ->join('myEntity.lastestStatus', 'lastestStatus') clause on the query builder...
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.
I'm trying to create a simple form to add companies and i've a trouble using an entity.
I added a select field using a company type entity :
->add('idtypesociete', 'entity', array('class' => 'PromocastUtilisateurBundle:PcastTypesociete', 'property' => 'nomtypesociete'))
But when i submit the form my idtypesociete field contain an 'PcastTypesociete' object and not just the value of the option selected. So the submission fail.
I made a Many-To-One relation between my company entity and my typeCompany entity like this:
/**
* #var integer $idtypesociete
*
* #ORM\Column(name="IDTYPESOCIETE", type="integer", nullable=false)
* #ORM\ManyToOne(targetEntity="Promocast\UtilisateurBundle\Entity\PcastTypesociete")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="PcastTypesociete_idtypesociete", referencedColumnName="idtypesociete")
* })
*/
private $idtypesociete;
Do you have a solution to get only the id of the company type selected? (if possible without made a simple sql request to list my companies types)
Thanks a lot !
If the relationships are working then Symfony 2 usually does a very good job of building the form fields for you.
I think the issue is the $idtypesociete property. Are you expecting to store an integer here on the hydrated entity?
Doctrine associations use Entity relationships. The annotations you supply determine the behind-the-scenes stuff like the join column:
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html#many-to-one-unidirectional
I suggest backing up or committing your work before doing anything else.
Does changing the entity property to the following help?
/**
* #var PcastTypesociete $typesociete
*
* #ORM\Column(name="IDTYPESOCIETE", type="integer", nullable=false)
* #ORM\ManyToOne(targetEntity="Promocast\UtilisateurBundle\Entity\PcastTypesociete")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="PcastTypesociete_idtypesociete", referencedColumnName="idtypesociete")
* })
*/
private $typesociete;
You may need to update your database schema via doctrine:schema:update using the console if it doesn't work properly the first time. Your Entity will also need to be updated to reflect the new property name.
If that works then your form should only need ->add('typesociete') in the form type and you'll have a functioning entity select field because Symfony is clever enough to know what field type to use.