DQL INNER JOIN with Zend Framework 2 / Doctrine 2 - doctrine-orm

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

Related

Doctrine delete items from collection - different column name

I have problem with Doctrine collection. In PromoCodeTerm entity I have #OneToMany $days collection, but when doctrine delete items from this collection, generates SQL query with different column name, like a promoCodeTerm instead of promo_code_term_id.
class PromoCodeTerm {
/**
* #var string
* #ORM\Column(name="id", type="guid")
* #ORM\Id()
*/
private $id;
/**
* #var PromoCodeTermDay[]
* #ORM\OneToMany(targetEntity="AppBundle\Entity\PromoCode\PromoCodeTermDay", mappedBy="promoCodeTerm", cascade={"persist"}, orphanRemoval=true)
*/
private $days;
}
class PromoCodeTermDay {
/**
* #var integer
* #ORM\Column(name="day_id", type="integer")
* #ORM\Id()
*/
private $day;
/**
* #var PromoCodeTerm
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\PromoCode\PromoCodeTerm", inversedBy="days")
* #ORM\JoinColumn(name="promo_code_term_id", referencedColumnName="id", onDelete="CASCADE")
* #ORM\Id()
*/
private $promoCodeTerm;
}
What am I doing wrong?
An exception occurred while executing 'DELETE FROM `promo_code__promo_code_term_day` WHERE `day` = ? AND `promoCodeTerm` = ?' with params [1, \"c2fce03a-0507-47ca-a060-362cce683b06\"]:\n\nSQLSTATE[42S22]: Column not found: 1054 Unknown column 'promoCodeTerm' in 'where clause'

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"})

Doctrine2, DQL, association not initialized

is here some doctrine expert, who can explain me, why these DQLs will not initialize tallyRevs field on Tally entity? I supposed, that when I fetch TallyRevs (owner side) and fetchJoin Tally entity to them, that field tallyRevs will be initialized. What am I doing wrong? I need to select TallyRev based on some criteria via DQL and since it is a bi-directional association, I would like it to be initialized from the other (Tally.tallyRevs) side also.
Screen of dump
<?php
/**
* #ORM\Entity
* #ORM\Table(name="v3_overview_calloff_tally")
*/
class Tally
{
/**
* #var int
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var TallyRev[]|Collection
* #ORM\OneToMany(targetEntity="STI\Model\Entity\V3\Overview\CallOff\TallyRev", mappedBy="tally")
*/
private $tallyRevs;
}
/**
* #ORM\Entity
* #ORM\Table(name="v3_overview_calloff_tallyrev")
*/
class TallyRev
{
/**
* #var int
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var int
* #ORM\Column(type="integer", nullable=false)
*/
private $revision;
/**
* #var Tally
* #ORM\ManyToOne(targetEntity="STI\Model\Entity\V3\Overview\CallOff\Tally", inversedBy="tallyRevs")
* #ORM\JoinColumn(name="tally_id", referencedColumnName="id", nullable=false)
*/
private $tally;
}
Here is some repository code:
$qb = $repository->createQueryBuilder();
$qb
->select('tallyRev')
->from(TallyRev::class, 'tallyRev', 'tallyRev.id')
// complicated filtering, this is just an example
->andWhere($qb->expr()->in('tallyRev.revision', ':rev'))
->setParameter('rev', $rev)
;
$tallyRevs = $qb->getQuery()->getResult();
$ids = array_keys($tallyRevs);
$qb2 = $repository->createQueryBuilder();
$qb2
->select('partial tallyRev.{id}')
->from(TallyRev::class, 'tallyRev', 'tallyRev.id')
->andWhere($qb2->expr()->in('tallyRev.id', ':ids'))
->setParameter('ids', $ids)
->leftJoin('tallyRev.tally', 'tally')
->addSelect('tally')
;
$qb2->getQuery()->getResult();
I know, that I can write DQL from the Tally side like this:
$qb
->select('tally')
->from(Tally::class, 'tally', 'tally.id')
->leftJoin('tally.tallyRev', 'tallyRev')
->addSelect('tallyRev')
->andWhere($qb->expr()->in('tallyRev.revision', ':rev'))
->setParameter('rev', $revs)
;
If you want to fetch join tally when you fetch tallyRev you should write something like this in the first qb, and delete qb2
->select(['tallyRev', 'tally'])
->from(TallyRev::class, 'tallyRev', 'tallyRev.id')
->join('tallyRev.tally', 'tally')
// complicated filtering, this is just an example
->andWhere($qb->expr()->in('tallyRev.revision', ':rev'))
->setParameter('rev', $rev)
;

How to write exists subquery DQL for many to many association

Given the entities below, would someone please help me understand how to write the DQL equivalent of the following SQL? I can't seem to find a good example of a DQL subquery that translates to a select on a pivot table. Thank you!
select *
from event a
where exists (
select *
from event_category b
where b.event_id = a.id
and b.category_id = 1
)
Entities:
/**
* #Entity
* #Table(name="event")
*/
class Event
{
/**
* #Column(type="integer")
* #Id
*/
protected $id;
/**
* #JoinTable(
* inverseJoinColumns={
* #JoinColumn(name="category_id", referencedColumnName="id")
* },
* joinColumns={
* #JoinColumn(name="event_id", referencedColumnName="id")
* },
* name="event_category"
* )
* #ManyToMany(targetEntity="Category")
*/
protected $categories;
}
/**
* #Entity
* #Table(name="category")
*/
class Category
{
/**
* #Column(type="integer")
* #Id
*/
protected $id;
}
Please have a look at Doctrine Query Language
Your example could be written :
SELECT event FROM Event event
WHERE EXISTS (
SELECT cat FROM Category cat
WHERE IDENTITY(cat.event) = event.id
AND cat.id = 1
)
Now I might be wrong but I don't think you need a subquery here.
If you want events that have a given category :
SELECT event FROM Event event JOIN event.category WHERE category.id = 1

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