Doctrine query builder select count onetomany entities - mysql

I have Group and User entities. They are associated through GroupUser entity - which is manytomany entity with extra fields like status.
Class Group
/**
* #ORM\OneToMany(targetEntity="GroupUser", mappedBy="group")
*/
private $groupUsers;
Class User
/**
* #ORM\OneToMany(targetEntity="GroupUser", mappedBy="user")
*/
private $userGroups;
Class GroupUser
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="User", inversedBy="userGroups", cascade={"persist"})
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $user;
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="Group", inversedBy="groupUsers", cascade={"persist"})
* #ORM\JoinColumn(name="group_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $group;
How to get list of groups which User has joined with number of users in each group with QueryBuilder
$qb = $this->createQueryBuilder('group')
->select('group.id', 'group.name', 'COUNT(groupUser.user) as membersCount')
->join('group.groupUsers', 'groupUser')
->where('groupUser.user = :userId')
->andWhere('groupUser.status = :status')
->addGroupBy('groupUser.group')
->setParameter('userId', $user->getId())
->setParameter('status', $status);
$results = $qb->getQuery()->getResult();
This query return membersCount as 1 for all groups even if there are more than one member. How to change query to get correct number of members for each group?

Related

How do I fetch association data from a list of users through a many to many relationship in doctrine query builder

I can't figure out how to do this using the query builder in Doctrine:
On a high level, I wan to fetch all of the userTags which are associated to each user, and I am fetching each user from user relationship table by their parent id.
Something like this:
from UserRelationship -> get users by parent_id -> also get each user's associated userTags. It's the last bit that I can't get the userTags. Idealy I would select only the id and name params from that table, as the rest, like company and users is redundant.
I have this query builder query in Symfony 3.* Doctrine 2.*
UserRelationshipRepository.php
protected function getBaseSubordinateQuery($managerId ){
$qb = $this->createQueryBuilder('r');
$qb
->select(
'u.id',
'u.username',
'u.email',
'u.first_name',
'u.last_name',
'u.lastLogin',
'u.userTags', // <-- How do I get these values?
'c.phone',
'c.position as jobTitle',
'co.name as company',
'c.createdAt',
'max(cl.expiresAt) as expiresAt',
'cl.expiresAt',
'c.yearsInPosition as titleSince',
'c.skype',
'c.linkedin',
'c.yearsAtCompany',
'up.path as imgPath',
'up.name as imgName',
'up.format as imgFormat'
)
->innerJoin( 'r.child', 'u' )
->innerJoin( 'u.clientInfo', 'c' )
->leftJoin( 'u.userTags', 'ut' )
->where('ut IS NOT NULL')
->leftJoin( 'u.company', 'co' )
->leftJoin( 'c.clientLicense', 'cl' )
->leftJoin( 'u.userPhotos', 'up' )
->where ( $qb->expr()->eq('r.parent', ':manager') )
->setParameter('manager', $managerId)
;
return $qb;
Note: as it is, that query gives me an error: 500 Internal Server Error. [Semantical Error] line 0, col 76 near 'userTags, c.phone,': Error: Invalid PathExpression. Must be a StateFieldPathExpression.
User.php
/**
* #var \Doctrine\Common\Collections\Collection|\Reddin\Bundle\CMSBundle\Entity\UserTag[]
*
* #ORM\ManyToMany(targetEntity="Reddin\Bundle\CMSBundle\Entity\UserTag", inversedBy="users")
* #ORM\JoinTable(name="users_user_tags")
*
* #JMS\Groups({"tags"})
* #JMS\MaxDepth(2)
*/
protected $userTags;
UserTag.php
class UserTag
{
use Timestampable;
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* Many UserTags belong to one Company.
*
* #ORM\ManyToOne(targetEntity="Reddin\Bundle\CMSBundle\Entity\Company", inversedBy="userTags", fetch="EXTRA_LAZY")
* #ORM\JoinColumn(name="company_id", referencedColumnName="id")
* #JMS\MaxDepth(2)
*/
private $company;
/**
* #var \Doctrine\Common\Collections\Collection|\Reddin\Bundle\UserAccountBundle\Entity\User[]
*
* #ORM\ManyToMany(targetEntity="Reddin\Bundle\UserAccountBundle\Entity\User", mappedBy="userTags", fetch="EXTRA_LAZY")
* #JMS\MaxDepth(2)
*/
private $users;
/**
* Company constructor.
*/
public function __construct()
{
$this->users = new ArrayCollection();
}
public function __toString()
{
return "" . $this->getName();
}
/**
* Get id
*
* #return int
*/
public function getId() {
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return UserTag
*/
public function setName($name) {
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName() {
return $this->name;
}
/**
* #return ArrayCollection|User[]
*/
public function getUsers()
{
return $this->users;
}
/**
* #return UserTag
*/
public function getCompany() {
return $this->company;
}
/**
* #param Company $company
* #return UserTag
*/
public function setCompany(Company $company = null) {
$this->company = $company;
return $this;
}
}
Replace
'u.userTags',
with
'ut'
This will solve the error you are getting, but probably you will get others.
I suggest change the query to:
$qb
->select(
'r',
'u',
'ut',
...
Also replace the 2nd where to:
->andWhere ( $qb->expr()->eq('r.parent', ':manager') )
Then you can fetch the tags for each user:
$userRel->getChild()->getUserTags();

Doctrine does not automatically load joined relation using GROUP BY

I have two Doctrine entity classes: Vertriebsschiene and Filiale:
/**
* Vertriebsschiene
*
* #ORM\Table(name="vertriebsschiene", indexes={
* #ORM\Index(columns={"name"}, flags={"fulltext"})
* }))
* #ORM\Entity(repositoryClass="CRMBundle\Repository\VertriebsschieneRepository")
*/
class Vertriebsschiene
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=100)
*/
private $name;
/**
* #var ArrayCollection
*
* #ORM\OneToMany(targetEntity="Filiale", mappedBy="vertriebsschiene", fetch="EAGER")
*/
private $filialen;
...
}
/**
* Filiale
*
* #ORM\Table(name="filiale")
* #ORM\Entity(repositoryClass="CRMBundle\Repository\FilialeRepository")
*/
class Filiale extends Lieferant
{
/**
* #var Vertriebsschiene
*
* #ORM\ManyToOne(targetEntity="CRMBundle\Entity\Vertriebsschiene", inversedBy="filialen", fetch="EAGER")
*/
private $vertriebsschiene;
...
}
The Vertriebsschine objects have a non-uniqe name. Now I try to display a list of Vertriebsschiene objects with their Filiale objects.
My findAllQuery method looks like this:
/**
* #param User $user
* #return \Doctrine\ORM\Query
*/
public function findAllQuery(User $user){
$qb = $this->createQueryBuilder('v')
->select('v as vertriebsschiene')
->addSelect('COUNT(f) as filial_num')
->leftJoin('v.filialen', 'f')
->groupBy('v.name');
$this->restrictAccess($user, $qb);
return $qb->getQuery();
}
/**
* #param User $user
* #param $qb
*/
protected function restrictAccess(User $user, QueryBuilder &$qb)
{
if ($user->hasRole(RoleVoter::AUSSENDIENST)) {
$qb ->leftJoin('f.vertreter', 'u')
->leftJoin('u.vertretungen', 'vx')
->andWhere($qb->expr()->orX(
'f.vertreter = :userid',
'f.vertreter IS NULL',
$qb->expr()->andX(
'vx.proxy = :userid',
$qb->expr()->between(':currentDate', 'vx.start', 'vx.end')
)
))
->setParameter('userid', $user->getId())
->setParameter('currentDate', new \DateTime(), \Doctrine\DBAL\Types\Type::DATETIME);
}
}
My problem is, that the Vertriebsschiene::$filiale array collection is not automatically loaded, but is loaded for every Vertriebsschiene resulting in many DB connections.
This also has the problem, that the WHERE statement is ignored when the Vertriebsschiene::$filiale is fetched.
The COUNT(f) returns the correct amount of Filiale objects.
I suspect this is an issue with the GROUP BY statement.
I think the problem is that you do not tell doctrine to select filiale fields.
Try to add the filiale alias in your select :
public function findAllQuery(User $user){
$qb = $this->createQueryBuilder('v')
->select('v as vertriebsschiene', 'f')
->addSelect('COUNT(f) as filial_num')
->leftJoin('v.filialen', 'f')
->groupBy('v.name');
$this->restrictAccess($user, $qb);
return $qb->getQuery();
}
If you check your query in the profiler, i think you'll see that doctrine add a LEFT JOIN fialiale f0_ ON v0_.id = f0_.vertriebsschiene_id (or something like this but does not add SELECT ... f0_.id, f0_.xxxx.
So every time you'll call $vertriebsschiene->getFieliale()->getXXX() doctrine will have to execute the corresponding query to get the filiale data.

Wrong ID given in Doctrine2 OneToOne mapping

I am seriously stack right now with the problem I have occurred with OneToOne mapping.
So let me show what I currently have:
OrderItem entity
/**
* OrderItem
*
* #ORM\Table(name="order_item")
* #ORM\Entity
*/
class OrderItem
{
/**
* #var integer
*
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
// ... //
/**
* #ORM\OneToOne(targetEntity="UserPricingOption", mappedBy="orderItem")
*/
private $userPricingOption;
/**
* #ORM\ManyToOne(targetEntity="Order", inversedBy="items")
* #ORM\JoinColumn(referencedColumnName="id")
*/
private $order;
// ... //
UserPricingOption entity
/**
* UserPricingOption
*
* #ORM\Table(name="user_pricing_option")
* #ORM\Entity
* #ExclusionPolicy("all")
*/
class UserPricingOption
{
/**
* #var integer
*
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
// ... //
/**
* #ORM\OneToOne(targetEntity="OrderItem", inversedBy="userPricingOption")
* #ORM\JoinColumn(referencedColumnName="id")
*/
private $orderItem;
// ... //
so generated tables look like this:
order_item table
* `id` 5
user_pricing_option table
* `id` 12
* `order_item_id` 5
So now I am trying to do the following:
echo $orderItem->getId(); // gives 5, `GOOD`
echo $orderItem->getUserPricingOption()->getId(); // gives 5 `BAD` (completely different user_pricing_option row), it should return 12.
I seriously have no idea why is that, please find the raw Doctrine query:
Keep in mind that query contains way more info than the showed examples above
SELECT t0.id AS id_1, t0.guid AS guid_2, t0.created_at AS created_at_3, t0.modified_at AS modified_at_4, t5.id AS id_6, t5.guid AS guid_7, t5.created_at AS created_at_8, t5.modified_at AS modified_at_9, t5.user_id AS user_id_10, t5.order_item_id AS order_item_id_11, t5.pricing_option_id AS pricing_option_id_12, t13.id AS id_14, t13.guid AS guid_15, t13.created_at AS created_at_16, t13.modified_at AS modified_at_17, t13.user_id AS user_id_18, t13.order_item_id AS order_item_id_19, t13.product_variant_id AS product_variant_id_20, t0.order_invoice_id AS order_invoice_id_21, t0.order_id AS order_id_22 FROM order_item t0 LEFT JOIN user_pricing_option t5 ON t5.order_item_id = t0.id LEFT JOIN user_product_variant t13 ON t13.order_item_id = t0.id WHERE t0.order_id = ? [131]
So basically $orderItem->getUserPricingOption()->getId() always returns the same ID as $orderItem->getId();
Anyone possibly see what is going on?
OneToOne bidireccional?
Try to define in joinColumn the name with column to reference id.
/**
* #ORM\OneToOne(targetEntity="OrderItem", inversedBy="userPricingOption")
* #ORM\JoinColumn(name="order_item_id", referencedColumnName="id")
*/
private $orderItem;
http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/association-mapping.html#one-to-one-bidirectional

Building OneToMany and ManyToOne associations beetwen more than three tables via additional table Doctrine 2

Building associations beetwen more than three tables
via additional [EntityTypes] table Doctrine2
I have next schema of database:
//==== some Tables ====
articles (
id
entity_type_id
)
posts (
id
entity_type_id
)
other... (
id
entity_type_id
)
//==== additional table of Types ====
entity_types (
id
)
//==== Comments ====
comments (
id
entity_type_id
entity_id
)
I use Symfony2 with Doctrine2 thereby I've been written next assotiations:
class Article
{
/**
* #ORM\ManyToOne(targetEntity="EntityType", inversedBy="articles", cascade={"persist"})
* #ORM\JoinColumn(name="entity_type_id", referencedColumnName="id")
*/
protected $entityType;
/**
* #ORM\OneToMany(targetEntity="Comment", mappedBy="article")
*/
protected $comments;
}
class Post
{
/**
* #ORM\ManyToOne(targetEntity="EntityType", inversedBy="posts", cascade={"persist"})
* #ORM\JoinColumn(name="entity_type_id", referencedColumnName="id")
*/
protected $entityType;
/**
* #ORM\OneToMany(targetEntity="Comment", mappedBy="post")
*/
protected $comments;
}
...
class EntityType
{
/**
* #ORM\OneToMany(targetEntity="Article", mappedBy="entityType")
*/
protected $articles;
/**
* #ORM\OneToMany(targetEntity="Post", mappedBy="entityType")
*/
protected $posts;
/**
* #ORM\OneToMany(targetEntity="Comment", mappedBy="entityType")
*/
protected $comments;
}
class Comment
{
/**
* #ORM\ManyToOne(targetEntity="EntityType", inversedBy="comments", cascade={"persist"})
* #ORM\JoinColumn(name="entity_type_id", referencedColumnName="id")
*/
protected $entityType;
/**
* #ORM\ManyToOne(targetEntity="Article", inversedBy="comments", cascade={"persist"})
* #ORM\JoinColumn(name="entity_id", referencedColumnName="id")
*/
protected $article;
/**
* #ORM\ManyToOne(targetEntity="Post", inversedBy="comments", cascade={"persist"})
* #ORM\JoinColumn(name="entity_id", referencedColumnName="id")
*/
protected $post;
}
There are no any mistakes in mapping and all operations are available
including commenting articles
but an exception appeared when I'm trying to comment post
need someone to help...

Doctrine query builder - any of joined elements

I have the following entities (simplified):
a work is composed of multiple tasks. Each task has a field storing the time it should be due and a field storing the time it has been completed.
/**
* Work
*
* #ORM\Entity
*/
class Work
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="Task", mappedBy="work", cascade={"persist", "remove"})
* #ORM\JoinColumn(name="task_work_id", referencedColumnName="id")
*/
protected $task;
/**
* Constructor
*/
public function __construct()
{
$this->task = new ArrayCollection();
}
}
/**
* Task
*
* #ORM\Entity
*/
class Task
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Work", inversedBy="task")
* #ORM\JoinColumn(name="work_id", referencedColumnName="id")
*/
private $work;
/**
* #var \DateTime
*
* #ORM\Column(name="completed", type="datetime", nullable=true)
*/
private $completed;
/**
* #var \DateTime
*
* #ORM\Column(name="duedate", type="datetime", nullable=true)
*/
private $duedate;
}
I am trying to build a query to retrieve all works that have uncompleted overdue tasks.
I initially thought something like this:
$repository = $this->getEntityManager()
->getRepository('MyBundle:Work');
$query = $repository->createQueryBuilder('v');
$query->leftJoin('v.task', 't');
$query->andWhere(
$query->expr()->isNull('t.completed')
)
->andWhere(
$query->expr()->gte('t.duedate', ':now')
)
;
$query->setParameter('now', new \DateTime(), \Doctrine\DBAL\Types\Type::DATETIME);
However, this retrieves works whose all tasks are overdue (I think).
So I tried with a sub-query:
$repository = $this->getEntityManager()
->getRepository('MyBundle:Work');
$query = $repository->createQueryBuilder('v');
$query2 = $this->getEntityManager()
->createQueryBuilder();
$query2->select('t')
->from('v.task', 't');
$query2->andWhere(
$query->expr()->isNull('t.completed')
)
->andWhere(
$query->expr()->gte('t.duedate', ':now')
)
;
$query2->setParameter('now', new \DateTime(), \Doctrine\DBAL\Types\Type::DATETIME);
$query->andWhere(
$query->expr()->any(
$query2->getDql()
)
);
But I get the following error:
QueryException: [Syntax Error] line 0, col 104: Error: Expected known function, got 'ANY'
Any idea on how I could write the query?
Thank you in advance!
You have not defined a repository for $query2