Using Doctrine, how do you access Entity methods from a EntityRepository? - symfony

In the Symfony "Book", they talk about Entities with references to other entities. Like, in my case, if I have a "Post" Entity with many "Comment" Entities referencing it, I can load the Post by its ID and then do $post->getComments().
The Comment are lazy-loaded, it seems, and I would have to either go one-by-one through them, loading each (which is clearly wrong) or load them all in a separate query (which I could do, but I don't know where, specifically, to put that query). The Book, however, suggests: "Of course, if you know up front that you'll need to access both objects, you can avoid the second query by issuing a join in the original query." It recommends I add a method to my (currently-empty) postRepository class. I do, and it looks like this:
public function loadPostFull($pid)
{
return $this->getEntityManager()
->createQuery('
SELECT p, c FROM BUNDLENAME:Post p
JOIN p.comments c
WHERE p.id = :id')
->setParameter('id', $pid);
try {
return $query->getSingleResult();
} catch (\Doctrine\ORM\NoResultException $e) {
return null;
}
}
And then in my controller I do:
$fullPost = $this->getDoctrine()
->getRepository('BUNDLENAME:Post')
->loadPostFull($id);
$id = $fullPost->getId();
But my simple request for $id gives a "Fatal error: Call to undefined method Doctrine\ORM\Query::getId()". I know the controller is finding the loadPostFull method correctly, because if I typo the name it will fail there. It's just not returning a proper Post entity. If I change the controller to call ->find($id) instead of ->loadPostFull($id) I can use all the internal methods of the Post id.
The manual seems to indicate I can do that! What's wrong?

I believe you have a typo :). Look at the first line of your code. You have a return statement! So you return the Query object and try { } catch() { } is never reached. You should put it like this:
$query = $this->getEntityManager()
->createQuery('
SELECT p, c FROM BUNDLENAME:Post p
JOIN p.comments c
WHERE p.id = :id')
->setParameter('id', $pid);
try {
return $query->getSingleResult();
} catch (\Doctrine\ORM\NoResultException $e) {
return null;
}

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());

Query only works if I run a query before it

I want to get the schools the user is in, but for some reason I can only access it by running a query to the school table that is otherwise unrelated. Here is my code:
This doesn't work (within the controller):
$schoolsEnrolled = $this->getUser()->getSchools();
The result is an Array with a School object with all it's properties as null (other than id for some reason).
This does work (within the controller):
//unrelated query
$repository = $this->getDoctrine()->getRepository('AcmeMainBundle:School');
$query = $repository->createQueryBuilder('s')->getQuery();
$schools = $query->getResult();
//the query I care about
$schoolsEnrolled = $this->getUser()->getSchools();
The result is an array of schools as desired.
Here are the related methods:
In the School Class:
public function getSchools(){
$schools = array();
foreach ($this->schoolHasUsers as $key=>$schoolHasUser){
$schools[] = $schoolHasUser->getSchool();
}
return $schools;
}
In the SchoolHasUser Class:
public function getSchool()
{
return $this->school;
}
How can I get the query I care about to work without the unrelated query?
In doctrine object can be lazy-loaded. Then you call $schoolHasUser->getSchool(); you trully no query the database, only get proxy object. So try to get some property of it, example getName or getId. This action make a call to DB and fetch object.
One of the proper ways to do it is like this
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('YourBundle:YourEntity')->findAll();

symfony2 create custom query inside entity - getEntityManager() undefined

I am looking to run a custom query inside my entity class. I dont want to use table mapping for this query I just want to return an array of results but I would like the query to still log to the logger. I have stripped down the class and renamed to try and illustrate what I'm trying to achieve
// src/Name/ExampleBundle/Entity/ExampleEntity.php
namespace Name\ExampleBundle\Entity;
class ExampleEntity
{
public function getArrayFromExample(){
$results = $this->getEntityManager()
->createQuery("SELECT * FROM exmapleTable LIMIT 50")
->getResult();
return $results;
}
}
The above returns something like
Fatal error: Call to undefined method {path}\ExampleEntity::getEntityManager()
Your queries should be in an entity repository instead of the entity itself. http://mackstar.com/blog/2010/10/04/using-repositories-doctrine-2
Then you could do something like:
public function getArrayFromExample(){
$results = $this->_em
->createQuery("SELECT * FROM exmapleTable LIMIT 50")
->getResult();
return $results;
}

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