Symfony Doctrine disable cache - php

In my symfony project I have two entities that are related via one to many.
I need to find the first and last child, so I use repository functions that look like this:
public function getFirstPost(Topic $topic)
{
$query = $this->createQueryBuilder('t')
->addSelect('p')
->join('t.posts', 'p')
->where('t.id = :topic_id')
->setParameter('topic_id' => $topic->getId())
->orderBy('p.id', 'ASC')
->setMaxResults(1)
->getQuery();
return $query->getOneOrNullResult();
}
public function getLastPost(Topic $topic)
{
$query = $this->createQueryBuilder('t')
->addSelect('p')
->join('t.posts', 'p')
->where('t.id = :topic_id')
->setParameter('topic_id' => $topic->getId())
->orderBy('p.id', 'DESC')
->setMaxResults(1)
->getQuery();
return $query->getOneOrNullResult();
}
So the only difference is in in ->orderBy(), for the first Post I use ASC and for the last I use DESC.
Now If I use one of those functions from my controller, the return the expected result and work just fine. But If I run them both at the same time from my controller, they return the same result, which they shouldn't.
My guess is that Doctrine caches these queries and the results somehow and that's why the return the same so I tried using $query->useResultCache(false) but that didn't do anything.
So my question is, why is this happening and how can I fix it?

Well, it is cache issue indeed, but mostly it is query issue. Instead of returning a post in these function you return the whole topic with joined posts.
What you can do is to rewrite these queries to select Post entity directly and join Topic entity to it which will be filtered by.
If you really(dont do this) need these queries to work you can detach first topic returned by one of those methods and then call the other method:
$this->getDoctrine()->getManager()->detach($firstTopic);

Related

Use contain() of ArrayCollection() class in DQL

I have this script.
It fetch the rows from DB and choose the row which belongs to a Category(CatData).
getCatDatas() returns \Doctrine\Common\Collections\ArrayCollection() class.
So it can use contain() to check.
However I want to put this method in DQL itself, is there any practice??
$result = array();
$articles = $em->createQuery("SELECT a FROM DefaultBundle:ArticleData a ")
->getResult();
foreach ($articles as $a){// I want to put this function in DQL.
if ($a->getCatDatas()->contain($cat)){
array_push($articles,$result);
}
}
Yes, you can use DQL and do a where condition on relation.
You can find more info here: https://symfonycasts.com/screencast/symfony3-doctrine-relations/relation-query
In particular, the method findAllRecentNotesForGenus in GenusNoteRepository.
I think you can do something similar:
public function findPostsByCategoryData(CategoryData $cat)
{
return $this->createQueryBuilder('a')
->andWhere('a.catDatas = :cat')
->setParameter('cat', $cat)
->getQuery()
->execute();
}
#Alessandro Filira started in the right direction but forgot to take into account that this is a to-many relationship that you want to filter on.
Doctrine supports MEMBER OF operator, which works a bit like IN but in the opposite direction. It allows you to check if the specific value that you have is found as an element in the related group.
So the code should be like:
public function findPostsByCategoryData(CategoryData $cat): array
{
return $this->createQueryBuilder('a')
->andWhere(':cat MEMBER OF a.catDatas')
->setParameter('cat', $cat)
->getQuery()
->getResult();
}
See Doctrine Docs for MEMBER OF usage: https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/dql-doctrine-query-language.html (use Ctrl+F and "member of")

Doctrine many to many: has user liked the article?

I have 2 Entities
User
Article
and a “likedByUsers” Many To Many relationship between both.
When I show an article, I want to know if the user has liked it so a heart icon is shown.
I've got this in the ArticleRepository:
public function findOneBySlug($slug,$userId): ?Pack
{
return $this->createQueryBuilder('p')
->andWhere('p.slug = :val')
->setParameter('val', $slug)
->addSelect('COUNT(u) AS userLike', 'p')
->leftJoin("p.users", 'u', 'WITH', 'u.id = :userId')
->setParameter('userId', $userId)
->getQuery()
->getOneOrNullResult()
;
}
But it throws an error:
Return value of App\Repository\ArticleRepository::findOneBySlug() must be
an instance of App\Entity\Article or null, array returned
I want to add "userLike" (bool) to the Article returned entity. Anyone can help me out?
calling addSelect(...) on a query builder might change the return type / format.
in your particular case, the former db result was something like [... all the article properties ...] which hydration and the getOneOrNullResult turns into one Article or null.
the new format looks like
[... all the article properties ..., userlike], which hydration turns into [Article, userlike] which can't possibly turned into one Article or a null result, because it's a "more complex" array.
So you have to use a different result fetcher. Depending on what the caller of your function expects as a return value (I would expect an article ^^) you maybe should rename the function or add a virtual property on article to hide the userlike or something, so you can return just the Article or null.
So the solution that I would choose:
$result = $this->createQueryBuilder(...)
//...
->getSingleResult();
if(!$result) {
// empty result, obviously
return $result;
}
// $result[0] is usually the object.
$result[0]->userLike = $result['userLike'];
// or $result[0]->setUserLike($result['userLike'])
return $result[0];
btw: $this->createQueryBuilder($alias) in a repository automatically calls ->select($alias), so you don't have to addSelect('... userLike', 'p') and just do addSelect('... userLike')

Eloquent queries work separately but not together in a subquery

I'm trying to fetch some data with a subquery using Eloquent but dding returns nothing. Separately, this
$discountArticles = $discountTableItemIdIn
->where('recipient_type', '=', 'article')
->toArray();
or this
$discountArticles = $discountTableItemIdIn
->where('recipient_id', '=', $articleId)
->toArray();
work fine.
However when I try something like this, it fails (or rather, returns nothing):
$discountArticles = $discountTableItemIdIn->where(function ($subQuery) {
$subQuery
->where('recipient_type', '=', 'article')
->where('recipient_id', '=', $articleId);
})->toArray();
I know I can do separate queries on the same collection and do an array_merge but I'd like to get this way working instead. Not sure what's happening.
So $discountTableItemIdIn is a collection of the entire table? That means you're gonna need a different function, as the ->where() logic on a collection is different from how it functions on a builder (eloquent) instance.
Try using filter():
$discountArticles = $discountTableItemIdIn->filter(function ($item) use($articleId) {
return $item->recipient_type == "article" && $item->recipient_id == $articleId;
})->toArray();
What this will do is filter your $discountTableItemIdIn collection for records that have a type of article and a recipient_id of whatever $articleId contains, return a new collection and convert that to an array.
Just a note, this is quite inefficient; you should try to avoid loading the whole table into a collection and just query the table directly using the subquery logic in your question.

Symfony2: Adding a join condition to a ManyToMany relationship

I have to change something in an existing Symfony2 project, but unfortunately I have never worked with Symfony2 before.
The database contains the following tables:
Location
========
id
....
Deal
========
id
deleted
...
deal_location
=================
deal
location
There is a Many-To-Many relationship between Deal and Location. This is mapped in the Location.orm.yml file like this:
manyToMany:
deals:
cascade: ['all']
targetEntity: Deal
mappedBy: locations
What I want to do is to exclude all deals which where deleted (deleted = 1) when reading the locations from the Database.
As I found out, this can be done in de LocationRepository class. In that class, I found the following function:
public function getFindAllByLatLonQueryBuilder($lat, $lon)
{
$qb = $this->createQueryBuilder('l');
$qb
->where('l.deleted IS NULL OR l.deleted = false')
->orderBy('(((ACOS(SIN((:lat*PI()/180)) *
SIN((l.latitude*PI()/180))+COS((:lat*PI()/180)) *
COS((l.latitude*PI()/180)) * COS(((:lon-l.longitude)*
PI()/180))))*180/PI())*60*1.1515*1.609344)', 'ASC')
->setParameter('lat', $lat)
->setParameter('lon', $lon)
;
return $qb->getQuery()->getResult();
}
I found a similar question and added the following line:
->leftJoin('l.deals', 'deals', 'WITH', 'deals.deleted = 0')
Unfortunately this doesn't work. How can I make this work?
Instead of having two conditions in your where clause I would leave only where('l.deleted IS NOT true AND deals.deleted IS NOT true') and would simply add the leftJoin clause.
Something like this should do the work:
public function getFindAllByLatLonQueryBuilder($lat, $lon)
{
$qb = $this->createQueryBuilder('l')
->leftJoin('l.deal', 'deal', 'WITH', 'deal.deleted IS NOT true') // you can also try 'deal.deleted != true'
->where('l.deleted IS NOT true')
->orderBy('(((ACOS(SIN((:lat*PI()/180)) *
SIN((l.latitude*PI()/180))+COS((:lat*PI()/180)) *
COS((l.latitude*PI()/180)) * COS(((:lon-l.longitude)*
PI()/180))))*180/PI())*60*1.1515*1.609344)', 'ASC')
->setParameter('lat', $lat)
->setParameter('lon', $lon)
;
return $qb->getQuery()->getResult();
}
try
->leftJoin('l.deals', 'deal', 'WITH', 'deal.deleted = 0')
you always have to join the object's name which in your case seems to be deals instead of deal given your yml.
Another thing which makes me wonder is why for location they check on deleted = null or false (see your code) but you check against deleted = 0. Are you sure this is the right check?
leftJoin() not help u, is only needed if u use data from it in query, it not exclude deals, because u in this query get only localization.
I think exclude localization with deleted deals not help to, because in some localization u will have deleted and not deals. It must be in other part of code where u get deals for localizations.
So u must first find where deals are get from db, to make it only take not deleted.

Doctrine ORM, two different querys produce the same result set

I'm using Doctrine 1.2 and Symfony 1.4.
In my action, I have two different query that return different result set. Somehow the second query seem to change the result (or the reference?) of the first one and I don't have any clue why..
Here is an example:
$this->categories = Doctrine_Query::create()
->from('Categorie AS c')
->innerJoin('c.Activite AS a')
->where('a.archive = ?', false)
->execute();
print_r($this->categories->toArray()); // Return $this->categories results, normal behavior.
$this->evil_query = Doctrine_Query::create()
->from('Categorie AS c')
->innerJoin('c.Activite AS a')
->where('a.archive = ?', true)
->execute();
print_r($this->categories->toArray()); // Should be the same as before, but it return $this->evil_query results instead!
Why Doctrine behave this way ? It's totally driving me crazy. Thanks!
To make it simple it seem like the Query 2 are hijacking the Query 1 result.
Use something like this between queries ($em - entity manager):
$em->clear(); // Detaches all objects from Doctrine!
http://docs.doctrine-project.org/en/2.0.x/reference/batch-processing.html
In the API docs for the toArray() method in Doctrine_Collection it says:
Mimics the result of a $query->execute(array(), Doctrine_Core::HYDRATE_ARRAY);
I suspect to answer this question to your satisfaction you're going to have to go through the source code.
This seems like it has to be an issue of $this->categories and $this->evil_query pointing to the same place. What are the results of $this->evil_query === $this->categories and $this->evil_query == $this->categories?
Hubert, have you tried storing the query into a separate variable and then calling the execute() method on it?
I mean something like:
$good_query = Doctrine_Query::create()
->from('...')
->innerJoin('...')
->where('...', false)
;
$evil_query = Doctrine_Query::create()
->from('...')
->innerJoin('...')
->where('...', true)
;
$this->categories = $good_query->execute();
$this->evil_query = $evil_query->execute();
It seems like both attributes (categories and evil_query) are pointing at the same object.
Erm, after both queries you print_r the result of the first query - change the last line to print_r($this->evil_query->toArray()); to see the difference :)

Categories