avoid duplication in many to many relationship - Doctrine DQL - php

noob alert;
I have Post and Tag Entities like this:
Post.php
/**
* Many posts can have many tags. This is the owning side.
* #ORM\ManytoMany(targetEntity="Tag", inversedBy="posts")
* #ORM\JoinTable(name="post_tag")
*/
protected $tags;
Tag.php
/**
* Many tags can belong to many different posts. This is the inverse side
* #ORM\ManytoMany(targetEntity="Post", mappedBy="tags")
*/
protected $posts;
Now, I want to query all posts with their tags.
For that, I'm using queryBuilder in my Repository and successfully able to retrieve results.
$query = $this->createQueryBuilder('P')
->select('P.title, T.name')
->leftJoin('P.tags', 'T')
->where('P.id = 1')
->getQuery()
->execute();
But as you can possibly imagine, this query fetches duplicates. So, if I had two tags for a post, there would be two similar posts inside the array.
Output
array(
array(title=>'Post One', name=>'php'),
array(title=>'Post One', name=>'python'),
)
Is there a doctrine way to turn these tags into an array collection and stuff this back into the final post array.

Related

include the related entities values in the result of a doctrine query

I'm doing a Doctrine query, and the results include the properties of the queried entity, but not it doesn't "follow" and fetch the values of the related entities.
E.g. I have this query inside of the OfertaRepository:
$query = $this->createQueryBuilder('o');
$query->select('o');
$query->leftJoin(Pais::class, 'p', 'with', 'o.idPais = p.id');
$query->leftJoin( Contrato::class, 'c', 'with', 'o.idTipoContrato = c.id');
$query->andWhere('p.nombreCorto = :pais');
$query->andWhere('o.activa = 1');
$query->andWhere('o.eliminado is NULL');
$query->andWhere('o.caducidad > :hoy');
$query->setParameter('pais', $pais)->setParameter('hoy', new \DateTime());
return $query->getQuery()->getArrayResult();
The entity Oferta has:
/**
* #var \Application\Entity\Pais
*
* #ORM\ManyToOne(targetEntity="Application\Entity\Pais")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="id_pais", referencedColumnName="id", nullable=true)
* })
*/
private $idPais;
/**
* #var \Application\Entity\TipoContrato
*
* #ORM\ManyToOne(targetEntity="Application\Entity\TipoContrato")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="id_tipo_contrato", referencedColumnName="id", nullable=true)
* })
*/
private $idTipoContrato;
All referenced tables ( tipo_contrato, pais, etc) exist and the relationships work for filtering data on queries, for example.
But my results from the $query->getQuery()->getArrayResult() expression do not include data from these relations.
It wont even include the ids from this fields. E.g., for any one record it will only include these fields:
id
alt
nombre"
nombreAlt
descripcion
poblacion
vacantes
horarioIni
horarioFin
url
tag
creado
caducidad
modificado
eliminado
activa
Which are all valid fields, but do not include any fields that are a many-to-one relationship.
How can I include these values for this query, and for this query only? E.g. without changing the entities definition so all queries are affected?
Just looking at: $query->select('o'); I'd say (think of it in terms of SQL) that is all you are selecting. Get some commas in there. I'd try something like this:
$query->select('o', 'p', 'c');
What I was trying to do first time should have been an array. Not a comma delimited string like I showed. See source:
$selects = is_array($select) ? $select : func_get_args();
So either an array or a list of arguments should work.
Update:
Maybe you are just missing the from? Totally missed that. Here I'll share some code that I know works. Maybe you will see something. Pretty sure you just need a from clause and the multiple obj references on the select part.
$qb = $this->_em->createQueryBuilder();
$qb->select('aimg', 'ai');
$qb->from('OAS\Entity\AuctionImage', 'aimg');
$qb->leftJoin('aimg.auctionItem', 'ai', \Doctrine\ORM\Query\Expr\Join::WITH, 'ai.lotNumber = aimg.lotNumber AND ai.auction = aimg.auction');
$qb->where('aimg.auction = :auction')->setParameter('auction', $this->_auction);
$qb->andWhere('aimg.isDeleted = :boolFalse')->setParameter('boolFalse', 0);
$qb->andWhere('aimg.auctionItem IS NULL');
$qb->orderBy('aimg.lotNumber', 'asc');
$results = $qb->getQuery()->getResult();

Doctrine load relations from an Arraycollection

I'm looking for an elegant way to do the following:
Lets say I have an Entity with a OneToMany Relation, e.g.
class Parent
{
/**
* #ORM\OneToMany(targetEntity="Child")
*/
private $children;
public function __construct()
{
$this->children = new ArrayCollection();
}
}
class Child
{
/**
* #ORM\ManyToOne(targetEntity="Parent")
*/
private $parent;
}
Now in my logic I have to filter those parents which can't be done by queries only. So I end up with an ArrayCollection of parents, e.g:
$parents = new ArrayCollection([
$parent1,
$parent2,
$parent3
]);
Now from all those parents, I would like to join the children with one query.
How could I do this with doctrine.
I know I could just loop over the collection and call ->getChildren() on each parent.
But I possibly have hundreds of parents (which would mean hundreds of queries). Which I would like to avoid.
Before anyone says 'do a LEFT JOIN on the parents when you get them', I cannot filter the parents I need with only queries. So this is why I cannot just LEFT JOIN.
Cookies for thoughs !
In order to avoid the N + 1 selects problem, I would suggest the following solution which doesn’t need JOINs and uses two separate queries. This is the most efficient solution.
First, retrieve all parents:
$parents = $em->createQueryBuilder()
->select("p")
->from("YourFoobarBundle:Parent", "p")
->where(/*...*/)
->setParameter(/*...*/)
->indexBy("p.id")
->getQuery()->getResult();
Now load all children of those parents:
$children = $em->createQueryBuilder()
->select("c")
->from("YourFoobarBundle:Child", "c")
->where("IDENTITY(child.parent) IN (?1)")
->setParameter(1, array_keys($parents))
->getQuery()->getResult();
The great thing about Doctrine is that now all needed entities are stored in memory. So when you do a $parent->getChildren(), you don’t trigger a new DB query (unless the children have additional relations themselves).
NOTE: If you always need all children of the selected parents, you should mark the $children for eager loading:
/**
* #ORM\OneToMany(targetEntity="Child", fetch="EAGER")
*/
private $children;
In this case, Doctrine will always (!) fetch the needed children automatically.
You can make a sub query and use it inside a clause (a where in expression) of your other query. It is very much like #lxg describes, but you can do all this in one single query for increased performance (You don't have to execute the queries separately).
$qb = $entityManager->createQueryBuilder();
$sub = $qb->select('p')
->from('Application\Entity\Parent', 'p')
->where(/*...*/)
->setParameter(/*...*/)
$children = $qb->select('c')
->from('Application\Entity\Child', 'c')
->where($qb->expr()->in('c.parent', $sub->getDQL()))
->getQuery()
->getResult();

Symfony2 - Doctrine - Filtering arrayCollection to get only one object

I am new in Doctrine. I have entities and associations works fine.
My issue:
I have three entities User, Comment, Comment_status.
Comment is connected to main table Finding but this doesnt matter in this case.
Association: Comment <---1:n---> Comment_status <---n:1---> User
That should handle relation between users wathing list of comments and setting like/dislike for every of them.
I am using TWIG to show a list of comments on the page and thats enough for all data like "date", "like count" etc but if I want to get a comment_status for specified user (loged in wathing a page) I dont know how to get this.
if I return array to TWIG "comments" , then loop it for every one of them as "comment" i can access using {{ comment.content }} to data but with comment_status I have no idea bacause it is an array and I just want one element of this array which match to logged in user ID.
I suppose I have to make some DQL ask to DB but have no idea how to do this.
I read some about Criteria in Doctrine but still no idea how to use it.
Can anybody give an step by step solution?
parts of my code:
Controller
> $comments = $em->getRepository('MySpecialBundle:Comment')
> ->findBy(array('finding' => $finding));
>
> return array('comments' => $comments );
Comment entity (part which create association)
> /** * #ORM\OneToMany(targetEntity="Comment_status",
> mappedBy="comment", cascade={"persist", "remove"}, orphanRemoval=true)
> */ protected $commentStatuses;
Comment_status entity (part which create association)
> /** * #ORM\ManyToOne(targetEntity="Comment",
> inversedBy="commentStatuses") * #ORM\JoinColumn(name="comment_id",
> referencedColumnName="id", nullable=false) */ protected $comment;
/**
> * #ORM\ManyToOne(targetEntity="My\MainBundle\Entity\User",
> inversedBy="commentStatuses") * #ORM\JoinColumn(name="user_id",
> referencedColumnName="id", nullable=false) */ protected $user;
User entity (part which create association)
/**
* #ORM\OneToMany(targetEntity="My\SpecialBundle\Entity\Comment_status", mappedBy="user", cascade={"persist"})
*/
protected $commentStatuses;
I want to have a way to get (instead an array collection of all "commentStatuses") only one object which contains an user id of user logged in.
Thank you for help.
You can select and hydrate only the entities matching your criteria in a doctrine querybuilder query. This way you get an already filtered collection. E.g. Something like:
$qb = $this->getEntitymanager()->createQueryBuilder();
$user = $qb->select('u, cs, c')
->from('YourBundle:User', 'u')
->join('u.commentStatuses', 'cs')
->join('cs.comment', 'c')
->where($qb->expr()->eq('u', ':user')
->setParameter('user', $yourLoggedInUser)
->getQuery()->getResult();
This will get you your user and comments/statuses only applicable to that user.
Thanks to #Richard I found a way to solve this.
The right answer is here:
$qb = $this->getEntitymanager()->createQueryBuilder();
$user = $qb->select('c, cs')
->from('MyBundle:Comment', 'c')
->leftjoin('c.commentStatuses', 'cs', 'WITH', 'cs.user = :user')
->setParameter('user', $myLoggedInUser)
->getQuery()->getResult();
Leftjoin - gives all records from Comment not only that contains commentStatus
WITH - choose only right commentStatuses
If I would use #Richard version with second join and WHERE it would show only Comments where user and commentStatus exists.
Anyway thans for help

Symfony2 and Doctrine: how to sort entity collection by dependant entity field

In my sf2 project I access entity collection by calling:
$user_payment_info_datas = $user->getUserPaymentInfoDatas();
In the User entity there is:
/**
* #ORM\OneToMany(targetEntity="UserPaymentInfoData", mappedBy="user")
* #ORM\OrderBy({"payment_info" = "ASC", "payment_info_data" = "ASC"})
*/
private $user_payment_info_datas;
So it's 1:n relation and user has many UserPaymentInfoData's. However, there is another entity called PaymentInfoData that contains the actually values for UserPaymentInfoData's. So the relation is
User -> UserPaymentInfoData -> PaymentInfoData.
So in terms of annotations in UserPaymentInfoData it is:
/**
* #ORM\ManyToOne(targetEntity="PaymentInfoData", inversedBy="user_payment_info_datas")
* #ORM\JoinColumn(name="payment_info_id", referencedColumnName="id")
* #ORM\OrderBy({"title"="ASC"})
*/
private $payment_info_data;
I need to sort the collection returned by
$user_payment_info_datas = $user->getUserPaymentInfoDatas();
ascending by a field from PaymentInfoData (let's call it 'title') and NOT UserPaymentInfoData.
Can I do this with Doctrine annotations? Or without writing DQL?
I know I can do it with:
$user_payment_info_datas = $em->getRepository('STMainBundle:UserPaymentInfoData')
->createQueryBuilder('upid')
->innerJoin ('upid.payment_info_data', 'pid')
->where('upid.user = :user')
->addOrderBy('upid.payment_info', 'ASC')
->addOrderBy('pid.title', 'ASC')
->setParameter('user', $user)
->getQuery()
->getResult();
but the question is, whether it's possible to stay with only annotations as I need to fix it in a few places and it would be convenient to just change annotations and not create query builder in two places.

Symfony2/Doctrine joined query

I need to create a simple query that produces a result set of a database entry plus the username of the person that posted it.
I've tried to setup the associations properly but I'm not sure if that's right either. I'm finding the whole idea of using these small string identifiers quite confusing. Surely there must be a simpler way of doing a join?
My two entities:
class Users
{
// ...
/**
* #ORM\Column(type="string")
* #ORM\OneToMany(targetEntity="Titles", mappedBy="addedBy")
*/
protected $username;
// ..
}
and
class Titles
{
// ....
/**
* #ORM\Column(type="string")
* #ORM\ManyToOne(targetEntity="Users", inversedBy="username")
*/
protected $addedBy;
// ....
}
with the following in the controller:
$titles = $em->createQueryBuilder()
->select('t.*', 'u.*')
->from('dvdLoggerdvdBundle:Titles', 't')
->leftJoin('t.addedBy', 'u')
->addOrderBy('t.title', 'DESC')
->getQuery()
->getResult();
I'm getting the following error:
[Semantical Error] line 0, col 69 near 'u ORDER BY t.title': Error: Class
dvdLogger\dvdBundle\Entity\Titles has no association named addedBy `
Update 1
I made all the changes suggested by Tom and did lots of reading!
It appears that in order to overcome the lazy loading feature I need to carry out a leftJoin. I have rewritten my query as follows:
public function getAllTitles()
{
// view all records in db
$titles = $this->createQueryBuilder('t')
->select('t, u')
->leftJoin('t.addedBy', 'u')
->addOrderBy('t.title', 'DESC');
return $titles->getQuery()->getResult();
}
I am getting a result set, but the addedBy is returning NULL when I dump the result set. As far as I'm aware shouldn't this pull the associated field in from the other table?
Best practice is to reference the entity by its id, you are trying to reference it using the username. The inversed field should also be a specific field not an existing one that holds data. And keep it mind this field is optional and defines the associations as bidirectional, for the specified use case you don't actually need it as you are joining from the Titles entity. I would advice reading the doc here http://symfony.com/doc/current/book/doctrine.html#entity-relationships-associations as well as here http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html
Bidirectional association (w/ inversed field)
First get rid of that line:
#ORM\Column(type="string")
In your $addedBy annotations and change inverseBy="username" to inversedBy="titles" (note the typo)
You optionaly could add
#ORM\JoinColumn(name="user_id", referencedColumnName="id")
Then in your Users Entity add
/**
*
* #ORM\OneToMany(targetEntity="Titles", mappedBy="addedBy")
*/
protected $titles;
And get rid of
* #ORM\OneToMany(targetEntity="Titles", mappedBy="addedBy")
In your $username annotations
Last make sure you update the database schema
Then your query should return the expected result.
Unidirectional association (w/out inversed field)
Get rid of
* #ORM\OneToMany(targetEntity="Titles", mappedBy="addedBy")
In your $username annotations
Then get rid of that line in your $addedBy annotations:
#ORM\Column(type="string")
As well as inverseBy="username"
You optionaly could add
#ORM\JoinColumn(name="user_id", referencedColumnName="id")
Last make sure you update the database schema
Then your query should return the expected result.

Categories