Symfony2 Inserting two entities related using foreign key - symfony

I have a Product entity which have option named synchronization setting which are stored in another table. Product is related with SynchronizationSetting using OneToOne. I've a little problem with persisting new product. My annotations of SynchronizationSetting look like that:
/**
* #var \Webrama\ProductBundle\Entity\Product
*
* #ORM\OneToOne(targetEntity="Webrama\ProductBundle\Entity\Product")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="id_product", referencedColumnName="id")
* })
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $idProduct;
Here is the table structure:
CREATE TABLE IF NOT EXISTS `synchronization_setting` (
`id_product` int(10) unsigned NOT NULL,
`local` char(1) DEFAULT '0',
`internet` char(1) DEFAULT '0',
PRIMARY KEY (`id_product`),
KEY `id_product` (`id_product`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `synchronization_setting`
ADD CONSTRAINT `fk_ss_id_product` FOREIGN KEY (`id_product`) REFERENCES `product` (`id`) ON UPDATE CASCADE;
I'm trying to insert new product with this code:
if($request->getMethod() == 'POST')
{
$form->handleRequest($request);
if($form->isValid())
{
$product = $form->getData();
$synchronizationSetting = $product->retSynchronizationSetting();
$synchronizationSetting->setIdProduct($product);
$em->persist($synchronizationSetting);
$em->flush();
$em->persist($product);
$em->flush();
$this->get('session')->getFlashBag()->add('notice', $this->get('translator')->trans('save_ok'));
return $this->redirect($this->generateUrl("webrama_product_index"));
}
}
The operations fails becouse product does no have related synchronization setting entity at the moment of inserting. The error is:
Entity of type Webrama\ProductBundle\Entity\SynchronizationSetting has identity through a foreign entity Webrama\ProductBundle\Entity\Product, 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 'Webrama\ProductBundle\Entity\SynchronizationSetting'. 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.
What I have to to is:
Create synchronization setting entity with idProduct before I insert the product,
Insert new Product to database
To do that I would make an insert to synchronization setting table before persisting the product but I'm pretty sure the is a better way to do that using Product and SynchronizationSetting entities given me by form. The question is: what way?

I'm afraid the easiest way to go around this will be to just flush product to DB so it gets it's primary key. After that relate synchronization to already flushed product and flush synchronization itself.

Related

Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails - doctrine

My userland code:-
$projects = $this->doctrine->getRepository(Project::class)->findBy(['deletionDate' => new DateTime('today + 364 day')]);
foreach($projects as $project){
$project = $this->entityManager->find('App\Entity\Project', $project->getId());
$this->entityManager->remove($project);
}
$this->entityManager->flush();
Here's the error:
An exception occurred while executing a query: SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails (`foo`.`entry`, CONSTRAINT `FK_2B219D70166D1F9C` FOREIGN KEY (`project_id`) REFERENCES `project` (`id`))
and here's what I'm attempting:-
class Entry
{
/**
* #ORM\ManyToOne(targetEntity=Project::class, inversedBy="entries")
* #ORM\JoinColumn(name="project_id", referencedColumnName="id", onDelete="CASCADE")
*
* #Assert\NotBlank
*/
public ?Project $project;
}
class Project
{
/**
* #ORM\OneToMany(targetEntity="Entry", mappedBy="project", cascade={"remove"})
*/
public Collection $entries;
}
This has nothing to do with Doctrine itself but with the general database rule - you cannot delete rows that other rows in the database depend on.
Now, something that caught my eye was:
#ORM\JoinColumn(name="project_id", referencedColumnName="id", onDelete="CASCADE")
Namely, you have onDelete="CASCADE", however:
`FK_2B219D70166D1F9C` FOREIGN KEY (`project_id`) REFERENCES `project` (`id`))
tells us a whole different story.
It seems that your database and your model are not in sync. Do you manage that by migrations? If so, did you run all of them?
You could try to:
php bin/console doctrine:migrations:diff
which will generate a single migration containing all the differences.
Be careful, inspect detected changes and apply them after making any necessary adjustments.
Update:
Given that you do not manage DB changes via migrations of any sort, the only way would be to execute the ALTER query by hand in order to fix this issue.
Something like this:
ALTER TABLE entry DROP FOREIGN KEY FK_2B219D70166D1F9C;
ALTER TABLE entry ADD FOREIGN KEY FK_2B219D70166D1F9C(project_id)
REFERENCES project(id) ON DELETE CASCADE;

doctrine nested entities cascade persist : how to reuse existing entities

If entity A contains multiple entity B and has cascade:persist, how to reuse existing entities B when persisting ?
B entity has one primary key, an integer, and the id of the A parent. The only data it contains is the primary key.
Example:
A has 2 B entities, identified by their id, 14 and 23.
A.Bs = [{id=14, AId=A.id}, {id=23, AId=A.Id}]
Now if I modify this managed entity, to add a B entity to A, with id = 56.
A.Bs = [{id=14, AId=A.id}, {id=23, AId=A.Id}, {id=56}]
Relationships
Entity A
/**
* #var B[]|ArrayCollection
*
* #ORM\OneToMany(targetEntity="B", mappedBy="A", cascade={"persist", "remove"}, orphanRemoval=true)
* #Assert\Valid
*/
private $Bs;
Entity B
/**
* #var A
*
* #ORM\ManyToOne(targetEntity="A", inversedBy="Bs")
* #ORM\JoinColumn(name="A_id", referencedColumnName="A_id")
* #Assert\NotNull()
*/
private $A;
If I try to persist I get Integrity constraint violation, because Doctrine tries to persist the existing entities, that have id 14 and 23.
I understand this is expected behaviour, but how can I make it persist new entities, and reuse existing ones ?
More details:
If I get an existing entity A with $em->find($id) and directly use persist and flush, I will get UniqueConstraintException because it tries to persist the already persisted B entities.
Example code:
/** #var A $existingEntityA */
$existingEntityA = $this->getEntity($id);
$this->serializerFactory->getComplexEntityDeserializer()->deserialize(json_encode($editedEntityADataJson), A::class, 'json', ['object_to_populate' => $existingEntityA]);
$this->entityValidator->validateEntity($existingEntityA);
$this->_em->flush();
Example error : Integrity constraint violation: 1062 Duplicate entry '777111' for key 'PRIMARY'
If I understand your example properly - you're doing something like this:
$b = new B();
$b->setId(56);
$a->getB()->add($b);
and you having a row with primary key 56 into database table that is represented by B?
If my assumption is correct - it is wrong way to go. Reason is that Doctrine internally stores so called "identity map" that keeps track of all entities that either being fetched from database or persisted by calling EntityManager::persist(). Every entity that is scheduled for commit but not available into identity map is considered as "new" and scheduled for insertion. If row with same primary key is already available in database - you're receiving UniqueConstraintException.
Doctrine doesn't handle a case "let me look if there is an entity with such primary key in database" by itself because it will hurt performance significantly and is not needed in most cases. Each such test will result into database query, imagine if you will have thousands of such entities. Since Doctrine doesn't know business logic of your application - it will spend even more resources with attempts to guess optimal strategy so this is intentionally left out of scope.
Correct way for you would be to get your entity by itself before adding to collection:
$newB = $em->find(B::class, 56);
if ($newB) {
$a->getB()->add($newB);
}
In this case new entity will internally have "managed" status and will be correctly handled by Doctrine at a time of commit.

Doctrine: set foreign relation

This is regarding a problem with Doctrine when I try to insert a record into a associative entity. Below is a simplified description of the problem.
I have two tables, let's call them One and Two. Table One has a foreign key to table Two, called twoId with a column two_id. Field two_id happens to be part of the primary key.
* #ORM\Id
* #ORM\Column(name="user_id", type="string", length=40)
*/
private $twoId;
/**
* #ManyToOne(targetEntity="[...]", inversedBy="[...]", fetch="EAGER")
* #JoinColumn(name="two_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $two;
I am trying to insert a new record into table A. This works:
$two = [.. read from DB ..];
$one = new One();
$one->setTwo($two);
$one->setTwoId($two->getId());
$em->persist($one);
$em->flush();
I don't like to call both setTwo and setTwoId. Furthermore, I don't like reading the $two record before referencing it.
If I skip setTwoId call, I get the error: Entity of type [..] is missing an assigned ID for field 'twoId'. The identifier generation strategy for this entity requires the ID field to be populated before EntityManager#persist() is called.
If I skip setTwo call, I get the error: Integrity constraint violation: 1048 Column 'two_id' cannot be null
My problems are:
How can I avoid calling both setTwo() and setTwoId()?
What if I want to reference a entity from Two without reading it? Should I use $em->getReference()? (PhpStorm doesn't even recognize it)
In case someone makes the same mistake:
As pointed out by #lordrhodos, declaring the field $twoId was wrong because Doctrine will create it automatically without having a definition.
Definition:
/**
* #ManyToOne(targetEntity="[...]", inversedBy="[...]", fetch="EAGER")
* #JoinColumn(name="two_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $two;
Usage:
$two = [.. read from DB ..];
$one = new One();
$one->setTwo($two);
$em->persist($one);
$em->flush();

How to have foreign keys each other on doctorine2

I have two tables named 'Lesson' and 'MutorSche'
MutorSche has columns 'lessonBooked'
Lesson has columns 'booked'
I want to have foreign key by each other.
But I am not sure the meaning of inversedBy and mappedBy.
These are my cords.
Is there anything wrong??
please help me .thanks..
/**
*
* #ORM\OneToMany(targetEntity="Acme\UserBundle\Entity\Lesson", inversedBy="booked*removethis : name of the variable in Lesson.php*")
* #ORM\JoinColumn(name="lessonBooked", referencedColumnName="id")
*/
private $lessonBooked = null;
/**
*
* #ORM\ManyToOne(targetEntity="Acme\UserBundle\Entity\MutorSche", mappedBy="lessonBooked*removethis : name of the variable in MutorSche.php*")
*/
private $booked;
foreign Keys are added on the owning side ( from doctrine's pov - not always what you consider being the owning side ) aka the side using inversedBy.
Which foreign key ( i.e. name="id ) plus the column name ( i.e. referencedColumnName="user_id" ) to add can be configured using the #JoinColumn annotation.
#ManyToOne is always the owning side of the relation.
Logically you can't add all foreign keys to one database entry in a column. those have to be stored on every single one of the "many" related entries.
Doctrine does not add foreign keys on both sides.

Doctrine one-to-many situation: how to easily fetch related entities

To simplify, two entities are defined: User and Comment. User can post many comments and every comment has only one user assigned, thus Comment entity has:
/**
* #var \Frontuser
*
* #ORM\ManyToOne(targetEntity="Frontuser")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="ownerUserID", referencedColumnName="id")
* })
*/
private $owneruserid;
However, when in action:
$orm = $this->getDoctrine()->getManager();
$repo = $orm->getRepository('CompDBBundle:Comment');
$repo->findBy(array('owneruserid' => $uid);
Error occured, that there's no such field like owneruserid.
How can I fetch all the user's comments then? The same happens to similar relations in my DB - looks likes you cannot run find() with foreign keys as parameters. I believe a function $user->getComments() should be automatically generated/recognised by Doctrine to allow efficient, quick access to related entities.
The example's simple but, what if there are more entities related to my User in the same way? Do I have to declare repositories for each and try to fetch them by it's owneruserid foreign keys?
Using doctrine, when you define a related entity it's type is the entity class (in this case FrontUser). Therefore firstly your related entity variable name is misleading. It should be e.g.
private $ownerUser;
Then, in order to do a findBy on a related entity field you must supply an entity instance e.g.
$orm = $this->getDoctrine()->getManager();
$userRepo = $orm->getRepository('CompDBBundle:FrontUser');
$user = $userRepo->findById($uid);
$commentRepo = $orm->getRepository('CompDBBundle:Comment');
$userComments = $commentRepo->findByOwnerUser($user);
If you don't have or want to retrieve the user entity you could use a DQL query with the 'uid' as a parameter instead.

Resources