load related entiites from doctrine collection symfony2 - php

I have category table and make table. Both tables related by third category_make table creating many to many relationship.
/**
* #var \Doctrine\Common\Collections\Collection
*
* #ORM\ManyToMany(targetEntity="Ladisi\MotorsBundle\Entity\Make", inversedBy="catogory", fetch="EAGER")
* #ORM\JoinTable(name="catogory_make",
* joinColumns={
* #ORM\JoinColumn(name="catogory_id", referencedColumnName="cat_id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="make_id", referencedColumnName="make_id")
* }
* )
*/
private $make;
I want to get makes that belongs to particular category. I have tried,
$query = $em
->createQuery(
'SELECT c, m FROM LadisiMotorsBundle:Catagory c
JOIN c.make m
WHERE c.catId= :id'
)->setParameter('id', $id);
$result = $query->getResult();
but every time I get only category fields, make entity is not available in result. I also tried to get makes just by calling getMakes method on catagory object, it also returns null (not entity, i guess proxy). How do i solve this. Any help would be great.

You've already created a linking table, so the only thing you have to do (if you have your entities configured correctly) in your controller:
$catagory = $this->getDoctrine()->getManager()->getRepository('LadisiMotorsBundle:Catagory')->findOneBy(['id' => $id]);
$catagory->getMakes();

Related

Doctrine ORM: filter articles by 2 categories

In our online shop we are selling spare parts for industrial computer systems. Therefore we have 2 main categories: spare parts and computer systems. Users can either search their needed spare parts via the spare parts category or via the computer system. If a user selected his computer system on the homepage, he should get a list of spare parts categories matching with his computer system.
Therefore i need to query all spare parts categories and filter them by all articles, that are also in the category of the preselected computer system. Otherwise the user could see an empty category.
I have 2 doctrine entites: article and category - each of them related many to many to the other:
Category Entity
/**
* #ORM\Table(name="categories")
* #ORM\Entity(repositoryClass="Repository")
*/
class Category extends ModelEntity
{
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="Models\Article\Article")
* #ORM\JoinTable(name="articles_categories",
* joinColumns={
* #ORM\JoinColumn(name="categoryID", referencedColumnName="id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="articleID", referencedColumnName="id")
* }
* )
*/
private $articles;
Article Entity:
/**
* #ORM\Entity(repositoryClass="Repository")
* #ORM\Table(name="articles")
*/
class Article extends ModelEntity
{
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="Models\Category\Category")
* #ORM\JoinTable(name="articles_categories",
* joinColumns={
* #ORM\JoinColumn(name="articleID", referencedColumnName="id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="categoryID", referencedColumnName="id")
* }
* )
*/
protected $categories;
I'm trying to query all categories with articles in 2 categories. For example: get all categories, that has articles in "this" category, but only where same articles are also in "that" category.
Unfortunately i have no idea, how to do this. Can anyone help me out?
To find categories who belongs to given list of articles (each category must has an association with each article from given list) then you can make use of some aggregation
$articleIds = [1,2,3,4,5];
$qb = $this->createQueryBuilder('c');
$qb->addSelect('COUNT(DISTINCT a.id) AS HIDDEN total_articles')
->innerJoin('c.articles', 'a')
->add('where', $qb->expr()->in('a', $articleIds))
->groupBy('c.id')
->having('total_articles = '.count($articleIds))
->getQuery()
->getResult();
Symfony2 - Doctrine2 QueryBuilder WHERE IN ManyToMany field
Doctrine QueryBuilder: ManyToOne Relationship where more than one subEntity must match
Sql/Doctrine query to find data with multiple condition with Many to Many associations
Convert SQL with subquery to Doctrine Query Builder
Thanks to M Khalid Junaid i finally figured it out.
This is my final query:
// select all active root categories from the online shop
$builder = Shopware()->Models()->createQueryBuilder();
$builder->from('Models\Category\Category', 'c')
->select('c as category')
->andWhere('c.active = 1')
->andWhere('c.parentId = 0');
// if the user already selected his computer system,
// only get articles, that are in both categories: the
// category of this particular computer system and the
// category of spare parts
if ($this->computerSystemCategoryId) {
$builder->addSelect('COUNT(DISTINCT a2.id) AS total_system_articles')
->leftJoin('c.articles', 'a2')
->leftJoin('a2.categories', 'c2')
->groupBy('c.id')
->having('total_system_articles > 0')
->andWhere($builder->expr()->in('c2', [$this->computerSystemCategoryId]));
}
$query = $builder->getQuery();
$categories = $query->getResult();
With this query i can get only the spare parts, that are associated with the particular spare parts category, but also with the particular computer system category id.

Fetch entity without related objects doctrine

I have the following classes:
class Category {
/**
* #ORM\OneToMany(targetEntity="Product", mappedBy="category")
*/
private $products;
...
}
class Product {
...
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="products")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
*/
private $category;
...
}
when i try to fetch one product from my db like this:
$query = $doctrineManager->createQuery(
"
SELECT p FROM AppBundle:Product p
WHERE p.id = :id
"
)->setParameter('id', $id);
$result = $query->getSingleResult();
i get not only my product, but also get category with all products (except the one i found). So, how can i fetch only model what i want without any related model?
They are just stubs, you don't actually fetch any related entity information unless you are using fetch=EAGER.
This answer explains it pretty well.
What is the difference between fetch="EAGER" and fetch="LAZY" in doctrine
In summary, you can't get rid of the associations, but they don't load the other entities until you call the data unless you specifically request otherwise. So don't worry about it.

How does inner join work on a many-to-many relationship using Doctrine and Symfony2

I recently worked out an issue with querying ManyToMany relationship join tables, the solution was same as this answer and was wondering how it works.
lets say i have a simple ManyToMany relationship between groups and team, there will be a groups_team tables that will automatically be created here
groups entity
/**
* Groups
*
* #ORM\Table(name="groups")
* #ORM\Entity(repositoryClass="AppBundle\Model\Repository\GroupsRepository")
*/
class Groups {
/**
* #ORM\ManyToMany(targetEntity="Team", inversedBy="group")
*/
protected $team;
public function __construct() {
$this->team = new ArrayCollection();
}
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="groupname", type="string", length=255)
*/
private $groupname;
//obligatory getters and setters :)
team entity
/**
* Team
*
* #ORM\Table(name="team")
* #ORM\Entity(repositoryClass="AppBundle\Model\Repository\TeamRepository")
*/
class Team {
/**
* #ORM\ManyToMany(targetEntity="Groups", mappedBy="team")
*/
protected $group;
public function __construct(){
$this->group = new ArrayCollection();
}
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="teamname", type="string", length=255)
*/
private $team;
//[setters and getters here]
in order to get all the teams in a group i would have to query the groups_team table.i would have directly queried the table in just mysql but in symfony i have to do this
$groups = $em->getRepository("AppBundle\Model\Entity\Groups")->findBy(array('tournament' => $tournament->getId()));
//get all teams with group id in groups_team table
foreach ($groups as $group) {
$teamsingroup = $em->getRepository("AppBundle\Model\Entity\Team")->createQueryBuilder('o')
->innerJoin('o.group', 't')
->where('t.id = :group_id')
->setParameter('group_id', $group->getId())
->getQuery()->getResult();
echo "</b>".$group->getGroupname()."</b></br>";
foreach ($teamsingroup as $teamingroup) {
echo $teamingroup->getTeam()."</br>";
}
}
Can someone explain to me how the innerJoin is working and what is the concept behind this, maybe a few documentation to learn about this. are there better way to do this with symfony and doctrine.
Using ManyToMany between 2 entities involves a third table generally called as a junction table in this type of relation when you build a DQL (doctrine query) doctrine automatically joins junction table depending on the nature of relation you have defined as annotation so considering your query
$teamsingroup = $em->getRepository("AppBundle\Model\Entity\Team")
->createQueryBuilder('o')
->innerJoin('o.group', 't')
You are joining Team entity with Group entity in innerJoin('o.group') part o is the alias for Team entity and o.group refers to property defined in Team entity named as group.
/**
* #ORM\ManyToMany(targetEntity="Groups", mappedBy="team")
*/
protected $group;
Which has a ManyToMany annotation defined for this type of relation doctrine joins your team table first with junction table and then joins your junction table with groups table and the resultant SQL will be something like
SELECT t.*
FROM teams t
INNER JOIN junction_table jt ON(t.id = jt.team_id)
INNER JOIN groups g ON(g.id = jt.group_id)
WHERE g.id = #group_id
Another thing related your way of getting team for each group you can minimize your code by excluding createQueryBuilder part within loop, once you have defined teams property as ArrayCollection i.e $this->team = new ArrayCollection(); on each group object you will get collections of teams associated to that particular group by calling getTeam() function on group object similar to below code.
foreach ($groups as $group) {
$teamsingroup = $group->getTeam();
echo "</b>".$group->getGroupname()."</b></br>";
foreach ($teamsingroup as $teamingroup) {
echo $teamingroup->getTeam()."</br>";
}
}
I guess it's literally select statement with INNER JOIN using key columns defined entity class as mappedBy or inversedBy.
Why don't you have a look of doctrine log and see what the native sql is composed?
How to get Doctrine to log queries in Symfony2 (stackoverflow)
http://vvv.tobiassjosten.net/symfony/logging-doctrine-queries-in-symfony2/ (some code examples)
I don't know your user story behind this, but I also heard that it is recommended to use one to many relationship instead of many to many, unless there is a strong reason to do so, as most of cases can be handled by one to many by reconsidering models.

Select many to many entity collection using DQL

I have to entities that have ManyToMany relation with linking table. Like this:
class User
{
/**
* #ORM\ManyToMany(targetEntity="Post")
* #ORM\JoinTable(name="favorite_posts",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="post_id", referencedColumnName="id")}
* )
**/
private $favoritePosts;
}
class Post
{
/**
* #ORM\ManyToMany(targetEntity="User", mappedBy="favoritePosts")
*/
private $usersInFavorite;
}
And I can get all user's favorite posts using a User entity object:
$favorites = $user->getFavoritesPosts();
But I have no idea how to get EXACTLY THE SAME result using DQL or Doctrine Query Builder. Under result i mean an array of POST entity objects.
Based on this exemple
If you want to fetch it by dql,
$dql = "SELECT p FROM Posts p INNER JOIN p.$usersInFavorite u WHERE u= ?1";
$query = $entityManager->createQuery($dql)
->setParameter(1, $user);
$favoritePosts = $query->getResult();
I tested it this time and i found the results as requested.
if you have the id of the user entity instead of the entity the same code will work with $user being the id of the user.

DQL many to many and count

I'm using Symfony 2 with Doctrine, and I've got two entities joined in a many to many association.
Let's say I have two entities: User and Group, and the related tables on db are users, groups and users_groups.
I'd like to get the top 10 most populated groups in DQL, but I don't know the syntax to perform queries on the join table (users_groups). I already looked on the Doctrine manual but I didn't found the solution, I guess I still have a lot to learn about DQL.
In plain sql that would be:
select distinct group_id, count(*) as cnt from users_groups group by group_id order by cnt desc limit 10
Can you please help me to translate this to DQL?
Update (classes):
/**
* Entity\E_User
*
* #ORM\Table(name="users")
* #ORM\Entity
*/
class E_User
{
/**
* #ORM\ManyToMany(targetEntity="E_Group", cascade={"persist"})
* #ORM\JoinTable(name="users_groups",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="cascade")},
* inverseJoinColumns={#ORM\JoinColumn(name="group_id", referencedColumnName="id", onDelete="cascade")}
* )
*/
protected $groups;
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $name
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/* ... other attributes & getters and setters ...*/
}
/**
* Entity\E_Group
*
* #ORM\Table(name="groups")
* #ORM\Entity
*/
class E_Group
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $name
*
* #ORM\Column(name="text", type="string", length=255)
*/
private $name;
/* ... other attributes & getters and setters ...*/
}
It's not easy without seeing the actual classes, but by guessing you have a many-to-many bidirectional relationship:
$dql = "SELECT g.id, count(u.id) as cnt FROM Entity\Group g " .
"JOIN g.users u GROUP BY g.id ORDER BY cnt DESC LIMIT 10;";
$query = $em->createQuery($dql);
$popularGroups = $query->getArrayResult();
UPDATE:
You don't have to use a bidirectional relationship, you can query the other way around:
$dql = "SELECT g.id, count(u.id) as cnt FROM Entity\User u " .
"JOIN u.groups g GROUP BY g.id ORDER BY cnt DESC LIMIT 10;";
For those who want to build the query with Doctrine's QueryBuilder instead of using DQL directly take this solution.
Please note that my problem wasn't to get the top user groups, but technically the problem is pretty similar to mine. I work with posts (like articles/blog posts) and tags that are added to posts. I needed to determine a list of related posts (identified by same tags). That list had to be sorted by relevance (the more same tags another post has the more relevant it is).
This is the method of my PostRepository class:
/**
* Finds all related posts sorted by relavance
* (from most important to least important) using
* the tags of the given post entity.
*
* #param Post $post
*
* #return POST[]
*/
public function findRelatedPosts(Post $post) {
// build query
$q = $this->createQueryBuilder('p')
->addSelect('count(t.id) as relevance')
->innerJoin('p.tags', 't')
->where('t.id IN (:tags)')
->setParameter('tags', $post->getTags())
->andWhere('p.id != :post')
->setParameter('post', $post->getId())
->addGroupBy('p.id')
->addOrderBy('relevance', 'DESC')
->getQuery();
// execute query and retrieve database result
$r = $q->execute();
// result contains arrays, each array contains
// the actual post and the relevance value
// --> let's extract the post entities and
// forget about the relevance, because the
// posts are already sorted by relevance
$r = array_map(function ($entry) {
// first index is the post, second one
// is the relevance, just return the post
return reset($entry);
}, $r);
// array of posts
return $r;
}
Thank you #Tom Imrei for you solution. Also the answer #26549597 was very helpful.
To improve Tom's answer, you could use DQL's HIDDEN keyword. This way, the result doesn't contain the useless cnt column and Arvid's array_map solution isn't needed (which could speed up the result significantly for larger queries).
And the OP's question was to get the top 10 groups, not just their IDs.
It would look something like this:
$query = 'SELECT g, HIDDEN COUNT(u.id) AS cnt FROM Entity\Group g LEFT JOIN g.users u ORDER BY cnt DESC';
$groups = $em->createQuery($query)->setMaxResults(10)->getResult();
Also, note the use of LEFT JOIN that ensures that empty groups are not dismissed.

Categories