Doctrine - Query builder - Select few column of a leftjoin - symfony

I have two entities :
NOTE : id, title, description, isPublic, user
USER: id, email, firstname, lastname, password, salt, roles, tag
I would like to select just few columns of the user when I get all notes of my database (in order to don't get the password of the user for example)
So in the NoteRepository I have made a doctrine request like this :
$qb = $this->createQueryBuilder('n');
//Get the owner of the knowledge
$qb
->leftJoin('n.user', 'owner')
->addSelect('owner.tag as ownerTag, owner.firstname as ownerFirstname, owner.lastname as ownerLastname')
;
return $qb->getQuery()->getResult(Query::HYDRATE_ARRAY);
(I insist to do it with a join clause, because we can imagine more than one user like owner of the note)
And I get this following JSON response :
[
{
"0":{
"id":6,
"title":"A1",
"description":"A1",
"isPublic":false,
"ownerTag":"#02a4c022d8",
"ownerFirstname":"ama",
"ownerLastname":"ama"
}
},
{
"1":{
"id":7,
"title":"Z1",
"description":"Z1",
"isPublic":false,
"ownerTag":"#00a7bd24g8",
"ownerFirstname":"z",
"ownerLastname":"z"
}
}
]
But I would like this following response :
[
{
"0":{
"id":6,
"title":"A1",
"description":"A1",
"isPublic":false,
"owner":{
"tag":"#02a4c022d8",
"firstname":"ama",
"lastname":"ama"
}
}
},
{
"1":{
"id":7,
"title":"Z1",
"description":"Z1",
"isPublic":false,
"owner":{
"tag":"#00a7bd24g8",
"firstname":"z",
"lastname":"z"
}
}
}
]
I don't know how to get it. Thank you to helping me ;)

Alright, I found a solution.
I need to use the clause PARTIAL like this:
$qb = $this->createQueryBuilder('n');
//Get the owner of the knowledge
$qb
->leftJoin('n.user', 'owner')
->addSelect('PARTIAL owner.{id,tag,firstname,lastname}')
;
return $qb->getQuery()->getResult(Query::HYDRATE_ARRAY);

I found this https://github.com/Atlantic18/DoctrineExtensions/issues/118
I'm not sure you're able to do it straight with your queryBuilder.
But you could surely do it with some PHP work on your array :)

Related

Find entity with relation collection all match value

In my Symfony project, I have an "ExerciceComptable" and a "DocumentAttendu" entities.
There is a relation in ExerciceComptable that reference DocumentAttendu (OneToMany).
In DocumentAttendu, I have a property named "recu" which is a boolean.
I need to retrieve all "ExerciceComptable" that are completed, meaning that all "DocumentAttendu" for an "ExerciceComptable" have the property "recu" set to true.
How can I achieve that ?
ExerciceComptable
#[ORM\OneToMany(mappedBy: 'exercice', targetEntity: DocumentAttendu::class)]
private Collection $documentAttendus;
/**
* #return Collection<int, DocumentAttendu>
*/
public function getDocumentAttendus(): Collection
{
return $this->documentAttendus;
}
public function addDocumentAttendu(DocumentAttendu $documentAttendu): self
{
if (!$this->documentAttendus->contains($documentAttendu)) {
$this->documentAttendus->add($documentAttendu);
$documentAttendu->setExercice($this);
}
return $this;
}
public function removeDocumentAttendu(DocumentAttendu $documentAttendu): self
{
if ($this->documentAttendus->removeElement($documentAttendu)) {
if ($documentAttendu->getExercice() === $this) {
$documentAttendu->setExercice(null);
}
}
return $this;
}
DocumentAttendu
#[ORM\ManyToOne(inversedBy: 'documentAttendus')]
#[ORM\JoinColumn(nullable: false)]
private ?ExerciceComptable $exercice = null;
#[ORM\Column(nullable: true)]
private ?bool $recu = null;
public function getExercice(): ?ExerciceComptable
{
return $this->exercice;
}
public function setExercice(?ExerciceComptable $exercice): self
{
$this->exercice = $exercice;
return $this;
}
public function isRecu(): ?bool
{
return $this->recu;
}
public function setRecu(?bool $recu): self
{
$this->recu = $recu;
return $this;
}
What I tried
$qb = $this->createQueryBuilder( 'ec' );
$qb->join( 'ec.documentAttendus', 'da');
$qb->andWhere('da.recu = true');
This is not working properly. If just one "DocumentAttendu" have "recu" = true, then the query will find it. I need all "DocumentAttendu" to have "recu" = true, not just one out of five for example.
I also tried to use Criteria, but I don't really understand how that works. I tried some line with "having('COUNT')", etc...But I'm not sure I used it correctly.
Important point, I need to be in "ExerciceComptableRepository".
The easiest solution might be a subquery. More specifically, use the Expr class from doctrine. Using a "where not exists (subquery)", should give you the correct results.
You'd get something like:
// This subquery fetches all DocumentAttendu entities
// for the ExerciceComptable where recu is false
$sub = $this->getEntityManager()->getRepository(DocumentAttendu::class)->createQueryBuilder('da');
$sub->select('da.id');
$sub->where('da.exercice = ec.id');
$sub->andWhere('da.recu IS FALSE');
// We fetch the ExerciceComptable entities, that don't
// have a result from the above sub-query
$qb = $this->createQueryBuilder('ec');
$qb->andWhere($qb->expr()-not(
$qb->expr()->exists($sub->getDQL()) // This resolves to WHERE NOT EXISTS (...)
))
In short: you're fetching all the ExerciceComptable entities that do not have DocumentAttendu entities with recu = false
Note: if a ExerciceComptable entity doesn't have any documentAttendus, this query will also return that ExerciceComptable entity
My solution is not a full doctrine solution and could make performance issue for larger data, but i believe it could be a great way to deal with very specific case like this.
Lets talk about the correct Sql query before doctrine, it should be something like that :
SELECT ec.id FROM ExerciceComptable ec
INNER JOIN (SELECT COUNT(*) total, exercice_comptable_id FROM DocumentAttendu)
all_documents ON all_documents.exercice_comptable_id = ec.id // COUNT ALL document for each execice
INNER JOIN (SELECT COUNT(*) total, exercice_comptable_id FROM DocumentAttendu da WHERE da.recu = 1)
received_documents ON received_documents.exercice_comptable_id = ec.id // COUNT ALL received document for each execice
WHERE all_documents.total = received_document.total;
Then only the ExerciceComptable with a total documents = received document will be retrieved.
It's important to know that subquery inside select are bad for performance since it doest 1 query for each result (so if you have 100 ExerciceComptable it will do 100 subqueries) where subquery using join only do 1 query for the the whole query. This is why i builded my query like that.
The problem is you wont get entity object with a raw mysql function inside a repositories.
So you have two choice.
Using subqueries inside Doctrine DQL (which is painfull for very complexe case). I advise you to do it only if you have performance issue
Execute the first query with raw sql -> retrieve only the ids -> call doctrine function findBy(['id' => $arrayOfIds]) -> you have the object you're looking for.
It's a trick, it's true.
But i believe specific usecase with doctrine are often very hard to maintain. Where sql query can be easily tested and changed.
The fact is that only the first will be the one to maintain and the second query will always be very fast since query on id are very fast.
If you want to see a case of DQL with subquery look at : Join subquery with doctrine 2 DBAL
I gave you generic guideline and i hope it helped.
Just never forget : Never Ever do subequeries inside select or where. It has very bad performance since it does one subqueries on server side for each line of result. Use Inner / Left Join to do that

How can i get current user's id to use it in a dql statement?

I know that this question was asked by many but no one was answered even :
This reponse.
is not a good reponse for how do i get current user's id into a dql statement in Repository.
i see so much talking about it's not logic or it's a bad choice so what 's the good one to use the current user's id like a param into a dql statement or QueryBuilder and this what i do but doesn't work :
My Controller :
public function GetGroupsByStudentAction()
{
$em=$this->getDoctrine()->getManager();
$modeles=$em->getRepository("AcmeMyBundle:GroupMatiere")
->findGroupByStudent();
return($this->render("AcmeMyBundle:GroupMatiere:list.html.twig",array("modeles"=>$modeles)));
}
public function ConnectedUserIdAction()
{ $user = $this->container->get('security.token_bag')->getToken()->getUser();
return $user->getId();
}
MyServices :
<service id="serviceGroupe" class="Acme\MyBundle\Controller\GroupMatiereController"/>
MyRepository :
public function findGroupByStudent()
{
// $currentid = i dont know how i call the methode from the service;
$query=$this->getEntityManager()
->createQuery("SELECT m from AcmeMyBundle:GroupMatiere m WHERE m.idGroupe=?1")
->setParameter(1, $currentid);
return $query->getResult();
}
That's work if i choose for the $currentid=1; But i need connected user id .
Thanks For Help and any other suggestion to change the logic i will be happy !
Define your repository method like this:
public function findGroupByStudent($student)
{
$query=$this->getEntityManager()
->createQuery("SELECT m from AcmeMyBundle:GroupMatiere m WHERE m.idGroupe=?1")
->setParameter(1, $student->getId());
return $query->getResult();
}
And then in the controller pass the Student entity that belongs to the currently logged in user, e.g.:
$modeles=$em->getRepository("AcmeMyBundle:GroupMatiere")
->findGroupByStudent($this->getUser()->getStudent());

Symfony2 dynamic queryBuilder

I'm making a tool in which a user can view data from an entity, where they can choose what data and how they see the records.
I created a form with two date fields (start and end) and a list of fields that correspond to data counts and sums of the entity.
My question is:
How I can create a dynamically QueryBuilder that allows me to add fields based on what the user wants to see?
EDIT for Symfony2 dynamic queryBuilder
public function reportData($fields, $dateStart, $dateFinish)
{
$em = $this->getEntityManager()
->getRepository('AcmeBundle:Entity');
$query = $em->createQueryBuilder('e');
foreach($fields as $field)
{
switch($field)
{
case 'totalResults':
$query->setect('SUM(e.id) AS '.$field);
break;
}
}
$query->addWhere('e.dateStart >= :dateStart');
$query->addWhere('e.dateFinish <= :dateFinish');
...
Something like this ? You store all your select queries in an array, then pass the array to the query builder after testing each of your fields.
public function reportData($fields, $dateStart, $dateFinish)
{
$em = $this->getEntityManager()
->getRepository('AcmeBundle:Entity');
$query = $em->createQueryBuilder('e');
$select_array = array();
foreach($fields as $field)
{
switch($field)
{
case 'totalResults':
$select_array[] = 'SUM(e.id) AS '.$field;
break;
}
}
$query->select($select_array);
$query->addWhere('e.dateStart >= :dateStart');
$query->addWhere('e.dateFinish <= :dateFinish');
....
Basically, you want to keep on adding the
Select Fields
based upon the conditions.
So, the solution is simple.
You can use,
$queryBuilder->addSelect();
See Doctrine Query Builder Documentation
I would do a regular full query then filter it into a not doctrine object (dao/dto) then display it.
This way you can do the complex and optimized query first, then filter the result on whatever you want, even if it's not related to the query itself

symfony2 QueryBuilder with a json field

Sorry for my english.
I'm blocked to a point and I wish you help me.
I have a json field in my entity. I want, with querybuilder, get statut of profil1.
Example from my field in my table in the BDD : (saved like this)
{
"Profil1":{
"statut":"première visite",
"site":"site officiel"
},
"Profil2":{
"statut":"ancien",
"motif":"facebook"
}
}
Something like this:
$queryBuilder = $entityManager->createQueryBuilder()
->select('e')
->from('ACMEDemoBundle:YourEntity', 'e');
$result = $queryBuilder
->getQuery()->getResult();
foreach ($result as $entity)
{
$jsonObject = json_decode($entity->getJsonField()); //where jsonField is the name of the field containing the json
$profile1Statut = $jsonObject['Profil1']['statut'];
}
Probably you want to make some sanity checks also. And depending of the size of your table, it may be a little bit overkilling.

Symfony2 Doctrine Join Entity

I have a entity with the next join:
class blogComment
{
....
/**
* #ORM\OneToMany(targetEntity="BlogComment", mappedBy="replyTo")
*/
protected $replies;
....
}
Now I get successfully all the replies. But I only want to get: where active = true
How to do that?
Oke if you guys recommend to get the comments by query in the controller how to build a nested array to get result like this:
For solving the part where you only want active replies there are a couple of options:
1) Use some custom DQL in a repository:
$dql = 'SELECT bc FROM BlogComment bc WHERE bc.replyTo = :id AND bc.active = :active';
$q = $em->createQuery($dql)
->setParameters(array('id' => $id, 'active' => true));
2) Using ArrayCollection::filter() in the getter:
public function getReplies()
{
return $this->replies
->filter(function ($reply) {
return $reply->isActive();
})
->toArray();
}
3) Using ArrayCollection::matching() (Collection Criteria API) in the getter:
use Doctrine\Common\Collections\Criteria;
// ...
public function getReplies()
{
$criteria = new Criteria::create()
->where(Criteria::expr()->eq('active', true));
return $this->replies
->matching($criteria)
->toArray();
}
4) Use Filters. These can add where clauses to queries regardless of where that query is generated. Please see the docs.
If you want to be able to fetch an entire set of replies, nested and all, in a single query, you need to implement some kind of "tree" of "nested set" functionality. I'd advise you to look at the Tree behavior of l3pp4rd/DoctrineExtensions.
http://doctrine-orm.readthedocs.org/en/latest/reference/dql-doctrine-query-language.html
Wherever you are obtaining your blog comments to display them (probably on a controller), you need to customise your query so that only the active replies are extracted. Something like:
$query = $em->createQuery('SELECT b FROM blogComment b JOIN b.replies r WHERE r.active = :active');
$query->setParameter('active', true);
$blogComments = $query->getResult();
EDIT:
For your new requirement of nested replies, you would need to specify a relationship between a comment entity and its parent comment.

Resources