1 to 1..0 relationship in an embedded form with doctrine - php

One user may have just 1 item or none. (1-1..0 relationship)
I'm trying to accomplish that in symfony2 with doctrine.
I've accomplished an 1 to 1 relationship, it's fairly simple. But how can I specify to doctrine that when I want to create an user, the item can be null? (and not to insert a new row and just leave id_item null)
This is what I have:
// User Class
/**
*
* #ORM\OneToOne(targetEntity="Items", cascade={"persist"})
* #ORM\JoinColumn(name="id_item", referencedColumnName="id", nullable=true)
*
* #var SOA\AXBundle\Entity\Items $userItem
* #Assert\Type(type="SOA\AXBundle\Entity\Items")
*/
protected $userItem;
And of course, I created ItemsTypeForm class, and added the type in my userstypeform class:
// UsersTypeForm Class
->add('userItem', new \SOA\AXBundle\Form\ItemsTypeForm())
When I add a new user, everything goes fine. The user is inserted as well as the item. But when I try to add an user where it has no item (user item fields are blank), I get the following error:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'name' cannot be null
It is trying to insert the item, with null values.
While I can live with an 1 to 1 relationship, I would like to learn how to make an 1 to 1..0 relationship.
edited with the real problem. edits are bold.

The annotation of the Assert is causing a problem. You must set that the null value is valid in the Assert.

Related

How to get the value for next table_id_seq (primary_key) in doctrine?

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

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.

Symfony/Doctrine 2 ManyToOne relationship, ORM does not retain set value for column, error ensues

I'm creating a ManyToOne relationship in table B, to one entry in table A.
I am using a particular key_id parameter, which is set in the controller, before being flushed to the db. My problem is that the SQL created by the ORM nulls the value for this key, for some reason. Before the ManyToOne relationship the same controller had no issues setting and persisting the key_id value.
Here is my annotation on Table B
/**
* #ORM\Column(type="string", length=63)
* #Filter\Trim()
* #Filter\StripNewlines()
*/
private table_b_id
/**
* #ORM\ManyToOne(targetEntity="TableA", inversedBy="table_b_entries", fetch="EAGER")
* #ORM\JoinColumn(name="table_b_id", referencedColumnName="table_a_id")
*/
private $reference_to_table_a;
And Table A
/**
* Constructor
*/
function __construct()
{
$this->table_b_entries = new ArrayCollection();
}
/**
* #ORM\OneToMany(targetEntity="TableB", mappedBy="table_b_id")
*/
private $table_b_entries;
The error that I get is:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'table_b_id' cannot be null
Although I DO set it in the controller before flushing to db, and I have verified the object that I flush that it contains the value, however the SQL does NOT contain this value anymore... I'm not sure where or why it gets lost...
Update
The error described here is because I was not setting the reference object, but trying to set the reference column id manually on Table B. I Should have first fetched the Table A, and set that object on Table B, through the reference setter...
The initial question was answered by: Doctrine 2 category_id always NULL when set ManyToOne relationship
Now I'm dealing with another error:
ContextErrorException: Notice: Undefined index: table_a_id in
/vagrant/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php
line 628
You don't need two fields for ManyToOne relation. This is sufficient:
class B
{
/**
* #ORM\ManyToOne(targetEntity="A", inversedBy="entitiesB")
* #ORM\JoinColumn(name="name_of_column_on_table_b", referencedColumnName="column_from_table_a_to_be_referenced")
*/
private $entityA;
// ...
}
class A
{
/**
* #ORM\OneToMany(targetEntity="B", mappedBy="entityA")
*/
private $entitiesB;
// ...
}
Doctrine will automatically create field called entityA_id on your B table, index it and create foreign key between the two.
When you have instance of entity B, you can retrieve it's A record relation Id by just calling:
$b->getEntityA()->getId()
People commonly think that this will trigger another query, but it will not, it will just read entityA_id from B record, for exaplanation see here

Extending SonataAdmin User with ManyToMany field

I am currently trying to extend the SonataUserBundle's User to add a new ManyToMany relationship with a existing Entity.
I extended the User based on the answer found here - Extending Sonata User Bundle and adding new fields
User.php
/**
* #ORM\ManyToMany(targetEntity="Foo\BarBundle\Entity\Pledge", inversedBy="pledgedUsers")
**/
private $pledgedOn;
// (...) generated getters and setters here
Pledge.php
/**
* #ORM\ManyToMany(targetEntity="Foo\UserBundle\Entity\User", inversedBy="pledgedOn")
**/
private $pledgedUsers;
// (...) generated getters and setters here
The first thing I noticed when I sync the schema, is that it creates 2 pivot tables, instead of just one: pledge_user and user_pledge. I tried adding a #ORM\JoinTable in, but that's just changing the name of one. When I add the same #ORM\JoinTable to both, I get the 'table already exists' error.
When I try to access the user list in admin, I am getting a big sql error
An exception occurred while executing 'SELECT count(DISTINCT u0_.id) AS sclr0 FROM User u0_ LEFT JOIN fos_user_user_group f2_ ON u0_.id = f2_.user_id LEFT JOIN fos_user_group f1_ ON f1_.id = f2_.group_id':
SQLSTATE[42703]: Undefined column: 7 ERROR: column u0_.id does not exist
I am sure this is something simple, but I am smacking my head finding the source of this problem. What did I miss?
Full User.php: http://pastebin.com/TXunsgm1
Full Pledge.php: http://pastebin.com/Mta6aiVm
I knew it was a derpy error. I had
* #ORM\ManyToMany(targetEntity="Foo\UserBundle\Entity\User", inversedBy="pledgedOn")
and
* #ORM\ManyToMany(targetEntity="Foo\BarBundle\Entity\Pledge", inversedBy="pledgedUsers")
One of them had to be mappedBy instead of inversedBy . Changing the second one to
* #ORM\ManyToMany(targetEntity="Foo\BarBundle\Entity\Pledge", mappedBy="pledgedUsers")
fixed the problem.

Categories