Query only works if I run a query before it - symfony

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

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

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

one to many setter without object

I would like to be able to set a foreign key just by its id.
Sometimes, for some long scripts, the fact that I need to give the full foreign object to my setter method force me to do some database queries, wasting resources.
$entity = new SomeEntity();
$entity->setIdAnswer(42);
$em->persist($entity);
Instead of
$world = $em->getRepositorye('My/Bundle:Answer')->findOneById(42);
$entity = new SomeEntity();
$entity->setIdAnswer( $world );
$em->persist( $entity);
How is it possible to occasionally set the foreign key with its integer key?
It would be great if we can do that without using some dirty code
Usually you can achieve exactly that with reference proxies:
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/advanced-configuration.html#reference-proxies
// $em instanceof EntityManager, $cart instanceof MyProject\Model\Cart
// $itemId comes from somewhere, probably a request parameter
$item = $em->getReference('MyProject\Model\Item', $itemId);
$cart->addItem($item);

Symfony2 Select one column in doctrine

I'm trying to refine the query trying to select fewer possible values ​​..
For example I have an entity "Anagrafic" that contains your name, address, city, etc.,
and a form where I want to change only one of these fields, such as address.
I have created this query:
//AnagraficRepository
public function findAddress($Id)
{
$qb = $this->createQueryBuilder('r')
->select('r.address')
->where('r.id = :id')
->setParameter('id', $Id)
->getQuery();
return $qb->getResult();
}
there is something wrong with this query because I do not return any value, but if I do the query normally:
//Controller
$entity = $em->getRepository('MyBusinessBundle:Anagrafic')->find($id);
Return the right value.
How do I do a query selecting only one column?
Since you are requesting single column of each record you are bound to expect an array. That being said you should replace getResult with getArrayResult() because you can't enforce object hydration:
$data = $qb->getArrayResult();
Now, you have structure:
$data[0]['address']
$data[1]['address']
....
Hope this helps.
As for the discussion about performance in comments I generally agree with you for not wanting all 30 column fetch every time. However, in that case, you should consider writing named queries in order to minimize impact if you database ever gets altered.
You can use partial objects to only hydrate one field and still return a object.
This worked for me:
$qb = $repository->createQueryBuilder('i')
->select('i.name')
->...
Use partial objects like this to select fields
$qb = $this->createQueryBuilder('r')
->select(array('partial r.{id,address}'))
...
Put your field names between the brackets

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