I have two tables (test and question) and the middle table (n-m). In this point all works fine.
But now, I need to put extra information in the (n-m) table, the order of this question in this test
I need this:
id | test_id | question_id | order
1 | 1 | 1 | 3
2 | 1 | 2 | 2
3 | 1 | 3 | 1
4 | 1 | 4 | 4
All these relationship have made with doctrine annotation...
Test Entity
/**
* #ORM\ManyToMany(targetEntity="Question", inversedBy="tests")
*/
private $questions;
Question Entity
/**
* #ORM\ManyToMany(targetEntity="Test", mappedBy="questions"))
*/
private $tests;
Any help will be appreciated
EDIT
Hi again!
Thanks a lot to #DonCallisto
my entities at the end:
Test
/**
* #ORM\OneToMany(targetEntity="RTestQuestion", mappedBy="question")
*/
private $questions;
Question
/**
* #ORM\OneToMany(targetEntity="RTestQuestion", mappedBy="test"))
*/
private $tests;
My new entity "RTestQuestion"
/**
* ET\BackendBundle\Entity\RTestQuestion
*
* #ORM\Table(name="rtest_question")
* #ORM\Entity
*/
class RTestQuestion {
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Question", inversedBy="questions", cascade={"persist", "remove"})
*/
private $question;
/**
* #ORM\ManyToOne(targetEntity="Test", inversedBy="tests", cascade={"persist", "remove"})
*/
private $test;
/**
* #var integer $order
*
* #ORM\Column(name="question_order", type="integer", nullable=true)
*/
private $question_order;
I had to make two changes:
The properties need the cascade on persist and remove actions (doctrine console show errors without this)
And the word "order" are restricted for mysql, and now become a question_order.
And, again, thanks to #DonCallisto!
Split the relationship into a 1-n and m-1 as follows
Test Entity --- (1 - m) ---> RTestQuestion Entity <--- (m - 1) --- Question
So your code will be
Test Entity
/**
* #ORM\OneToMany(targetEntity="RTestQuestion", inversedBy="question")
*/
private $questions;
Question Entity
/**
* #ORM\OneToMany(targetEntity="RTestQuestion", mappedBy="test"))
*/
private $tests;
RTestQuestion Entity
/**
* #ORM\ManyToOne(targetEntity="Question", mappedBy="questions"))
*/
private $question;
/**
* #ORM\ManyToOne(targetEntity="Test", mappedBy="tests"))
*/
private $test;
/**
* EXTRA ATTRIBUTES HERE
*/
Remember that an association with extra fields isn't an association anymore but a new entity!
Related
In my work, I am trying to map the legacy database scheme with Doctrine. I can't change this scheme, because it is used by other company applications. Here is a brief scheme overview:
Table - global_register_item
ID | NAME | DTYPE
1 | "global register item with article #1" | law_regulation
2 | "global register item without article #1" | financial_reporter
3 | "global register item without article #2" | law_regulation
4 | "global register item without article #3" | law_regulation
5 | "global register item with article #2" | financial_reporter
Table - article
ID | SID | other fields which I actually do not need
1 | 89 | ...
5 | 45 | ...
Table - law_regulation
ID | other fields
1 | ...
3 | ...
4 | ...
Table - financial_reporter
ID | other fields
2 | ...
5 | ...
So global_register_item is a parent and law_regulation and financial_reporter inherit from this table. To solve this I used class table inheritance and it works fine.
The problem is a relation between global_register_item and article. It is one-to-one relation and joining is done via their ID columns (if there is a record in global_register_item with relation to article, there is a record in article table with the same ID). But some records in global_register_item doesn't have record in article. Is there any way how to map this relation with Doctrine?
EDIT 1
Here is my PHP code from the project. Btw. I only need to read records. And I need to get information about SID column to my GlobalRegisterItem entity.
Class GlobalRegisterItem
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="global_register_item")
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="DTYPE", type="string")
* #ORM\DiscriminatorMap({
* "law_regulation" = "App\Entity\LawRegulation",
* "financial_reporter" = "App\Entity\FinancialReporter"})
*/
abstract class GlobalRegisterItem
{
/**
* #var int
* #ORM\Column(name="id", type="integer")
* #ORM\Id
*/
private $id;
/**
* #var string
* #ORM\Column(name="name", type="string")
*/
private $name;
/**
* Article|null
* HOW TO MAP THIS?
*/
private $article;
}
Class Article
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="article")
*/
class Article
{
/**
* #var int
* #ORM\Column(name="id", type="integer")
* #ORM\Id
*/
private $id;
/**
* #var int
* #ORM\Column(name="SID", type="int")
*/
private $sid;
}
Class LawRegulation
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="law_regulation")
*/
class LawRegulation extends GlobalRegisterItem
{
/** SOME MAPPED FIELDS */
}
Class FinancialReporter
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="law_regulation")
*/
class FinancialReporter extends GlobalRegisterItem
{
/** SOME MAPPED FIELDS */
}
You can use a OneToOne bidirectional relation, in the Article entity:
/**
* #ORM\OneToOne(targetEntity="GlobalRegisterItem", inversedBy="article")
* #ORM\JoinColumn(name="id", referencedColumnName="id", nullable=true)
*/
private $item;
And reference it in your GlobalRegisterItem class:
/**
* Article|null
* #ORM\OneToOne(targetEntity="Article", mappedBy="item")
*/
private $article;
/**
* Gets the sid of the article (if any).
* #returns int|null
*/
public function getArticleSid()
{
if (null !== $this->article) {
return $this->article->getSid();
}
return null;
}
One possible approach how to solve this (if you just need to read data) is:
Create view from global_register_item and article
SELECT global_register_item.*, article.SID AS `SID`
FROM global_register_item
LEFT JOIN article ON global_register_item.id = article.id
ID | NAME | DTYPE | SID
1 | "global register item with article #1" | law_regulation | 89
2 | "global register item without article #1" | financial_reporter | NULL
3 | "global register item without article #2" | law_regulation | NULL
4 | "global register item without article #3" | law_regulation | NULL
5 | "global register item with article #2" | financial_reporter | 45
Class GlobalRegisterItem
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="global_register_item_view")
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="DTYPE", type="string")
* #ORM\DiscriminatorMap({
* "law_regulation" = "App\Entity\LawRegulation",
* "financial_reporter" = "App\Entity\FinancialReporter"})
*/
abstract class GlobalRegisterItem
{
/**
* #var int
* #ORM\Column(name="id", type="integer")
* #ORM\Id
*/
private $id;
/**
* #var string
* #ORM\Column(name="name", type="string")
*/
private $name;
/**
* int|null
* #ORM\Column(name="SID", type="integer")
*/
private $sid;
}
I think this is far from being optimal, but this is the best solution I could come up with.
I've implement Class Table Inheritance using Doctrine 2 in my Symfony 3 project, so as to have one base profile table, that houses both employee and company profiles.
When trying to persist a sub class (EmployeeProfile) of the mapped super class (AbstractProfile), I get the following error:
An exception occurred while executing 'INSERT INTO profile (id) VALUES
(?)' with params [27, 10, 85, \"employee\"]:\n\nSQLSTATE[HY093]:
Invalid parameter number: number of bound variables does not match
number of tokens
I'm not entirely sure what's going wrong, and why Doctrine is generating a query that's entirely ignoring the AbstractProfile's properties. Initially I thought it was due to said properties not being visible to the children, but even after setting the properties to protected, the error remains.
How exactly can I fix this, or am I trying to fit a square peg into a round hole by not using this functionality for what is was intended?
profile DB Table:
+------------+--------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+-------------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| user_id | int(11) | NO | | NULL | |
| type | varchar(100) | NO | | NULL | |
| status | int(11) | NO | | NULL | |
| created_at | datetime | NO | | CURRENT_TIMESTAMP | |
| updated_at | datetime | NO | | CURRENT_TIMESTAMP | |
+------------+--------------+------+-----+-------------------+----------------+
AbstractProfile Super Class:
/**
* AbstractProfile
*
* #ORM\Table(name="profile")
* #ORM\Entity(repositoryClass="ProfileBundle\Repository\ProfileRepository")
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="type", type="string")
* #ORM\DiscriminatorMap({
* "employee" = "EmployeeProfile",
* "company" = "CompanyProfile"
* })
*/
abstract class AbstractProfile
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var int
*
* #ORM\Column(name="status", type="integer")
*/
protected $status;
/**
* #ORM\OneToOne(targetEntity="User", inversedBy="profile")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
//... Getters, setters and all the rest
}
EmployeeProfile Sub Entity:
<?php
/**
* EmployeeProfile
*
* #ORM\Table(name="profile")
* #ORM\Entity
*/
class EmployeeProfile extends AbstractProfile
{
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="Skill", inversedBy="profiles")
* #ORM\JoinTable(name="profile_skills",
* joinColumns={#ORM\JoinColumn(name="profile_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="skill_id", referencedColumnName="id", unique=true)}
* )
*/
private $skills;
public function __construct()
{
$this->skills = new ArrayCollection();
}
//... Getters, setters and all the rest
}
CompanyProfile Sub Entity:
<?php
/**
* CompanyProfile
*
* #ORM\Table(name="profile")
* #ORM\Entity
*/
class CompanyProfile extends AbstractProfile
{
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="Event", inversedBy="profiles")
* #ORM\JoinTable(name="profile_events",
* joinColumns={#ORM\JoinColumn(name="profile_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="event_id", referencedColumnName="id", unique=true)}
* )
*/
private $events;
public function __construct()
{
$this->events = new ArrayCollection();
}
//... Getters, setters and all the rest
}
It looks like you are trying to use #ORM\InheritanceType("JOINED") with a single table. You use #ORM\Table(name="profile") in your 3 entities.
The result is that Doctrine don't know what to do with your entities.
You could try replacing #ORM\InheritanceType("JOINED") by #ORM\InheritanceType("SINGLE_TABLE").
I have got two classes which are being associated using one to one uni-direction
{
id: 1,
name: "onetooneuniparent name",
onetooneunichild: {
id: 1,
name: "onetooneunichild name",
__initializer__: null,
__cloner__: null,
__isInitialized__: true
}
}
the above is the result when I do query like following
http://localhost:8000/onetooneRead?id=1
I want to know where and why the following come from
__initializer__: null,
__cloner__: null,
__isInitialized__: true
my expected result is just this
{
id: 1,
name: "onetooneuniparent name",
onetooneunichild: {
id: 1,
name: "onetooneunichild name"
}
}
OnetoOneUniParent.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="onetooneuniparent")
*/
class OnetoOneUniParent{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string",name="name")
*/
private $name;
/**
* #ORM\OneToOne(targetEntity="OnetoOneUniChild",cascade={"persist"})
* #ORM\JoinColumn(name="child_id", referencedColumnName="id")
*/
private $onetooneunichild;
<.... getter and setter here ...>
}
OnetoOneUniChild.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="onetooneunichild")
*/
class OnetoOneUniChild{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string",name="name")
*/
private $name;
<.... getter and setter here ...>
This is the method in controller
/**
* #Route("/onetooneRead")
* #Method("GET")
*/
public function onetooneReadAction(Request $request){
$logger = $this->get('logger');
$encoders = array(new XmlEncoder(), new JsonEncoder());
$normalizers = array(new ObjectNormalizer());
$serializer = new Serializer($normalizers, $encoders);
$logger->info('onetoone Read');
$id = $request->query->get("id");
$em = $this->getDoctrine()->getManager();
$onetooneuniparent = $em->getRepository('AppBundle:OnetoOneUniParent')->find($id);
$onetooneuniparentJson = $serializer->serialize($onetooneuniparent, 'json');
$response = new JsonResponse();
$response->setContent($onetooneuniparentJson);
return $response;
}
This is what is inside in MySQL
mysql> select * from onetooneuniparent;
+----+----------+------------------------+
| id | child_id | name |
+----+----------+------------------------+
| 1 | 1 | onetooneuniparent name |
| 2 | 2 | onetooneuniparent name |
| 3 | 3 | onetooneuniparent name |
+----+----------+------------------------+
3 rows in set (0.00 sec)
mysql> select * from onetooneunichild;
+----+-----------------------+
| id | name |
+----+-----------------------+
| 1 | onetooneunichild name |
| 2 | onetooneunichild name |
| 3 | onetooneunichild name |
+----+-----------------------+
3 rows in set (0.00 sec)
Those functions are part of the Doctrine proxy coding, since you are using Lazy Loading Doctrine needs to keep track of the child entity if it needs to be loaded or not. Part of that keeping track is these functions (I believe it is in this portion of Doctrine)
There may be a way around this which would be to avoid using lazy loading. To do that you can utilize EAGER loading if you always want the child to load with the parent. Alternatively if you only want to use EAGER for this one query and not every time you would have to switch to DQL as documented here or you could use the JOIN comma (second example down) here
I have a self-referencing entity Product:
<?php
/** #Entity #Table(name="products") **/
class Product
{
/** #Id #Column(type="integer") #GeneratedValue **/
protected $id;
/** #Column(type="string", nullable=true) **/
protected $name;
/**
* #ManyToMany(targetEntity="Product", mappedBy="connectedBy", cascade={"all"})
*/
protected $connectedWith;
/**
* #ManyToMany(targetEntity="Product", inversedBy="connectedWith", cascade={"all"})
* #JoinTable(name="connection",
* joinColumns={#JoinColumn(name="product_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="connected_product_id", referencedColumnName="id")}
* )
*/
protected $connectedBy;
public function __construct()
{
$this->connectedWith = new \Doctrine\Common\Collections\ArrayCollection();
$this->connectedBy = new \Doctrine\Common\Collections\ArrayCollection();
}
public function getConnected()
{
return $this->connectedWith;
}
public function addConnection(Product $product)
{
$this->connectedWith->add($product);
$product->connectedBy->add($this);
}
public function removeConnection(Product $product)
{
$this->connectedBy->removeElement($product);
$this->connectedWith->removeElement($product);
}
}
Next I created two products (IDs 1 and 2) and a connection between the both products:
mysql> select * from products;
+----+------+
| id | name |
+----+------+
| 1 | NULL |
| 2 | NULL |
+----+------+
2 rows in set (0.00 sec)
mysql> select * from connection;
+------------+----------------------+
| product_id | connected_product_id |
+------------+----------------------+
| 2 | 1 |
+------------+----------------------+
1 row in set (0.01 sec)
Now I want to remove the connection with this code:
$product1 = $entityManager->find('Product', 1);
$product2 = $entityManager->find('Product', 2);
$product1->removeConnection($product2);
$entityManager->persist($product1);
$entityManager->flush();
$product3 = $entityManager->find('Product', 1);
print count($product3->getConnected()) . "\n";
As expected, the code prints 0 as its result. But when I look into the database, the connection entry still exists. What could be the cause any how could this be fixed?
I've already tried to $entityManager->persist($product2) but to no avail.
I researched a little more and found the solution myself:
My function removeConnection() has a bug: I removed the product from both lists, connectedBy and connectedWith, which is wrong. Instead, I should do it like in addConnection():
$this->connectedWith->removeElement($product);
$product->connectedBy->removeElement($this);
In my database I have an entity named Graphique. This the schema for:
class Graphique
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var decimal
*
* #ORM\Column(name="index", type="decimal", precision=9, scale=3, nullable=false)
*/
private $index;
/**
* #var datetime
*
* #ORM\Column(name="date", type="datetime", nullable=false)
*/
private $date;
/*getters and setters*/
This is some values for index, according to my database schema (example):
----------------------------------
id | index | dateTime |
----------------------------------
1 | 1700.000 | dateTime datas|
----------------------------------
2 | 1200.000 | dateTime datas|
----------------------------------
3 | 1200.000 | dateTime datas|
----------------------------------
4 | 1304.000 | dateTime datas|
----------------------------------
etc...| etc... | etc... |
I have this method into a controller:
$em=$this->getDoctrine()->getManager();
$queryIndex = $em->createQuery( 'SELECT g.index
FROM MySpaceMyBundle:Graphique g');
$array = array_map('current', $queryIndex);
$response = new Response();
$data = json_encode($array);
$response->headers->set('Content-Type', 'application/json');
$response->setContent($data);
return $response;
it returns me this into my json response:
["1700.000","1200.000","1200.000","1304.000","1800.000","2012.000","2048.000","1048.000","3000.000","5421.000"]
but I need to have this simple array result (instead the json response I give you just above):
[1700.000,1200.000,1200.000,1304.000,1800.000,2012.000,2048.000,1048.000,3000.000,5421.000]
I need to return a simple array in my json response in order to have this decimal values for displaying them into a highchart graphic.
How can I proceed? I already try some Doctrine methods like ->getArrayresult(), ->getScalarResult(), ->toArray(), but the results are the same. I need to make my query result to a simple array.
json_encode($array, JSON_NUMERIC_CHECK);
This solution should help you.