Doctrine2 Self Reference Query -- Doesn't work - doctrine-orm

$ I am trying to create following scenario with doctrine 2 query builder
SELECT
p . *
FROM
_tree p
LEFT JOIN
_tree c ON p.id = c.parent_id
AND (c.lft >= p.lft AND c.rgt <= p.rgt)
WHERE
p.id = 3
I have set following relationship self generated by Doctrine2
class Tree {
/**
* #var \Tree
*
* #ORM\ManyToOne(targetEntity="Tree")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id")
* })
*/
private $parent;
// other code
}
here is my repo class
_em->createQueryBuilder();
$qb->select('p')
->from('Entity\Tree', 'p')
->leftJoin('p.Entity\Tree','c', 'ON','p.id = c.parent_id');
return $qb->getQuery()->getResult();
}
}
but I couldn't get it done. It throws following errors
[Tue Oct 01 22:30:11 2013] [error] [client 127.0.0.1] PHP Fatal error:
Uncaught exception 'Doctrine\ORM\Query\QueryException' with
message 'SELECT p FROM Entity\Tree p LEFT JOIN p.Entity\Tree c ON
p.id = c.parent_id' in
/var/www/pcb_frame_work/System/Libraries/Vendors/Doctrine/ORM/Query/QueryException.php:39\nStack
trace:\n#0
/var/www/pcb_frame_work/System/Libraries/Vendors/Doctrine/ORM/Query/Parser.php(429):
Doctrine\ORM\Query\QueryException::dqlError('SELECT p FROM
E...')\n#1
/var/www/pcb_frame_work/System/Libraries/Vendors/Doctrine/ORM/Query/Parser.php(925):
Doctrine\ORM\Query\Parser->semanticalError('Class
Entity\Ed...')\n#2
/var/www/pcb_frame_work/System/Libraries/Vendors/Doctrine/ORM/Query/Parser.php(1561):
Doctrine\ORM\Query\Parser->JoinAssociationPathExpression()\n#3
/var/www/pcb_frame_work/System/Libraries/Vendors/Doctrine/ORM/Query/Parser.php(1506):
Doctrine\ORM\Query\Parser->JoinAssociationDeclaration()\n#4
/var/www/pcb_frame_work/System/Libraries/Vendors/Doctrine/ORM/Query/Parser.php(1435):
Doctrine\ORM\Query\Parser->Join()\n#5
/var/www/pcb_frame_work/System/Librari in
/var/www/pcb_frame_work/System/Libraries/Vendors/Doctrine/ORM/Query/QueryException.php
on line 49, referer:

I don't know if I understand you completely, but I think you have to change your ManyToOne relation to:
/**
* #var \Tree
*
* #ORM\ManyToOne(targetEntity="Tree", inversedBy="children")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id")
* })
*/
private $parent;
/**
* #ORM\OneToMany(targetEntity="Tree", mappedBy="parent")
*/
private $children;
That way you can access the children of a class with $Tree->children and it's parent with $Tree->parent.
More information about self referencing associations can be found here: http://docs.doctrine-project.org/en/latest/reference/association-mapping.html#one-to-many-self-referencing

Related

doctrine2 entity number in month generation

I've got Invoice entity, in which I'd like to generate subsequent numbers within a given month.
Entity code:
/**
* Class Invoice
* #package App\Entity
* #ORM\Entity()
* #ORM\HasLifecycleCallbacks()
*/
class Invoice
{
(...)
/**
* #var int
* #ORM\Column(type="integer")
*/
private $year;
/**
* #var int
* #ORM\Column(type="integer")
*/
private $month;
/**
* #var int
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="CUSTOM")
* #ORM\CustomIdGenerator(class="App\Helper\InvoiceNumberGenerator")
*/
private $counter;
(...)
/**
* #ORM\PrePersist
* #ORM\PreUpdate
*/
public function numberGenerator()
{
if ($this->getYear() === null) {
$this->setYear(date('Y'));
$this->setMonth(date('m'));
}
}
And App\Helper\InvoiceNumberGenerator code is:
<?php
namespace App\Helper;
use App\Entity\Invoice;
use Doctrine\Common\Persistence\ObjectRepository;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Id\AbstractIdGenerator;
use Exception;
class InvoiceNumberGenerator extends AbstractIdGenerator
{
/**
* Generates an invoice number
*
* #param EntityManager $em
* #param Invoice $entity
* #return mixed
* #throws Exception
*/
public function generate(EntityManager $em, $entity)
{
if (!$entity instanceof Invoice) {
throw new Exception('Generator służy tylko do generowania numerów faktur.');
}
/** #var ObjectRepository | EntityRepository $invoiceRepository */
$invoiceRepository = $em->getRepository(Invoice::class);
/** #var Invoice $lastInvoice */
$lastInvoice = $invoiceRepository->findOneBy(
array(
'year' => $entity->getYear(),
'month' => $entity->getMonth()
),
array(
'counter' => 'desc'
)
);
if (empty($lastInvoice)) {
return 1;
}
return $lastInvoice->getCounter() + 1;
}
}
When I dump $lastInvoice, it shows:
Invoice {#5522 ▼
-id: 1
-generated: false
-fileName: "example"
-year: 2019
-month: 11
-counter: 1
-name: "AG"
-company: "Gall"
-address: "Street 1"
-address2: "Gliwice"
-nip: "6314567890"
-reservation: Reservation {#5855 ▶}
-date: null
}
So it looks like the generator gets to selecting last one correctly, but nevertheless I got error when trying to create new Invoice:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'counter'
cannot be null
Any advise on what I'm doing wrong?
the #CustomIdGenerator annotation is only called when the column is also marked with #Id. From the docs:
This annotations allows you to specify a user-provided class to generate identifiers. This annotation only works when both #Id and #GeneratedValue(strategy="CUSTOM") are specified.
Ids are always a special kind of thing and thus must sometimes be perfect before inserting. To solve your problem - because the counter is not an id column -, you could use lifecycle events instead (prePersist, probably) and use the event's entity manager in an event listener/subscriber to run your query.

How to remove a relationship cleanly in Docrine 2?

There are entities Endpoint, EndpointServerConfig, and Server:
/**
* Server
*
* #ORM\Table(
* name="server",
* indexes={
* #ORM\Index(name="fk_server_server_type_idx", columns={"server_type_id"}),
* #ORM\Index(name="fk_server_cluster_idx", columns={"cluster_id"})
* }
* )
* #ORM\Entity
*/
class Server
{
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=32, nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="NONE")
*
* #Groups({"export"})
*/
protected $name;
/**
* #var EndpointServerConfig[]
*/
protected $endpointServerConfigs;
}
/**
* EndpointServerConfig
*
* #ORM\Table(name="endpoint_server_config", indexes={
* #ORM\Index(name="fk_endpoint_server_config_server_idx", columns={"server_name"})}
* )
* #ORM\Entity
*/
class EndpointServerConfig
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* #var Server
*
* #ORM\ManyToOne(targetEntity="Server")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="server_name", referencedColumnName="name")
* })
*
* #Groups({"export"})
*/
protected $server;
/**
* #var Endpoint
*
* #ORM\OneToOne(targetEntity="Endpoint", mappedBy="endpointServerConfig")
*/
protected $endpoint;
}
/**
* Endpoint
*
* #ORM\Table(
* name="endpoint",
* indexes={
* ...
* #ORM\Index(name="fk_endpoint_endpoint_server_config_idx", columns={"endpoint_server_config_id"}),
* ...
* }
* )
* #ORM\Entity
* ...
*/
class Endpoint
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
}
Now I want to update an entity (e.g. Foo), that contains an Endpoint. Among other changes I want to remove the reference to the Server from the Endpoint's EndpointServerConfig. That means for the database: The endpoint_server_config.server needs to be set to NULL.
I load the Foo to a Zend\Form, disable the server and submit the changes. On the server side I unset the EndpointServerConfig#server over Foo:
/** #var Foo $myFoo */
if(! $myFoo->getEndpoint()->getEndpointServerConfig()->getServer() || ! $myFoo->getEndpoint()->getEndpointServerConfig()->getServer()->getName()) {
$myFoo->getEndpoint()->getEndpointServerConfig()->setServer(null);
}
$this->entityManager->persist($myFoo);
$this->entityManager->flush($myFoo);
It leads to an error:
An exception occurred while executing 'UPDATE server SET name = ? WHERE name = ?' with params ["", "someservername"]:
SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails (`mydb`.`endpoint_server_config`, CONSTRAINT `fk_endpoint_server_config_server` FOREIGN KEY (`server_name`) REFERENCES `server` (`name`) ON DELETE NO ACTION ON UPDATE NO ACTION)
That means, Doctrine tries to UPDATE the Server, instead of just to remove the reference to it from the EndpointServerConfig. But why?
Only when I manually set endpoint_server_config.server_name to NULL (directly in the database), I can save the changes via form and Doctrine.
How to get it working?
EDIT
Just noticed, that I get the same problem on every update of the EndpointServerConfig. So not only on setServer(null), bu also when I try to set a new Server. In this case the attempts leads to the error:
An exception occurred while executing 'UPDATE server SET name = ? WHERE name = ?' with params ["newservername", "someservername"]:
SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails (`mydb`.`endpoint_server_config`, CONSTRAINT `fk_endpoint_server_config_server` FOREIGN KEY (`server_name`) REFERENCES `server` (`name`) ON DELETE NO ACTION ON UPDATE NO ACTION)
Add following:-
cascade={"persist", "remove"}
on your relation.
#ORM\ManyToOne(targetEntity="Server", cascade={"persist", "remove"})

Symfony 2.8 : DQL query to display an arraycollection

I'm a beginner on Symfony 2 and I have a problem with a DQL query : I'm trying to display a property that is an ArrayCollection.
Here's my case : Two classes joined together with a "ManyToOne" and "OneToMany"
DishGrade.php :
/**
* #var Dish $dish
*
* #ORM\ManyToOne(targetEntity="Dish", inversedBy="grades",
cascade={"persist", "remove", "merge"})
* #ORM\JoinColumn(name="dish_id", referencedColumnName="id", nullable=false)
*
*/
private $dish;
Dish.php :
/**
* #var \Doctrine\Common\Collections\Collection $grades
*
* #ORM\OneToMany(targetEntity="DishGrade", mappedBy="dish", cascade={"all"},
orphanRemoval=true)
*/
private $grades;
Then here's my controller which returns an array of objects "DishGrade"
/**
* Get grades dish.
*
*
* #ApiDoc(
* output = "Awadac\ApiBundle\Model\Dish",
* statusCodes = {
* 200 = "Returned when successful",
* 404 = "Returned when the dish is not found"
* }
* )
*
* #Get("/dishes/{id}")
*
* #param Request $request the request object
* #param int $id the dish id
*
* #return array
*
* #throws NotFoundHttpException when dish not exist
*/
public function getDishAction(Request $request, $id)
{
$dish = $this->getDishManager()->getRepository()->getGrades($id);
if (!$dish) {
throw $this->createNotFoundException('Dish does not exist.');
}
return $dish;
}
And here's my query that doesn't work :
DishRepository.php :
public function getGrades($id){
$qb = $this->_em->createQueryBuilder();
return $qb->select('d.grades')
->from('AwadacApiBundle:Dish', 'd')
->where('d.id = :id')
->setParameter('id', $id)
->getQuery()
->getResult();
}
the error code I get :
{"code":500,"message":"[Semantical Error] line 0, col 9 near 'grades FROM AwadacApiBundle:Dish': Error: Invalid PathExpression. Must be a StateFieldPathExpression.","errors":null}
And when I want to get all the properties from my object "Dish" it works, so I just wonder why I can't display this ArrayCollection alone.
Thank you for your time !
If you are already in the DishRepository you will be able to create the query builder by
$qb = $this->createQueryBuilder('d');
Further just remove the ->from() statement and I guess it should work properly:
return $qb->select('d.grades')
->where('d.id = :id')
->setParameter('id', $id)
->getQuery()
->getResult();

Doctrine Dql for One-To-Many, Unidirectional with Join Table

So I've read the following article.
I've done something similar with my own entities and am trying to create a DQL.
DQL:
$em = $this->getDoctrine()->getManager();
$query = $em->createQuery(
'SELECT s,st,tt
FROM CrmClientBundle:Session s
INNER JOIN s.session_treatments st
INNER JOIN st.treatment_type_id tt
WHERE s.client_id = ' . $id
);
$sessions = $query->getResult();
I receive the following error though:
[Semantical Error] line 0, col 152 near 'tt
': Error: Class Crm\ClientBundle\Entity\TreatmentType has no association named
treatment_type_id
However if I remove the second join and check the symfony profiler it creates the following query which seems to me that I have created my entities properly:
SELECT
s0_.id AS id0,
s0_.client_id AS client_id1,
s0_.date AS date2,
s0_.session_id AS session_id3,
s0_.session_type AS session_type4,
s0_.session_cost AS session_cost5,
s0_.products_bought AS products_bought6,
s0_.products_cost AS products_cost7,
s0_.total_cost AS total_cost8,
s0_.total_paid AS total_paid9,
s0_.notes AS notes10,
t1_.id AS id11,
t1_.type AS type12,
t1_.cost AS cost13,
t1_.category_id AS category_id14
FROM
sessions s0_
INNER JOIN session_treatments s2_ ON s0_.id = s2_.session_id
INNER JOIN treatment_types t1_ ON t1_.id = s2_.treatment_type_id
WHERE
s0_.client_id = 1
Crm\ClientBundle\Session.php:
/**
* #ORM\Entity
* #ORM\Table(name="sessions")
*/
class Session {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/** #ORM\Column(type="integer") */
private $client_id;
/** #ORM\Column(type="date") */
private $date;
/**
* **
* #ORM\ManyToMany(targetEntity="TreatmentType")
* #ORM\JoinTable(name="session_treatments",
* joinColumns={#ORM\JoinColumn(name="session_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="treatment_type_id", referencedColumnName="id ", unique=true)}
* )
*/
private $session_treatments;
/**
* Constructor
*/
public function __construct()
{
$this->session_treatments = new ArrayCollection();
}
}
Crm\ClientBundle\TreatmentType.php:
/**
* #ORM\Entity
* #ORM\Table(name="treatment_types")
*/
class TreatmentType {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/** #ORM\Column(type="string", length=255) */
private $type;
/** #ORM\Column(type="decimal") */
private $cost;
}
You have 2 entities and you are trying to retrieve 3 entities. The second join is unnecessary. It's only needed if TreatmentType has yet another relationship (other then the one with Session). For clarification:
$em = $this->getDoctrine()->getManager();
$query = $em->createQuery(
'SELECT s,st,tt
FROM CrmClientBundle:Session s // <-- s is your Session
INNER JOIN s.session_treatments st // <-- st is your TreatmentType
INNER JOIN st.treatment_type_id tt <-- TreatmentType does not have a property $treatement_type_id that points to non-defined relationship. tt would be your 3rd entity.
WHERE s.client_id = ?1'
);
$query->setParameter(1, $id);
$sessions = $query->getResult();
Bonus: used bound a parameter -- helps against SQL injection and speeding up your query in the future

DQL INNER JOIN with Zend Framework 2 / Doctrine 2

With this SQL-Statement, i can fetch the latest messages from all users:
SELECT m1.*
FROM message AS m1
INNER JOIN user
AS u1
ON m1.sender_id = u1.user_id
INNER JOIN (
SELECT sender_id,
MAX(dateSent) MaxDate
FROM message
WHERE receiver_id = 4
GROUP BY sender_id
) AS m2
ON m1.sender_id = m2.sender_id
AND m1.datesent = m2.MaxDate;
Those are my Entities in my Zend Framework 2 application:
First, i got the Message. A User can send a Message to another user.
/**
* #ORM\Entity
* #ORM\Table(name="message")
*/
class Message
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Application\Entities\User", inversedBy="messagesSent")
* #ORM\JoinColumn(referencedColumnName="user_id")
*/
private $sender;
/**
* #ORM\ManyToOne(targetEntity="Application\Entities\User", inversedBy="messagesReceived")
* #ORM\JoinColumn(referencedColumnName="user_id")
*/
private $receiver;
/**
* #ORM\Column(type="string", length=1024)
*/
private $message;
And the User entity here:
/**
* #ORM\Entity
* #ORM\Table(name="user")
*/
class User
{
/**
* #var int
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $user_id;
I tried to translate that SQL into DQL and got this:
SELECT m1.*
FROM Application\Entities\Message AS m1
INNER JOIN Application\Entities\User AS u1
WITH m1.sender_id = u1.user_id
INNER JOIN (
SELECT sender_id,
MAX(dateSent) MaxDate
FROM Application\Entities\Message
WHERE receiver_id = 4
GROUP BY sender_id
) AS m2
WITH m1.sender_id = m2.sender_id
AND m1.datesent = m2.MaxDate;
If i execute it, i get an error from Doctrine:
[Semantical Error] line 0, col 206 near '(
': Error: Class '(' is not defined.
What have i done wrong?
Subqueries are unavailable in doctrine(as far as I know) in DQL(from this). Can you write it query builder?
public function innerJoin($join, $alias = null, $conditionType = null, $condition = null);
See Reference