Symfony2 DQL How to join the last row in a OneToMany relation - symfony

I have two entities related by a OneToMany relation:
<?php
namespace CRMBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* User
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="CRMBundle\Entity\ContactRepository")
*/
class User
{
/*...*/
/**
* #ORM\OneToMany(targetEntity="CRMBundle\Entity\Message", mappedBy="user", cascade={"persist"})
* #ORM\OrderBy({"datetime" = "DESC"})
*/
protected $messages;
/*...*/
}
And
<?php
namespace CRMBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Message
*
* #ORM\Table()
* #ORM\Entity
*/
class Message
{
/*...*/
/**
* #ORM\ManyToOne(targetEntity="CRMBundle\Entity\User", inversedBy="messages")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="SET NULL")
*/
private $user;
/**
* #var \DateTime
*
* #ORM\Column(name="Datetime", type="datetime", nullable=true)
*/
private $datetime;
/*...*/
}
My question is how to create a query in the UserController to get every user with the last message (i.e. the most recent according to the datetime attribute) of each user?

I think what you are looking for is in one of my previous answers to one of my own questions ^^
You have to use a subquery to select dynamically the most recent datetime value of one user's messages, and to join the message having this value.
To do this, you must define the (sub) query selecting the MAX value of message.datetime:
$qb2= $this->createQueryBuilder('ms')
->select('MAX(ms.datetime) maxDate')
->where('ms.user = u')
;
And then use it in your join clause, the whole function being in your UserRepository:
$qb = $this->createQueryBuilder('u');
$qb ->leftJoin('u.messages', 'm', 'WITH', $qb->expr()->eq( 'm.datetime', '('.$qb2->getDQL().')' ))
->addSelect('m');
Your User (each of them) will then have a messages Collection, containing one (or null if no message from the user) message, which you will get this way:
$user->getMessages()->first();
But if you use the lazy loading function of Symfony, as you have already defined an orderby annotation on your user.messages attribute, calling
$user->getMessages()->first()
should return to you the most recent message (but will also load all the others silently).
To avoid this silent second DB query, you can join it directly to the query requesting your users:
$qb = $this->createQueryBuilder('u');
$qb ->leftJoin('u.messages', 'm')
->addSelect('m');

Related

Symfony, Doctrine order by foreign key even when the FK is null

Lets imagine I have an Entity like this
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
*
* #ORM\Table(name="entity")
* #ORM\Entity(repositoryClass="AppBundle\Repository\ MyEntityRepository")
*/
class Entity
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(name="title", type="string", length=255)
*/
private $title;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Kind")
*/
private $kind;
}
I want to query them and order them by kind.
Here what I do that in my repository
<?php
namespace AppBundle\Repository;
class MyEntityRepository extends \Doctrine\ORM\EntityRepository
{
public function getAllOrdered()
{
return $this->createQueryBuilder('e')
->join('e.kind', 'k')
->orderBy('k.id', 'ASC')
->getQuery()
->getResult();
}
}
This work great but that completely ignore all line where the kind is null.
So how can I retrieve and order all entities even if the kind is null ?
You are using an inner join. This is a more efficient query because it only retrieves records where there is a match between the 2 tables.
If you want to pick up null values, you need to use leftJoin. This should be used with care as these queries are heavier than innerJoin because all records in the base table are considered instead of just the matches.
<?php
namespace AppBundle\Repository;
class MyEntityRepository extends \Doctrine\ORM\EntityRepository
{
public function getAllOrdered()
{
return $this->createQueryBuilder('e')
->leftJoin('e.kind', 'k')
->orderBy('k.id', 'ASC')
->getQuery()
->getResult();
}
}

Symfony2 - Problem with entity relation and doctrine request

I have an entity offer which has a oneToOne relation with a product and I have a user who would like to get all the offers he got on his products.
I would like to access to the user who has made the product from an offer in my SQL request.
So it's something like that:
$user = $this->getUser();
$listofofferusergot = $em->getRepository('blabla:Offer')->findBy(array('product.autor.id' => $user->getId()));
(ps : Offer has a OneToOne relation with product)
(ps2: the thing I wrote doesn't work )
So the question is general Can I simply access to a subfield (like id in my case) or must I do a $em->createQuery() stuff
offer class:
<?php
namespace Nemi\TwigBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Offer
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="Nemi\TwigBundle\Entity\OfferRepository")
*/
class Offer
{
/**
* #ORM\ManyToOne(targetEntity="Nemi\TwigBundle\Entity\Product")
* #ORM\JoinColumn(nullable=false)
*/
private $product;
...
}
for the product class:
<?php
namespace Nemi\TwigBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Product
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="Nemi\TwigBundle\Entity\ProductRepository")
*/
class Product
{
/**
* #ORM\ManyToOne(targetEntity="Nemi\UserBundle\Entity\User")
* #ORM\JoinColumn(nullable=false)
*/
private $autor;
....
}
You could add this method in your OfferRepository:
public function findOffersByProductAuthor(User $user)
{
return $this->createQueryBuilder('offer')
->join('offer.product', 'product')
->join('product.author', 'author')
->where('author = :user')
->setParameter('user', $user)
->getQuery()
->getResults();
}
Then, call:
$em->getRepository('blabla:Offer')-> findOffersByProductAuthor($this->getUser());

How to get a collection of related entities by using Doctrine ResultSetMapping?

I use Doctrine 2.3.4. and Symfony 2.3.0
I have two entities: Person and Application.
Application gets created when some Person applies for a job.
Relation from Person to Application is OneToMany, bidirectional.
Using the regular Doctrine documentation here I managed to get a correct result set only when working with a single entity.
However, when I add joined entity, I get a collection of root entities but joined to a wrong related entity.
In other words, the problem is that I get a collection of Applications but all having the same Person.
Native sql query, when executed directly returns a correct result.
This is the code:
$sql = "SELECT a.id, a.job, p.first_name, p.last_name
FROM application a
INNER JOIN person p ON a.person_id = p.id";
$rsm = new ResultSetMapping;
$rsm->addEntityResult('\Company\Department\Domain\Model\Application', 'a');
$rsm->addFieldResult('a','id','id');
$rsm->addFieldResult('a','job','job');
$rsm->addJoinedEntityResult('\Company\Department\Domain\Model\Person' , 'p', 'a', 'person');
$rsm->addFieldResult('p','first_name','firstName');
$rsm->addFieldResult('p','last_name','lastName');
$query = $this->em->createNativeQuery($sql, $rsm);
$result = $query->getResult();
return $result;
Here are the Entity classes:
namespace Company\Department\Domain\Model;
use Doctrine\ORM\Mapping as ORM;
/**
* Person
*
* #ORM\Entity
* #ORM\Table(name="person")
*/
class Person
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string First name
*
* #ORM\Column(name="first_name",type="string",length=255)
*/
private $firstName;
/**
* #var string Last name
*
* #ORM\Column(name="last_name",type="string",length=255)
*/
private $lastName;
/**
*
* #var Applications[]
* #ORM\OneToMany(targetEntity="Application", mappedBy="person")
*/
private $applications;
Application class:
namespace Company\Department\Domain\Model;
use Doctrine\ORM\Mapping as ORM;
/**
* Application (Person applied for a job)
*
* #ORM\Entity
* #ORM\Table(name="application")
*/
class Application
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var Person
*
* #ORM\ManyToOne(targetEntity="Person", inversedBy="applications")
* #ORM\JoinColumn(name="person_id", referencedColumnName="id")
*/
private $person;
/**
* #var string
* #ORM\Column(name="job",type="string", length=100)
*/
private $job;
I must be missing something here?
Found out where the error was:
The Person->id property has to be mapped too.
Also, order of columns in SELECT clause has to match the order of addFieldResult() statements.
Therefore, $sql should look like this:
SELECT a.id, a.job, p.id AS personId, p.first_name, p.last_name
FROM application a
INNER JOIN person p ON a.person_id=p.id
And mapping for related property like this:
$rsm->addJoinedEntityResult('\Company\Department\Domain\Model\Person' , 'p', 'a', 'person');
$rsm->addFieldResult('p','personId','id');
$rsm->addFieldResult('p','first_name','firstName');
$rsm->addFieldResult('p','last_name','lastName');
So, the mapped field result column name corresponds to sql result column name, and third parameter, id in this case, should be the property actual name.

Small issue with persist()

I have the following two entities:
<?php
namespace Site\AnnonceBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Site\UserBundle\Entity\User;
/**
* Site\AnnonceBundle\Entity\Sujet
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="Site\AnnonceBundle\Entity\SujetRepository")
*/
class Sujet
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
//Some code...
/**
*
* #ORM\ManyToOne(targetEntity="Site\UserBundle\Entity\User")
*/
private $user;
//getter/setter....
user Entity(FOSUserBundle) :
<?php
namespace Site\UserBundle\Entity;
use FOS\UserBundle\Entity\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table()
*
*/
class User extends BaseUser{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
public function getId() {
return $this->id;
}
}
when I created a "Sujet", I made(in SujetController.php):
$em = $this->getDoctrine()->getEntityManager();
$sujet->setResolu(false);
$em->persist($sujet);
$em->flush();
its works, But the inserted "Sujet" in the database refer to user null... so in the second version i made this:
$em = $this->getDoctrine()->getEntityManager();
$sujet->setResolu(false);
$sujet->setUser(new User($this->get('session')->get('user_id'))) ;//the user is already in the DB
$em->persist($sujet);
$em->flush();
but i get this error:
A new entity was found through the relationship 'Site\AnnonceBundle\Entity\Sujet#user' that was not configured to cascade persist operations for entity: . Explicitly persist the new entity or configure cascading persist operations on the relationship. If you cannot find out which entity causes the problem implement 'Site\UserBundle\Entity\User#__toString()' to get a clue.
I do not understand, I have already worked with another ORM (JPA) and it works in that way ...
how to tell "Sujet" about what is related to an entity already existing in database?
(sorry if my english is bad)
EDIT : its worked for me :
$user = $this->get('security.context')->getToken()->getUser();
$sujet->setUser($user);
$em->persist($sujet);
$em->flush();
Just in case, the error came from the fact you created a new User and linked it to the sujet without persisting it (as there is no cascade, the entity was linked to a none persisted entity, resulting in the error).
Your edit suggests you found a way to get the current User (this one is persisted, unlike the "new User") you made before.
You could also have done :
$repository = $this->getDoctrine()
->getEntityManager()
->getRepository('YourBundle:User');
$user = $repository->find($iduser);
$sujet->setUser($user);
It would have been a good solution if you wanted to make the edit "for another user".

Doctrine2.1: Finding by DiscriminatorColumn results in "Unknown field" exception

I've tried to search for this error but the fact that I haven't found anything leads me to believe that I'm doing something silly. I'll include the relevant code below, but basically I'm using multiple table inheritance (or Class Table Inheritance) and trying to use the Doctrine ORM findBy() method to query based on the discriminator column, which results in the following ORMException being thrown: "Unrecognized field: type".
Here is the code that triggers the exception:
// $this->em is an instance of \Doctrine\ORM\EntityManager
$repository = $this->em->getRepository('JoeCommentBundle:Thread');
return $repository->findOneBy(array(
'type' => $this->type,
'related_id' => $id
));
Here is the relevant code for the 'base' abstract entity:
<?php
namespace Joe\Bundle\CommentBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity
* #ORM\Table(name="comment_threads")
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="type", type="string")
* #ORM\DiscriminatorMap( {"story" = "Joe\Bundle\StoryBundle\Entity\StoryThread"} )
*/
abstract class Thread
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(name="related_id", type="integer")
*/
protected $relatedId;
/** MORE FIELDS BELOW.... **/
And finally, here is the code for the concrete thread entity:
<?php
namespace Joe\Bundle\StoryBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Joe\Bundle\CommentBundle\Entity\Thread as AbstractThread;
/**
* #ORM\Entity
* #ORM\Table(name="story_comment_threads")
*/
class StoryThread extends AbstractThread
{
/**
* #ORM\OneToOne(targetEntity="Story")
* #ORM\JoinColumn(name="story_id", referencedColumnName="id")
*/
protected $story;
}
I've double checked my schema, and the type column definitely exists, so I'm not sure what could be causing this. Any ideas? Thanks.
Rob, when querying your actually using the parent entity and trying to filter on the discriminator value. Instead, work on the repository relative to the child entity you want to fetch. Doctrine will do the rest for you. So in your case you want to get the repository for StoryThread.
$repository = $this->em->getRepository('JoeCommentBundle:StoryThread');
return repository->find($id);
You cannot use the discriminator column as a standard entity property.
Instead you may do the following:
$dql = 'SELECT e FROM JoeCommentBundle:Thread e
WHERE e.related_id = :related_id AND e INSTANCE OF :type';
$query = $em->createQuery($dql);
$query->setParameters(array(
'type' => $this->type,
'related_id' => $id
));
$record = $query->getSingleResult();

Resources