Set PK from ManyToMany annotations in Doctrine2 entity - php

I've a ManyToMany relationship between Pais and FabricanteDistribuidor tables defined as follow:
Pais.php
class Pais
{
// column definitions
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\FabricanteDistribuidor", inversedBy="paises", cascade={"persist"})
* #ORM\JoinTable(name="negocio.fabricante_distribuidor_pais", schema="negocio",
* joinColumns={#ORM\JoinColumn(name="fabricante_distribuidor_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="pais_id", referencedColumnName="id")}
* )
*/
protected $fabricanteDistribuidor;
/**
* Add fabricanteDistribuidor
*
* #param AppBundle\Entity\FabricanteDistribuidor $fabricanteDistribuidor
*/
public function addfabricanteDistribuidor(\AppBundle\Entity\FabricanteDistribuidor $fabricanteDistribuidor)
{
$this->fabricanteDistribuidor[] = $fabricanteDistribuidor;
}
/**
* Get fabricanteDistribuidor
*
* #return Doctrine\Common\Collections\Collection
*/
public function getfabricanteDistribuidor()
{
return $this->fabricanteDistribuidor;
}
}
FabricanteDistribuidor.php
class FabricanteDistribuidor
{
// column definitions
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Pais", mappedBy="fabricanteDistribuidor", cascade={"persist"})
*/
protected $paises;
public function __construct()
{
$this->paises = new ArrayCollection();
}
/**
* Set paises
*
* #param AppBundle\Entity\Pais $pais
* #return FabricanteDistribuidor
*/
public function addPaises(\AppBundle\Entity\Pais $pais)
{
$this->paises[] = $pais;
return $this;
}
/**
* Get paises
*
* #return Doctrine\Common\Collections\Collection
*/
public function getPaises()
{
return $this->paises;
}
}
That will generate a table fabricante_distribuidor_pais on the schema negocio with fabricante_distribuidor_id and pais_id FK pointing to the PK on the related tables, that's fine.
Regarding this scenario:
1- It's possible to define fabricante_distribuidor_id and pais_id as PK on the fabricante_distribuidor_pais table? I mean adding some extra annotation or I need to create a external entity and set them as #ORM\Id on the column definition?
2- Are the addXXX and getXXX methods right in my entities? By right I mean: I should add one or many paises (from Pais entity) to FabricanteDistribuidor easily and I don't care about to the inverse relation meaning I will not add FabricanteDistribuidor from a Pais, are them right or do I need to change something?

1- If one id is a primary key doesn't the relation becomes many to one/ one to many ? Even 1to1 if both are PK
2- If you don't care about the inverse you are going to add getters and setters in only one entity yes. You can still change it to a biredictionnal later with the attribute "mappedBy"
Check if an entity exists :
You can do that in your controller :
for example in Pays
$data = $em->getRepository('AcmeBundle:Pais')->findOneByFabricanteDistribuidor($id);
if($data)
{
// the entity is allready persisted
}
else
{
// no, we can persist the entity
}

Related

Symfony Doctrine2 manyToMany relationship not removed - Specific to SQLite

I have several classes using a Taggable trait to set up a tag system common to several doctrine entities (Project, Note, ...).
The relationship between these entities and these tags is a ManyToMany relationship that I can not make multi-directional.
My problem: When I delete a Project entity, it is removed from the project table, but the relationships in the project_tag table between this project and the tags are not deleted. Then, if I create a new Project entity, an exception is thrown.
An exception exists while executing 'INSERT INTO project_tag (project_id, tag_id) VALUES (?,?)' With params [2, 4]:
SQLSTATE [23000]: Integrity constraint violation: 19 UNIQUE constraint failed: project_tag.project_id, project_tag.tag_id
Entities :
Tag
/**
* Tag
*
* #ORM\Table(name="tag")
* #ORM\Entity(repositoryClass="AppBundle\Repository\TagRepository")
*/
class Tag
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255, unique=true)
*/
private $name;
/**
* #ORM\Column(name="last_use_at", type="datetime", nullable=false)
* #var \DateTime
*/
private $lastUseAt;
public function __construct()
{
$this->lastUseAt = new \DateTime();
}
public function __toString()
{
return $this->name;
}
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Tag
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName(): string
{
return $this->name;
}
/**
* #return \DateTime
*/
public function getLastUseAt(): \DateTime
{
return $this->lastUseAt;
}
/**
* #param \DateTime $lastUseAt
*/
public function setLastUseAt(\DateTime $lastUseAt)
{
$this->lastUseAt = $lastUseAt;
}
}
Taggable
trait Taggable
{
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Tag", cascade={"persist"})
*/
protected $tags;
/**
* Add tag
*
* #param Tag $tag
*
* #return $this
*/
public function addTag(Tag $tag)
{
$tag->setLastUseAt(new \DateTime());
$this->tags[] = $tag;
return $this;
}
/**
* Remove tag
*
* #param Tag $tag
*/
public function removeTag(Tag $tag)
{
$this->tags->removeElement($tag);
}
/**
* Get tags
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getTags()
{
return $this->tags;
}
}
Project
/**
* Project
*
* #ORM\Table(name="project")
* #ORM\Entity(repositoryClass="AppBundle\Repository\ProjectRepository")
*/
class Project
{
use Taggable;
}
Note
class Note
{
use Taggable;
}
Is this the only solution or is my annotation incomplete / incorrect?
I tried with JoinColumns, JoinTable and onDelete = "cascade" but nothing works.
In the meantime, I dodged the problem with this instruction placed before the suppresion.
$project->getTags()->clear();
Full code of the action in the controller :
/**
* #Route("/project/{id}/delete", name="project_delete")
*/
public function deleteAction($id) {
$em = $this->getDoctrine()->getManager();
$project = $em->getRepository('AppBundle:Project')->find($id);
if(!$project) {
return $this->redirectToRoute('index');
}
$project->getTags()->clear();
$em->remove($project);
$em->flush();
return $this->redirectToRoute('index');
}
I think I found a better solution: you can set the PRAGMA within Doctrine configuration. Like:
doctrine:
dbal:
# configure these for your database server
driver: 'pdo_sqlite'
#server_version: '5.7'
#charset: utf8mb4
#default_table_options:
#charset: utf8mb4
#collate: utf8mb4_unicode_ci
url: '%env(resolve:DATABASE_URL)%'
options:
'PRAGMA foreign_keys': 'ON'
I just tried it on my Symfony 4 application, re-created the database and tested using DB Browser for SQLite and it works as I expected.
Hope this helps
I managed to fix the problem. Here's my solution working for SQLite conections.
Create an eventListener listening on the kernel.request event :
namespace AppBundle\EventListener;
use Doctrine\Bundle\DoctrineBundle\Registry;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
class RequestListener
{
/**
* #var Registry
*/
private $doctrine;
public function __construct(Registry $doctrine)
{
$this->doctrine = $doctrine;
}
public function onKernelRequest(GetResponseEvent $event)
{
$this->doctrine->getConnection()->exec('PRAGMA foreign_keys = ON');
}
}
Service declaration
app.event_listener.request_listener:
class: AppBundle\EventListener\RequestListener
arguments:
- '#doctrine'
tags:
- { name: kernel.event_listener, event: kernel.request }
I think the problem is that you have your trait Taggable set as the owning side of the ManyToMany relationship but your are deleting the inverse side and expecting something to happen as a result. Doctrine will only check the owning side of the relationship in order to persist any changes. See here for docs on this.
You can solve by making the Taggable the inverse side of each of your relationships, or by manually telling doctrine to delete the owning side.
The first solution will probably not work for you since you won't (easily) specify multiple inverse sides. (Are you sure a trait is the right way to go for this??)
The second solution is easy. In your entities like Project for your deleteTag($tag) function, call a delete function on the owning side (e.g., deleteProject($project). You will have to create if one does not exist.
class Project
{
use Taggable;
public function deleteTag($tag)
{
$this->tags->removeElement($tag);
// persist on the owning side
$tag->deleteProject($this);
}
}
EDIT:
After seeing full code, it looks like you are deleting correctly. Now you need to tell doctrine to carry that through. See this post for full details, but basically you can change your trait to this:
trait Taggable
{
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(
* targetEntity="AppBundle\Entity\Tag",
* cascade={"persist"},
* onDelete="CASCADE"
* )
*/
protected $tags;
// ...
}

Doctrine trying to persist Owner

I've setup Doctrine and Symfony-forms independent of the Symfony Framework (as I don't need most of it).
The issue I'm having is, when trying to persist a new "Audit" which has an "Type" doctrine seems to want to persist the owning side of the relationship (Type).
For example as Audit may have a type of Vehicle Service.
// -- Model/Audit.php --
/**
* #var \Model\Type
*
* #ORM\ManyToOne(targetEntity="Model\Audit\Type", inversedBy="audits")
* #ORM\JoinColumn(name="type_id", referencedColumnName="id", nullable=true)
*/
private $type;
/**
* Set type
*
* #param \Model\Type $type
* #return Audit
*/
public function setType(\Model\Type $type)
{
$this->type = $type;
return $this;
}
And then in the inverse side:
/**
* #ORM\OneToMany(targetEntity="Model\Audit", mappedBy="type")
* #var type */
private $audits;
public function __construct() {
$this->audits = new \Doctrine\Common\Collections\ArrayCollection();
}
Persistance code looks as follows:
$data = $form->getData();
$entityManager->persist($data);
$entityManager->flush();
And finally the form class is:
class AuditType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('name')
->add('type', 'entity', array(
'class' => "Model\Type"
));
}
All looks (to me at least) exactly the same as in all the documentations both Doctrine and Symfony sides but I'm getting this error:
A new entity was found through the relationship 'Model\Audit#type'
that was not configured to cascade persist operations for entity:
Vehicle Service. 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={"persist"})."
Which is really frustrating as I don't want to persist the Type side, I just want to put (in most basic terms) the id of 3 into the type_id column. Yet Doctrine seems to think I want to create a new "Type" which I certainly do not. They already exist.
Using $entityManager->merge($audit); works in part, it allows the inital Audit and its FK's to be saved. However it caused any embedded forms to become ignored.
I think you need set
/**
* #ORM\OneToMany(targetEntity="Model\Audit", mappedBy="type")
* #var type
*/
private $audits;
public function __construct() {
$this->audits = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* #return ArrayCollection
*/
public function getAudits()
{
return $this->audits;
}
/**
* #param Audit $audit
*/
public function addAudits(Audit $audit)
{
$this->audits->add($audit);
$audit->setTyoe($this);
}
and in Type Audit.model
// -- Model/Audit.php --
/**
* #var \Model\Type
*
* #ORM\ManyToOne(targetEntity="Model\Audit\Type", inversedBy="audits")
* #ORM\JoinColumn(name="type_id", referencedColumnName="id", nullable=true)
*/
private $type;
/**
* Set type
*
* #param \Model\Type $type
* #return Audit
*/
public function setType(\Model\Type $type)
{
$this->type = $type;
}

Failed to set up a ManyToMany

I want to be able to select a school (that has its own entity) while creating a mission (also has its entity)
Since a school can have several missions, and you can select several schools at the mission's creation, I used a ManyToMany.
The problem is that after creating this "ManyToMany", generating the entities and updating my schema, Symfony created a table, but left it totally empty, without the two columns that I asked for. I'm not really used to Symfony nor to the ManyToMany system, so I might have done some mistake without noticing it, still I find this weird.
Here's the interesting part of my ecole (school) entity:
class Ecole{
// ...
/**
* #ORM\ManyToMany(targetEntity="MissionBundle\Entity\Mission", mappedBy="ecolesDispo")
*/
protected $missionsDispos;
// ...
/**
* Add missionsDispo
*
* #param \MissionBundle\Entity\Mission $missionsDispo
*
* #return Ecole
*/
public function addMissionsDispo(\MissionBundle\Entity\Mission $missionsDispo)
{
$this->missionsDispos[] = $missionsDispo;
return $this;
}
/**
* Remove missionsDispo
*
* #param \MissionBundle\Entity\Mission $missionsDispo
*/
public function removeMissionsDispo(\MissionBundle\Entity\Mission $missionsDispo)
{
$this->missionsDispos->removeElement($missionsDispo);
}
/**
* Get missionsDispos
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getMissionsDispos()
{
return $this->missionsDispos;
}
And here is the interesting part of my mission entity:
/**
* #ORM\ManyToMany(targetEntity="EcoleBundle\Entity\Ecole", inversedBy="missionsDispo")
* #ORM\JoinTable(name="Mission2Ecole",
* joinColumns={#ORM\JoinColumn(name="em_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="me_id", referencedColumnName="id")}
* )
*/
protected $ecolesDispo;
// ...
/**
* Constructor
*/
public function __construct()
{
$this->ecolesDispo = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add ecolesDispo
*
* #param \EcoleBundle\Entity\Ecole $ecolesDispo
*
* #return Mission
*/
public function addEcolesDispo(\EcoleBundle\Entity\Ecole $ecolesDispo)
{
$this->ecolesDispo[] = $ecolesDispo;
return $this;
}
/**
* Remove ecolesDispo
*
* #param \EcoleBundle\Entity\Ecole $ecolesDispo
*/
public function removeEcolesDispo(\EcoleBundle\Entity\Ecole $ecolesDispo)
{
$this->ecolesDispo->removeElement($ecolesDispo);
}
/**
* Get ecolesDispo
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getEcolesDispo()
{
return $this->ecolesDispo;
}
After all this was created, I was supposed to get a multi selector with the list of all the schools saved in the database (I already added it to the missionType file), but I get absolutely nothing.
I don't really know if I inverted the annotations, or if the "joinTable" part is correct, but I'm completely lost here.
Does anyone have an idea?
Thank you in advance
Just wrong typo "s"? inversedBy="missionsDispo" >>> inversedBy="missionsDispos"
PS. Official doc here
http://doctrine-orm.readthedocs.io/projects/doctrine-orm/en/latest/reference/association-mapping.html#many-to-many-bidirectional

Many to Many relation between article and quote symfony

I have a manytomany relation between two entities (product and quote) so that one or more products could be in one or more quotes. For example:
assuming that a customer chooses two products from a specific enterprise and he would like to receive a quote that summarize all the chosen products. Then he decides to choose another product of another enterprise to get at the end another quote. So here we have two quotes of different enterprises and of same user that each quote has its own products.
you will say that it is a onetomany relation between the quote and the product because as I mentioned above that one quote can have many products so in the database you will have a quote_id column in the product table.
However, if another customer chooses the same products, a new quote will be created but when the query will insert these products in the quote by filling the quote_id column of the product table, it finds out that these articles are already have a quote_id of another quote.
That's why it is a manytomany relation so that many articles could be in many quotes.
This part is realized and I can match many products to many quotes without any problems.
Here is the entity of quote:
/**
* #ORM\ManyToMany(targetEntity="ArticleBundle\Entity\Article", mappedBy="quotes")
*/
private $articles;
public function __construct() {
$this->articles = new \Doctrine\Common\Collections\ArrayCollection();
$this->createdAt = new \DateTime();
}
/**
* Set articles
*
* #param \ArticleBundle\Entity\Article $articles
* #return Quote
*/
public function setArticles(\ArticleBundle\Entity\Article $articles = null) {
$this->articles = $articles;
return $this;
}
/**
* Get articles
*
* #return \ArticleBundle\Entity\Article
*/
public function getArticles() {
return $this->articles;
}
/**
* Add articles
*
* #param \QuoteBundle\Entity\Quote$articles
* #return Devis
*/
public function addArticle(\ArticleBundle\Entity\Article $article) {
$article->addQuote($this); // synchronously updating inverse side
$this->articles[] = $article;
return $this;
}
/**
* Remove articles
*
* #param \QuoteBundle\Entity\Quote $articles
*/
public function removeArticle(\ArticleBundle\Entity\Article $article) {
$this->articles->removeElement($article);
}
}
the entity article:
/**
* #ORM\ManyToMany(targetEntity="QuoteBundle\Entity\Quote", inversedBy="articles")
* #ORM\JoinTable(name="quotes_articles")
*/
private $quotes;
/**
* Add devises
*
* #param \DevisBundle\Entity\Quote $quote
* #return Article
*/
public function addQuote(\QuoteBundle\Entity\Quote $quote) {
$this->quotes[] = $quote;
return $this;
}
/**
* Remove quotes
*
* #param \QuoteBundle\Entity\Quote $quote
*/
public function removeQuote(\QuoteBundle\Entity\Quote $quote) {
$this->quotes->removeElement($quote);
}
/**
* Get quotes
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getQuotes() {
return $this->quotes;
}
}
The part that I find it difficult to reach is that when a customer chooses only one product , I would like to let him specify the quantity of the chosen product.
so when I updated the doctrine schema I got these tables :
quote table
product table
quotes_products table
can anyone tell me in which table I must add the quantity column and how can I write the annotation to let it added automatically in the database . I think it will be added to the quotes_products table.
Thank you for helping me.
when adding a quantity column to the join table, you are making of your relationship a non pure manytomany. So, there will be no problem in separating the manyToMany relationships to two oneToMany relationships, and create an Entity Class for the join with additional field quantity.
The idea in pseudo code:
Class Quote
{
private $products;
/**
* #ORM\OneToMany(targetEntity="Product", mappedBy="quote")
*/
}
Product:
Class Product
{
private $quotes;
/**
* #ORM\OneToMany(targetEntity="Quote", mappedBy="product")
*/
}
ProdcutQuote:
Class ProductQuote
{
/**
* #ORM\ManyToOne(targetEntity="Quote")
* #ORM\JoinColumn(name="quote_id", referencedColumnName="id")
*/
private $quote;
/**
* #ORM\ManyToOne(targetEntity="Product")
* #ORM\JoinColumn(name="product_id", referencedColumnName="id")
*/
private $product;
private $quantity;
}

Inconsistent Database Relation in Symfony2/Doctrine

I have a GAME table and a SPELL table.
In my game table, I have two spells from the spell table.
//SPELL TABLE
/**
* #ORM\OneToMany(targetEntity="Game", mappedBy="spell")
*/
protected $game;
public function __construct()
{
$this->game = new ArrayCollection();
}
//GAME TABLE
/**
* #ORM\ManyToOne(targetEntity="Spell", inversedBy="game")
* #ORM\JoinColumn(name="spell1", referencedColumnName="id")
*/
protected $spell1;
/**
* #ORM\ManyToOne(targetEntity="Spell", inversedBy="game")
* #ORM\JoinColumn(name="spell2", referencedColumnName="id")
*/
protected $spell2;
In my Symfony2 Profiler I get these messages.
"AppBundle\Entity\Game":
The mappings AppBundle\Entity\Game#spell1 and AppBundle\Entity\Spell#game are inconsistent with each other.
The mappings AppBundle\Entity\Game#spell2 and AppBundle\Entity\Spell#game are inconsistent with each other.
Are these the wrong relations?

Categories