Join-Table with metadata, composite key and one to many relationship - symfony

i am building a cart that can take items with specified versions.
I am using Symfony 2.4.3 and Doctrine 2
I have following code for three entities, Cart, CartItem and CartItemVersion.
Cart.php
// ----
/**
* #ORM\OneToMany(targetEntity="CartItem", mappedBy="cart")
*/
private $cartItems;
// ----
CartItem.php
// ----
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="Cart")
*/
private $cart;
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="Item")
*/
private $item;
/**
* #ORM\OneToMany(targetEntity="CartItemVersion", mappedBy="cartItem")
*/
private $cartItemVersions;
// ----
CartItemVersion.php
// ----
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="CartItem")
*/
private $cartItem;
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="ItemVersion")
*/
private $itemVersion;
// ----
While updating schema, i got this error.
[Doctrine\ORM\ORMException]
Column name `id` referenced for relation from CartItemVersion towards CartItem does not exist.
Then i gave name to the fields like cartItem_id in CartItemVersion.php and others.
Then updating schema returns,
[Doctrine\DBAL\DBALException]
An exception occurred while executing 'ALTER TABLE cart_item_version ADD CONSTRAINT FK_4D3EA2E02EA80FC1 FOREIGN KEY (cartItem_id) REFERENCES cart_item (cartItemVersion_id)':
SQLSTATE[HY000]: General error: 1005 Can't create table 'symfony.#sql-3d8_12a' (errno: 150)
I have referred Doctrine 2's documentation and followed Use Cases for OrderItem but it seems that this is something because of composite primary keys, but still giving proper names couldn't solve this issue.
Can anyone help?

In your mapping you have a bunch of issues which are corrected as below. You need define the referenced Column name and its own column name; furthermore, for those field which has mappedBy you need to define inversedBy, too.
Cart.php
/**
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="CartItem", mappedBy="cart")
*/
private $cartItems;
CartItem.php
/**
* #ORM\ManyToOne(targetEntity="Cart", inversedBy="cartItems")
* #ORM\JoinColumn(name="cart_id", referencedColumnName="id")
*/
private $cart;
/**
* #ORM\ManyToOne(targetEntity="Item")
* #ORM\JoinColumn(name="item_id", referencedColumnName="id")
*/
private $item;
/**
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="CartItemVersion", mappedBy="cartItem")
*/
private $cartItemVersions;
CartItemVersion.php
/**
* #ORM\ManyToOne(targetEntity="CartItem", inversedBy="cartItemVersions")
* #ORM\JoinColumn(name="cart_item_id", referencedColumnName="id")
*/
private $cartItem;
/**
* #ORM\ManyToOne(targetEntity="ItemVersion")
* #ORM\JoinColumn(name="item_version_id", referencedColumnName="id")
*/
private $itemVersion;
To get more info check Relationship Mapping Metadata Documentation

I think you have to make your entities slowly, step by step.
Begin with unidirectional and then add bidirectional when it is necessary...
By using association mapping documentation.
This answer could be a comment but can't post a comment because of my reputation...

Related

Foreign key and cascade problems on delete Symfony4

An entity project can have many personnages, many chapitres, one highConcept for each project. And the user can have many projects. Then, when I want to remove a project I have this error message :
An exception occurred while executing 'DELETE FROM projets WHERE id = ?' with params [2]:
SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails (writtle.personnages, CONSTRAINT FK_286738A6C18272 FOREIGN KEY (projet_id) REFERENCES projets (id))
this is my entities:
Personnages Entity
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Chapitre", mappedBy="personnages")
* #ORM\JoinColumn(name="projet_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $chapitres;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Projets", inversedBy="personnages")
* #ORM\JoinColumn(name="projet_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $projet;
Projet entity
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="projets")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Personnages", mappedBy="projet")
*/
private $personnages;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Chapitre", mappedBy="projet", cascade={"remove"})
* #ORM\joinColumn(onDelete="SET NULL")
*/
private $chapitres;
Chapitre entity
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Projets", inversedBy="chapitres")
*/
private $projet;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Personnages", inversedBy="chapitres")
*/
private $personnages;
HighConcept
/**
* #ORM\OneToOne(targetEntity="App\Entity\Projets", cascade={"persist", "remove"})
*/
private $projet;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="highconcepts")
*/
private $user;
User entity
/**
* #ORM\OneToMany(targetEntity="App\Entity\Projets", mappedBy="user")
*/
private $projets;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Highconcept", mappedBy="user")
*/
private $highconcepts;
I don't know how I can relate this, I tried some things like JoinColumn ondelete cascade...
I see some topics, but I think I use it correctly.
Considering that you want to remove all the entities contained in your project I suggest you to use the orphanRemoval option as explained in the Doc.
There is another concept of cascading that is relevant only when removing entities from collections. If an Entity of type A contains references to privately owned Entities B then if the reference from A to B is removed the entity B should also be removed, because it is not used anymore.
making the members of your entity look like:
/**
* #ORM\OneToMany(targetEntity="App\Entity\Personnages", mappedBy="projet", orphanRemoval=true)
*/
private $personnages;

Problem Symfony/Doctrine : One-To-Many - Self-referencing on a primary key

I would like to have a "post" with an identifier. This one could be classified in another "post" by storing the identifier of his parent.
I tried to do like this:
class Post {
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
* #ORM\OneToMany(targetEntity="Post", mappedBy="Id_Post_Parent")
*/
private $Id_Post;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Post", inversedBy="Id_Post")
* #ORM\JoinColumn(name="Id_Post", referencedColumnName="Id_Post", nullable=true)
*/
private $Id_Post_Parent;
...
}
but I have this error when i'm checking with doctrine:schema:validate :
[FAIL] The entity-class App\Entity\Post mapping is invalid:
The association App\Entity\Post#Id_Post_Parent refers to the inverse side field App\Entity\Post#Id_Post which is not defined as association.
The association App\Entity\Post#Id_Post_Parent refers to the inverse side field App\Entity\Post#Id_Post which does not exist.
The referenced column name 'Id_Post' has to be a primary key column on the target entity class 'App\Entity\Post'.
Can someone help me to fix this ?
There is small logical error with your structure - your ID_Post variable tries to be both the primary key (the ID) and the collection association side. I didn't check this syntax in too much details (you can find an example of this association along with most of the other associations from doctrine documentation: https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/association-mapping.html#one-to-many-self-referencing), but basically you need to add the children association separately to your entity like this:
class Post
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Post", inversedBy="postChildren")
* #ORM\JoinColumn(name="id_parent_post", referencedColumnName="id", nullable=true)
*/
private $postParent;
/**
* #ORM\OneToMany(targetEntity="Post", mappedBy="postParent")
*/
private $postChildren;
public function __construct() {
$this->postChildren = new \Doctrine\Common\Collections\ArrayCollection();
}
}

Integrity constraint violation 1451 between entities

I've got these entities (relevant part for the problem) :
/**
* Criterion
*
* #ORM\Table(name="innova_stepcondition_criterion")
* #ORM\Entity(repositoryClass="Innova\PathBundle\Repository\CriterionRepository")
*/
class Criterion implements \JsonSerializable
{
/**
* Criteriagroup
* #var \Innova\PathBundle\Entity\Criteriagroup
*
* #ORM\ManyToOne(targetEntity="Innova\PathBundle\Entity\Criteriagroup", inversedBy="criteria", cascade={"all"})
* #ORM\JoinColumns({
* #ORM\JoinColumn(onDelete="SET NULL")
* })
*/
protected $criteriagroup;
}
and
/**
* Criteriagroup
*
* #ORM\Table(name="innova_stepcondition_criteriagroup")
* #ORM\Entity(repositoryClass="Innova\PathBundle\Repository\CriteriagroupRepository")
*/
class Criteriagroup implements \JsonSerializable
{
/**
* Criteria linked to the criteriagroup
* #var \Doctrine\Common\Collections\ArrayCollection
*
* #ORM\OneToMany(targetEntity="Innova\PathBundle\Entity\Criterion", mappedBy="criteriagroup", indexBy="id", cascade={"persist", "remove"})
*/
protected $criteria;
}
When i try to delete a criteriagroup, i want to delete the attached criterion. I've got this error :
An exception occurred while executing 'DELETE FROM innova_stepcondition_criteriagroup WHERE id = ?' with params [1]: SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails (`claroline_path`.`innova_stepcondition_criteriagroup`, CONSTRAINT `FK_F33A94EA727ACA70` FOREIGN KEY (`parent_id`) REFERENCES `innova_stepcondition_criteriagroup` (`id`))
So i've read some post like this our this one and the suggested solution is the solution is to use a onDelete="SET NULL" on the joinColumn in the ManyToOne side, which i did. But i still get this error.
What could be wrong ?
Thank you
EDIT :
#RaulFerriz : Thank you for the answer. If i try your modifications : with cascade={"persist"} instead of cascade={"all"} in Criterion, i still have the same error.
But if i remove entirely the cascade in Criterion, i have :
A new entity was found through the relationship \u0027Innova\\PathBundle\\Entity\\Criterion#criteriagroup\u0027 that was not configured to cascade persist operations for entity: Innova\\PathBundle\\Entity\\Criteriagroup#0000000034dfa7b200000000f76198d8. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example #ManyToOne(..,cascade={\u0022persist\u0022}). If you cannot find out which entity causes the problem implement \u0027Innova\\PathBundle\\Entity\\Criteriagroup#__toString()\u0027 to get a clue`
that seems to mean the cascade={"persist"} is needed.
I don't knwon what to try next.
I do a doctrine:schema:update after each modification.
Your problem is not in your relation Criteriagroup and Criteria.
If you check the SQL error, the constraint that failed is for the field parent_id. Which is part of your Criteriagroup / Criteriagroup relation.
I put here the definition of your buggy relation, if someone else need this :
/**
* Parent criteriagroup
* #var \Innova\PathBundle\Entity\Criteriagroup
*
* #ORM\ManyToOne(targetEntity="Criteriagroup", inversedBy="children", cascade={"all"})
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id")
*/
protected $parent;
/**
* Children criteriagroup
* #var \Doctrine\Common\Collections\ArrayCollection
*
* #ORM\OneToMany(targetEntity="Criteriagroup", mappedBy="parent", indexBy="id", cascade={"persist", "remove"})
* #ORM\OrderBy({"order" = "ASC"})
*/
protected $children;
In order to make this work, you need to replace :
#ORM\JoinColumn(name="parent_id", referencedColumnName="id")
by
#ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="CASCADE")
By adding this, when you will delete a CriteriaGroup, all children CriteriaGroup will be deleted. If you don't want to delete children, just add onDelete="SET NULL".
Hope it helps.
As you have defined the entities, when you try to remove a "Criterion" the entity Criteriongroup will also be deleted. That is not what you are attempting to achieve.
Try with this
/**
* Criterion
*
* #ORM\Table(name="innova_stepcondition_criterion")
* #ORM\Entity(repositoryClass="Innova\PathBundle\Repository\CriterionRepository")
*/
class Criterion implements \JsonSerializable
{
/**
* Criteriagroup
* #var \Innova\PathBundle\Entity\Criteriagroup
*
* #ORM\ManyToOne(targetEntity="Innova\PathBundle\Entity\Criteriagroup", inversedBy="criteria")
* #ORM\JoinColum(referencedColumnName="id")
*/
protected $criteriagroup;
}
And this
/**
* Criteriagroup
*
* #ORM\Table(name="innova_stepcondition_criteriagroup")
* #ORM\Entity(repositoryClass="Innova\PathBundle\Repository\CriteriagroupRepository")
*/
class Criteriagroup implements \JsonSerializable
{
/**
* Criteria linked to the criteriagroup
* #var \Doctrine\Common\Collections\ArrayCollection
*
* #ORM\OneToMany(targetEntity="Innova\PathBundle\Entity\Criterion", mappedBy="criteriagroup", indexBy="id", cascade={"persist", "remove"})
*/
protected $criteria;
}
Basically, clean up Criterion entity from cascading all, at most I suppose you will need {cascade="persist"} at this entity that means: when Criterion is persisted, persist also Criteriagroup linked with it.

Symfony2 OneToMany relationship - remove action isn't called

I'm using Symfony 2.4.6 and I'm trying to use OneToMany relationship to manage images added to a banner.
I did read a lot about the deletion of a child element (setting orphanRemoval, adding 'remove' to cascade) but none of those worked for me. What I've noticed is that remove actions isn't called at all on update.
I have 2 classes, Banner and BannerFile and using collection field type for adding images and it seems to work OK except the delete action.
class Banner
{
/.../
/**
* #ORM\OneToMany(targetEntity="BannerFile", cascade={"persist", "remove"}, mappedBy="banner", orphanRemoval=true)
*/
private $bannerFiles;
/.../
/**
* Remove bannerFiles
*
* #param BannerFile $bannerFiles
*/
public function removeBannerFile(BannerFile $bannerFiles)
{
$this->bannerFiles->removeElement($bannerFiles);
}
}
class BannerFile
{
/.../
/**
* #var integer $banner
*
* #ORM\ManyToOne(fetch="EXTRA_LAZY", inversedBy="bannerFiles", targetEntity="Banner")
* #ORM\JoinColumn(name="banner_id", nullable=false, onDelete="CASCADE", referencedColumnName="id")
*/
private $banner;
/.../
}
My problem is that the removeBannerFile isn't called.
Thanks for any help.
This happens if you use a database table engine type that does not support foreign keys (such as MyISAM).
Change your engine type to InnoDB, then run the following code to update your database schema.
php console doctrine:schema:update
Add cascade to the ManyToOne definition:
class BannerFile
{
/.../
/**
* #var integer $banner
*
* #ORM\ManyToOne(fetch="EXTRA_LAZY", inversedBy="bannerFiles", targetEntity="Banner", cascade={"remove"})
* #ORM\JoinColumn(name="banner_id", nullable=false, onDelete="CASCADE", referencedColumnName="id")
*/
private $banner;
/.../
}

Entity containing other entities without having a table

I have an Entity ( Invoice ) which is purely for calculation purposes and has no table, that associates with two other entities having relations by tables. (Although there are so many other entities involved ).
class Row{
/**
* #var integer
*
* #ORM\Column(name="row_id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="File")
* #ORM\JoinColumn(name="file_id", referencedColumnName="file_id")
*/
protected $file;
/**
* #var \DateTime
*
* #ORM\Column(name="date", type="date")
*/
private $date;
}
class File
{
/**
* #var integer
*
* #ORM\Column(name="file_id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
}
class Invoice
{
/**
* #ORM\Id
* #ORM\Column(name="invoice_id", type="integer")
* #ORM\GeneratedValue
*/
protected $id = null;
/**
* #ORM\OneToMany(targetEntity="Row", mappedBy="row_id")
*/
protected $row;
/**
* #ORM\OneToMany(targetEntity="File", mappedBy="file_id")
*/
protected $file;
}
I want to be able to query for Invoices :
$sDate = //Some date
$this->getEntityManager()
->createQuery("SELECT Invoice, Row, File
FROM
ReportsEntitiesBundle:Invoice Invoice
LEFT JOIN
Row.row Row
LEFT JOIN
Row.file File
WHERE date=:date"
)
->setParaMeter(':date', $sDate)
->setFirstResult($iPage*$iLimit)
->setMaxResults($iLimit)
->getResult();
The questions :
# Doctrine tries to query the database, how can I prevent that and have it find the relevant entities?
# How can I relate the date ( which is in Row entity and cannot be in Invoice ) to the query?
Later this Invoice will become a part of another big entity for calculating/search purposes.
Thank you
Short Answer: You can't
Long Answer : You can't because an entity with #ORM annotations means its persisted to a database - querying that entity relates to querying a database table. Why not just create the table ?!?!?
You need somewhere to persist the association between file and row - a database table is a perfect place !!!!
Update
Just to clarify ... an Entity is just a standard class - it has properties and methods ... just like any other class - When you issue doctrine based commands it uses the annotations within the entities to configure the tables / columns / relationships etc if remove those you can use it however you like ... but you will need to populate the values to use it and you wont be able to use it in a Doctrine query and it obviously wont be persisted !
You can use a read-only entity. It's contents are backed by a view which you create manually in SQL.
PHP:
/** #ORM\Entity(readOnly =true) */
class InvoiceView
{ ...
SQL:
CREATE VIEW invoice_view AS (
SELECT ...

Resources