I'm using Doctrine2 in Zend Framework 2. In a very unique case, I need to create a native SQL query which only selects a table's id and does some geographic calculations. I followed the documentation and created a ResultSetMapping definition for this query:
$rsm = new ResultSetMapping();
$rsm->addEntityResult('Location', 'l');
$rsm->addFieldResult('l', 'id', 'id');
$rsm->addScalarResult('distance', 'distance');
$query = $em->createNativeQuery('SELECT l.id, (3959 * acos(cos(radians(:lat))
* cos( radians( l.latitude ) )
* cos( radians( l.longitude ) - radians(:lng) )
+ sin( radians(:lat) )
* sin( radians( l.latitude ) ) ) ) AS distance
FROM location l
WHERE
l.latitude BETWEEN (:lat - .2) AND (:lat + .2)
AND l.longitude BETWEEN (:lng -.2) AND (:lng + .2)
HAVING distance < 10 ORDER BY distance'), $rsm);
$query->setParameter('lng', $lng)
->setParameter('lat', $lat)
This returns a result set correctly and I process it.
However, I want to return a fully hydrated collection of those entities so I do a simple DQL query
$em->createQuery('SELECT l FROM Location l WHERE l.id IN (:locations)')
->setParameter('locations', $locationIds)
->getResult();
The resulting collection of entities only has the 'id' field hydrated. All other values are null.
I suspect this is because I set the ResultSetMapping for the previous query and it's being applied in the DQL afterwards as well. How do I go about resetting Doctrine's mapping behavior back to default?
Edit
Here is a truncated version of the Location entity. It hydrates just fine in all other areas of the site.
<?php
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="location")
*/
class Location extends RestrictedSite
{
/**
* #ORM\Id
* #ORM\Column(type="integer");
* #ORM\GeneratedValue(strategy="AUTO")
*
* #Annotation\Exclude()
*
* #var int
*/
protected $id;
/**
* #ORM\Column(type="string", length=255, name="name")
*
* #var string
*/
protected $name;
/**
*
* #ORM\Column(type="float")
*
* #var float
*/
protected $latitude;
/**
*
* #ORM\Column(type="float")
*
* #var float
*/
protected $longitude;
... more columns & methods ...
}
The schema is something like:
CREATE TABLE `location` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`latitude` float DEFAULT NULL,
`longitude` float DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
Found it in the doctrine docs here:
http://docs.doctrine-project.org/en/latest/reference/working-with-objects.html
I need simply invoke $em->clear(); to reset the mapping and the full entity is hydrated in the subsequent DQL. Yay!
Related
I'm working on upgrading a product from Symfony 2.7 to 4.2 (currently at 3.4) and am getting stuck with some existing associations.
The field AppBundle\Entity\User#currentBillingAgreement is on the owning side of a bi-directional relationship, but the specified mappedBy association on the target-entity AppBundle\Entity\BillingAgreement# does not contain the required 'inversedBy' attribute.
If association AppBundle\Entity\User#currentBillingAgreement is one-to-one, then the inversed side AppBundle\Entity\BillingAgreement#user has to be one-to-one as well.
The User entity has these associations:
/**
* #var BillingAgreement
* #ORM\OneToOne(
* targetEntity="AppBundle\Entity\BillingAgreement",
* inversedBy="user",
* cascade={"persist"}
* )
* #ORM\JoinColumn(
* name="currentBillingAgreementID",
* referencedColumnName="billingAgreementID"
* )
*/
protected $currentBillingAgreement;
/**
* #var ArrayCollection
* #ORM\OneToMany(
* targetEntity="AppBundle\Entity\BillingAgreement",
* mappedBy="user",
* cascade={"persist"}
* )
* #Serializer\Exclude()
*/
protected $billingAgreements;
and BillingAgreement has this:
/**
* #var User
* #ORM\ManyToOne(
* targetEntity="AppBundle\Entity\User",
* inversedBy="billingAgreements"
* )
* #ORM\JoinColumn(
* name="userID",
* referencedColumnName="userID",
* nullable=false
* )
*/
protected $user;
When I add a OneToOne mapping to BillingAgreement::$user (#ORM\OneToOne(targetEntity="AppBundle\Entity\User", inversedBy="currentBillingAgreement")), I get a new error:
The field AppBundle\Entity\BillingAgreement#user is on the owning side of a bi-directional relationship, but the specified mappedBy association on the target-entity AppBundle\Entity\User# does not contain the required 'inversedBy' attribute.
and the original 2 errors remain.
You can make the OneToOne association unidirectional by removing inversedBy="user", from the annotation.
or
Use a different field for each association on BillingAgreement entity:
/**
* #var User
* #ORM\ManyToOne(
* targetEntity="AppBundle\Entity\User",
* inversedBy="billingAgreements"
* )
* #ORM\JoinColumn(
* name="userID",
* referencedColumnName="userID",
* nullable=false
* )
*/
protected $user;
/**
* #var User
* #ORM\OneToOne(targetEntity="AppBundle\Entity\User", inversedBy="currentBillingAgreement")
*/
protected $singleUser;
and in User entity:
/**
* #var BillingAgreement
* #ORM\OneToOne(
* targetEntity="AppBundle\Entity\BillingAgreement",
* inversedBy="singleUser",
* cascade={"persist"}
* )
* #ORM\JoinColumn(
* name="currentBillingAgreementID",
* referencedColumnName="billingAgreementID"
* )
*/
protected $currentBillingAgreement;
/**
* #var ArrayCollection
* #ORM\OneToMany(
* targetEntity="AppBundle\Entity\BillingAgreement",
* mappedBy="user",
* cascade={"persist"}
* )
* #Serializer\Exclude()
*/
protected $billingAgreements;
References
Doctrine 2.6 Association Mapping
When I try to store values to DB with symfony via doctrine, doctrine produces a NULL for a field although it is set and I have no idea why.
Entity:
https://github.com/Invis00/pub/blob/master/OldbCompetitionstage.php
Test Controller:
public function compTest(){
$em = $this->getDoctrine()->getManager();
$object = new OldbCompetitionstage();
$object->setName("name");
$object->setNr(2);
$object->setOldbCompetitionid(2);
// Echo tells 2
echo $object->getOldbCompetitionid();
$em->persist($object);
$em->flush();
return $this->render('base.html.twig', array( "current" => "pubmin")
);
}
Table:
compstageID int(11)
name varchar(100)
nr int(11)
oldb_competitionID int(11)
startDate datetime
ipub_compstageID int(11)
Symfony Profiler tells:
INSERT INTO oldb_competitionstage (name, nr, oldb_competitionID, startDate, ipub_compstageID) VALUES (?, ?, ?, ?, ?)
Parameters: { 1: name, 2: 2, 3: null, 4: null, 5: null }
But why is oldb_competitionid NULL instead of 2?
echo tells me it is 2.
It seems that the mapping information for competition is in some kind the reason for this behaviour but there are no errors told and I dont see the problem with it.
Your problem come from $oldbCompetitionid in your class. It doesn't have any reason to exist, and because you assigned the same column name than the one used in your ManyToOne ORM column definition, it causes the NULL.
As chalasr commented,
you can't neither define a joinColumn as a member of your class nor
set its value as a simple column, with or without ORM
Remove it and check the relation definitions between your classes.
The problematic part of the code:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity
* #ORM\Table(name="oldb_competitionstage")
*/
class OldbCompetitionstage
{
/**
* #var integer
*
* #ORM\Column(name="compstageID", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $compstageid;
/**
* #var integer
*
* #ORM\Column(name="oldb_competitionID", type="integer", nullable=true)
*/
private $oldbCompetitionid;
/**
* Mappings
*/
/**
* #ORM\ManyToOne(targetEntity="OldbCompetition", inversedBy="compstages")
* #ORM\JoinColumn(name="oldb_competitionID", referencedColumnName="competitionID")
*/
private $oldbCompetition;
You should have something like this in your OldbCompetition class:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity
* #ORM\Table(name="oldb_competitionstage")
*/
class OldbCompetition
{
/**
* #var integer
*
* #ORM\Column(name="competitionID", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $compid;
/**
* #ORM\ManyToOne(targetEntity="OldbCompetition", mappedBy="oldbCompetition")
*/
private $compstages;
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
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
I'm looking for a suggestion on how to map a OneToMany/ManyToOne relationship that uses a join table. The mapping I have is not taking, and I get an error that article_id is not set in the media table.
class Media
{
// ...
/**
* #ManyToOne(targetEntity="Document", inversedBy="media")
* #JoinTable(name="articles_x_media", referencedColumnName="id")
* joinColumns={#JoinColumn(name="media_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="bid_id", referencedColumnName="id")})
* )
*/
protected $document;
}
class Document
{
// ...
/**
* #OneToMany(targetEntity="Media", mappedBy="document"))
* #JoinTable(name="articles_x_media", referencedColumnName="id")
* joinColumns={#JoinColumn(name="article_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="media_id", referencedColumnName="id")}
* )
*/
protected $media;
}
There's a specific paragraph in the documentation about OneToMany mapping with join table.
Anyway, what you probably want is an uni-directional ManyToMany association.
Also, #OneToMany does not come with a #JoinTable, and the same for #ManyToOne either.