I'm using a foreign table to join 2 entities, exactly this way :
http://www.prowebdev.us/2012/07/symfnoy2-many-to-many-relation-with.html
I'd like to understand why do we need an ID as primary key in the foreign table ?
I'd rather take the couple of foreign keys as the primary keys, this way I make sure that there is no double entries for the same relation. no ?
I think Doctrine just wants every table to have and Id field, you can however force Doctrine the check if the combination of foreign keys is unique:
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
...
/*
* #UniqueEntity({"foreign_key1", "foreign_key2"})
* ...
*/
class JoinTable
{
...
}
Related
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.
From what I've gathered, Symfony 2 / Doctrine uses the database definitions (foreign key constraints in my case) to map relations between entities. I have two tables in particular that I want to be able to relate from both sides, but I do not want to create redundant foreign keys in each table. In this case, I have an Account table, and a Transaction table.
Account Table
CREATE TABLE "account" (
"account_id" BIGSERIAL NOT NULL,
"name" VARCHAR (100) NOT NULL,
"date_created" TIMESTAMP (6) WITH TIME ZONE NOT NULL,
"date_modified" TIMESTAMP (6) WITH TIME ZONE,
CONSTRAINT "pk-account-account_id"
PRIMARY KEY ("account_id"),
);
Transaction Table
CREATE TABLE "transaction" (
"transaction_id" BIGSERIAL NOT NULL,
"account_id" BIGINT NOT NULL,
"amount" MONEY NOT NULL,
"date_created" TIMESTAMP (6) WITH TIME ZONE NOT NULL,
"date_modified" TIMESTAMP (6) WITH TIME ZONE,
CONSTRAINT "pk-transaction-transaction_id"
PRIMARY KEY ("transaction_id"),
CONSTRAINT "fk-transaction-account_id-account-account_id"
FOREIGN KEY ("account_id")
REFERENCES "account" ("account_id")
ON DELETE RESTRICT
ON UPDATE CASCADE,
);
When I generate the entities using php bin/console doctrine:generate:entities I see that the transaction entity has an $account property, but my account entity does not have a $transaction entity. I assume this is because I do not define a foreign key constraint in my account table.
In my code, I create my account object by the following:
$accounts = $this->getDoctrine()
->getRepository('BalancesBundle:Account')
->findAll();
I then would want to iterate over the array to get the total balance for each account. In the long-term, I'd like to create a helper method inside my account entity that would call getTransactions() to add up all of the transactions into one sum total.
Is this possible? I feel like I'm missing something, and that my only recourse would be to do this from within the transaction entity. I would like to avoid doing this from the transaction entity if possible.
From what I get, you can't get transaction entities of the account entity. The weird thing is that you don't have "transactions" property inside your ccount entity, am I right ? Seems like a bad mapping.
Take a look at Doctrine documentation, you have to define your "transcations" property inside your "account" entity and map it with the OneToMany association.
Then you can use "php bin/console do:ge:entities AppBundle:Account"
If your entities are setup correctly then you should be able to access the transactions from the account.
foreach ($accounts as $account) {
$transactions = $account->getTransactions();
$transaction_total = 0;
foreach($transactions as $transaction) {
$transaction_total += $transaction->getAmount();
}
echo 'Transactions Total: '.$transaction_total;
}
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.
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.
I'm wondering how to avoid having a circular reference in my symfony2.1 application.
I have an entity like
customer (
name
addresses -- OneToMany
currentAddress -- OneToOne )
and
address (
street
customer -- ManyToOne )
Now my fixtures won't load because it can't delete the customer because of the foreign key.
For performances' sake I would like to avoid having to add a getCurrentAddress() method on customer which would select in the addresses table.
Does anybody have a solution for that?
Adding a getCurrentAddress() isn't such a performance issue.
This way, I'll avoid the circular reference and all the issues that come along with it.
In my situation, using an order by date in the doctrine annotation was enough:
// on customer entity :
/** #ORM\OrderBy({"datemodified" = "DESC"}) */
private $addresses
public function getCurrentAddress()
{
return $this->addresses[0];
}