createQueryBuilder / Join Column - Symfony - php

I have a trouble to implement this SQL Request :
SELECT *
FROM project
LEFT JOIN user
ON project.idAuthor=user.id
WHERE project.isVisible = 1 AND
user.role = 'agency'
To a simple Symfony Query Builder :
$query = $this->createQueryBuilder('p')
->leftJoin('WebAwardsBundle:User', 'u')
->where('p.isVisible = 1')
->andwhere("u.role = 'agency'")
->orderBy('p.id', 'DESC')
->getQuery();
The response of this query give me all the project, include the project when role !== agency ...
I don't know where I can put the ON project.idAuthor=user.id
Mapping :
Project :
/**
* Project
*
* #ORM\Table(name="project")
*#ORM\Entity(repositoryClass="WebAwardsBundle\Repostory\ProjectRepository")
*/
class Project
{
...
/**
* #var int
*
* #ORM\ManyToOne(targetEntity="User", inversedBy="projects")
* #ORM\JoinColumn(name="idAuthor", referencedColumnName="id")
*/
private $idAuthor;
...
User :
/**
* User
*
* #ORM\Table(name="user")
* #ORM\Entity(repositoryClass="WebAwardsBundle\Repository\UserRepository")
*/
class User implements UserInterface, \Serializable{
...
/**
* #ORM\OneToMany(targetEntity="Project", mappedBy="idAuthor")
*/
private $projects;
...

The correct answer to implement this SQL Request :
SELECT *
FROM project
LEFT JOIN user
ON project.idAuthor=user.id
WHERE project.isVisible = 1 AND
user.role = 'agency'
is to join the correct column of the entity (project.idAuthor to user in this case) :
$query = $this->createQueryBuilder('p')
->join('p.idAuthor', 'u')
->where('p.isVisible = 1')
->andWhere("u.role = 'agency'")
->orderBy('p.id', 'DESC')
->getQuery();

Related

Doctrine join entities

I'm trying to make some efficient improvements when querying for an entity which has a relationship to another entity.
Entity A:
/**
* #var integer
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string
* #ORM\Column(name="name", type="string", length=225)
* #Assert\NotBlank(message="Please enter a name for your profile.")
*/
protected $display_name;
/**
* #ORM\OneToOne(targetEntity="Posts", mappedBy="profile", cascade={"all"}, fetch="LAZY")
*/
protected $posts;
Entity B:
/**
* #var integer
* #ORM\Column(name="profile_id", type="integer")
* #ORM\Id
*/
protected $profile_id;
/**
* #ORM\OneToOne(targetEntity="Profile", inversedBy="posts")
* #ORM\JoinColumn(name="profile_id", referencedColumnName="id")
*/
protected $profile;
/**
* #var string
* #ORM\Column(name="content", type="string")
*/
protected $content;
When I count the number of queries being executed I get two, I guess being doctrine runs two separate queries for each entity even through they have a relationship.
I am currently fetching entity A like so:
public function fetchById($id)
{
return $this->createQueryBuilder('p')
->where('p.id = :id')
->setParameter('id', $id)
->getQuery()
->getOneOrNullResult();
}
And then calling entity B like so:
$profile = $profileRepository->fetchById($user->getUserId());
$lastpost = $profile->getPosts()[0];
But I want to be able to join the second entity in this query so that I can just call one query rather than two. I was hoping to do something like this:
public function fetchById($id)
{
return $this->createQueryBuilder('p')
->select('p','pp')
->leftJoin(Posts::class, 'pp', \Doctrine\ORM\Query\Expr\Join::WITH, 'p.id = pp.profile_id')
->where('p.id = :id')
->setParameter('id', $id)
->getQuery()
->getResults();
}
However the left join returns an array of two entities. This is not what I am wanting, because I want to still be able to call for example the getPosts() method in entity A.
I essentially want to populate entity A, including all the related entities. But by only executing one query rather than two, is this possible in doctrine?
Some time ago I had a similar scenario (not with 1-1 but 1-n though), which I solved like this:
// ...Repository
public function findAllForUserWithAll(\AppBundle\Entity\User $u)
{
$query = $this->getEntityManager()->createQueryBuilder('ca1')
->add('select', 'c, s, i, m')
->add('from', 'AppBundle:Contact c')
->leftJoin('c.skills', 's')
->leftJoin('c.interests', 'i')
->leftJoin('c.metAt', 'm')
->where('c.user = :user')
->orderBy('c.lastname', 'ASC')
->setParameters([
'user' => $u,
])
->getQuery();
return $query->getResult();
}
This was in Symfony 2.8.* with Doctrine ^2.4.8 - and the result is one query with 3 joins (instead of 4 queries). Probably not the best code and honestly all the magic happened under the hood. Also your code looks alot alike. But maybe this is expected due to fetch="LAZY" (on entity A) - which my entity does not have?

Symfony2: Left Join producing wrong query

I'm trying to build a query with doctrine query builder like this:
$q = $this
->createQueryBuilder('u')
->select('u, r')
->leftJoin('u.roles', 'r')
->where('u.username = :username OR u.email = :email')
->setParameter('username', $username)
->setParameter('email', $username)
->getQuery();`
This code produces 2 queries:
Query 1 is correct
SELECT t0.user_id AS user_id1, t0.username AS username2, t0.salt AS salt3, t0.password AS password4, t0.email AS email5, t0.is_active AS is_active6, t0.created AS created7, t0.updated AS updated8, t0.last_login AS last_login9
FROM users t0
WHERE t0.username = ?
LIMIT 1
Query 2 is not correct:
SELECT t0.role_id AS role_id1, t0.role AS role2
FROM roles t0
INNER JOIN user_role ON t0.id = user_role.role_id
WHERE user_role.user_fk = ?
Query 2 should be:
SELECT t0.role_id AS role_id1, t0.role AS role2
FROM roles t0
INNER JOIN user_role ON t0.role_id = user_role.role_fk
WHERE user_role.user_fk = ?
Entity\Role looks like this:
/**
* #ORM\Table(name="roles")
* #ORM\Entity(repositoryClass="XXX\XXXBundle\Entity\Repository\RoleRepository")
*/
class Role implements RoleInterface
{
/**
* #ORM\Id
* #ORM\Column(name="role_id", type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $_roleId;
/**
* #ORM\Column(name="role", type="string", length=20, unique=true)
*/
protected $_role;
/**
* #ORM\ManyToMany(targetEntity="User", mappedBy="roles")
* #ORM\JoinTable(name="user_role",
* joinColumns={#ORM\JoinColumn(name="role_fk", referencedColumnName="role_id")})
*/
private $_users;
...
Entity\User looks like this:
class User implements UserInterface, \Serializable
{
/**
* #ORM\Column(name="user_id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $_userId;
...
/**
* #ORM\ManyToMany(targetEntity="Role", inversedBy="users")
* #ORM\JoinTable(name="user_role",
* joinColumns={#ORM\JoinColumn(name="user_fk", referencedColumnName="user_id")})
*/
protected $_roles;
...
The annotation in the Entity\Role code states the column names to use and the SELECT portion of the statement is using the correct names. The WHERE portion of the statement is using the correct column user_role.user_fk, this is defined in the Entity\User code.
How do I stop doctrine using column names that do not exist and use the defined column names for the INNER JOIN part of the statement?
I found a solution which works.
Entity\User needs to be altered to include the inverseJoinColumns on the annotation like this:
/**
* #ORM\ManyToMany(targetEntity="Role", inversedBy="_users")
* #ORM\JoinTable(name="user_role",
* joinColumns={#ORM\JoinColumn(name="user_fk", referencedColumnName="user_id")},
* inverseJoinColumns={#ORM\JoinColumn(name="role_fk", referencedColumnName="role_id")})
*/
protected $_roles;
Entity\Role needs to be altered to include the inverseJoinColumns like this:
/**
* #ORM\ManyToMany(targetEntity="User", mappedBy="_roles")
* #ORM\JoinTable(name="user_role",
* joinColumns={#ORM\JoinColumn(name="role_fk", referencedColumnName="role_id")},
* inverseJoinColumns={#ORM\JoinColumn(name="user_fk", referencedColumnName="user_id")})
*/
private $_users;
and the createQueryBuilder code needs to look like this:
$q = $this
->createQueryBuilder('u')
->select('u, r')
->from('XXXXXXBundle:User', 'u')
->leftJoin('u.roles', 'r', \Doctrine\ORM\Query\Expr\Join::ON, 'user_role.role_kf = r.role_id')
->where('u.username = :username OR u.email = :email')
->setParameter('username', $username)
->setParameter('email', $username)
->getQuery();

How to JOIN without relational table in Symfony Doctrine with QueryBuilder between 2 entities

I have an Entity Video related with a Entity Category and I need to run this SQL with Doctrine QueryBuilder, with this I can get the most used categories in all videos (1000+):
SELECT c.*
FROM Video v
INNER JOIN video_category vc ON vc.video_id = v.id
INNER JOIN Category c ON vc.category_id = c.id
GROUP BY c.id
HAVING COUNT(v.id) > 1000
ORDER BY c.name ASC;
My querybuilder:
$queryBuilder = $this->getEntityManager()
->createQueryBuilder()
->select('c')
->from('AcmeVideoBundle:Video', 'v')
// Can Doctrine join itself silently with relational info in the Entities?
->join('AcmeCategoryBundle:Category', 'c', Expr\Join::WITH, 'v.id = c.id')
->groupBy('c.id')
->having('COUNT(v.id) > 1000')
->orderBy('c.name', 'ASC')
->getQuery();
But the SQL query output by queryBuilder is this:
SELECT c0_.id AS id0, c0_.NAME AS name1
FROM Video v1_
INNER JOIN Category c0_ ON (v1_.id = c0_.id)
GROUP BY c0_.id
HAVING COUNT(v1_.id) > 1000
ORDER BY c0_.NAME ASC
Without the relational table (video_category)
The Entities mapping:
/**
* Video
*
* #ORM\Table
* #ORM\Entity(repositoryClass="Acme\VideoBundle\Entity\VideoRepository")
*/
class Video
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToMany(targetEntity="Acme\CategoryBundle\Entity\Category", cascade={"persist"})
*/
private $category;
// More fields, getters and setters etc...
}
/**
* Category
*
* #ORM\Table
* #ORM\Entity(repositoryClass="Acme\CategoryBundle\Entity\CategoryRepository")
*/
class Category
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
// More fields, getters and setters etc...
}
How can I use the relation table to run the original SQL query with doctrine Querybuilder? I missed something?
INFO: When I findBy{field}, persist, flush, clear on all entities works fine, the Doctrine relations are ok, I have a Video, Category and video_category tables fine, the original SQL query works perfect.
// Can Doctrine join itself silently with relational info in the Entities?
->join('AcmeCategoryBundle:Category', 'c', Expr\Join::WITH, 'v.id = c.id')
Yes! In fact that is one of the major reasons for using an ORM such as Doctrine 2.
Try:
->leftJoin('v.category','c')
The manual goes into more details though oddly enough is does not seem to have a join example. Hence the common confusion.
http://docs.doctrine-project.org/en/latest/reference/query-builder.html
And you may not be aware of this but the United Nations has passed a resolution outlawing the use of abbreviations for aliases. Just to be safe, try:
$queryBuilder = $this->getEntityManager()
->createQueryBuilder()
->addSelect('category')
->from('AcmeVideoBundle:Video', 'video')
->leftJoin('video.category', 'category')
->groupBy('category.id')
->having('COUNT(video.id) > 1000')
->orderBy('category.name', 'ASC')
->getQuery();
Ok, solved, the problem was that the Entities isn't fully mapped for ManyToMany bidirectional relationship.
The Entities are now:
class Video
{
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="Acme\CategoryBundle\Entity\Category", inversedBy="video") // Note the inversedBy key
*/
private $category;
}
class Category
{
/**
* #var Video
*
* #ORM\ManyToMany(targetEntity="Acme\VideoBundle\Entity\Video", mappedBy="category") // Again the inversed
*/
private $video; // New field for bidirectional ManyToMany
}
And the final QueryBuilder working (now with full alias version :P):
$queryBuilder = $this->getEntityManager()
->createQueryBuilder()
->select('category')
->from('AcmeCategoryBundle:Category', 'category')
->join('category.video', 'video')
->groupBy('category.id')
->having('COUNT(video.id) > 1000')
->orderBy('category.name', 'ASC')
->getQuery();
Best regards

Doctrine2 query error in OneToMany relation

I've got a doctrine2 problem with a MySQL database:
I have a model User and a model Documents. Each User may have 0 or n Documents. Each Document is assigned to exactly one User. My models:
User
<?php
namespace Entity;
/**
* #Entity(repositoryClass="Entity\Repository\UserRepository")
* #Table(name="user")
*/
class User extends Object
{
/**
* #Id #GeneratedValue(strategy="UUID")
* #Column(type="string", length=36)
* #var string
*/
protected $id;
/**
* #OneToMany(targetEntity="\Entity\Document", mappedBy="user")
*/
private $documents;
public function __construct($options = array())
{
$this->documents = new \Doctrine\Common\Collections\ArrayCollection;
}
}
Document
<?php
namespace Entity;
/**
* #Entity(repositoryClass="Entity\Repository\DocumentRepository")
* #Table(name="document")
*/
class Document extends Object
{
/**
* #Id #Column(type="string")
* #var string
*/
protected $id;
/**
* #ManyToOne(targetEntity="\Entity\User", inversedBy="documents")
* #JoinColumn(name="user_id", referencedColumnName="id")
* #var User
*/
private $user;
}
Now I want to get the User of a given Document ID. The SQL-query would be:
SELECT u.*
FROM `user` u
INNER JOIN `document` d ON d.user_id = u.id
WHERE d.id = 'mydocumentid'
But this does't work:
$user = $queryBuilder
->select('u.*')
->from('\\Entity\\User', 'u')
->innerJoin('\\Entity\\Document', 'd', \Doctrine\ORM\Query\Expr\Join::ON, 'd.user_id = u.id')
->where('d.id = :documentId')
->setParameter('documentId', 'mydocumentid')
->setMaxResults(1)
->getQuery()
->getSingleResult();
Also the direct query doesn't work:
$query = $em->createQuery('
SELECT
u.*
FROM
Entity\\User u
INNER JOIN
Entity\\Document d ON d.user_id = u.id
WHERE
d.id = "mydocumentid"
');
Could you please help me to get this run?
Error message
[Semantical Error] line 0, col 66 near 'd ON d.user_id': Error: Identification Variable \Entity\Document used in join path expression but was not defined before.
Instead of using:
->innerJoin('\\Entity\\Document', 'd', \Doctrine\ORM\Query\Expr\Join::ON, 'd.user_id = u.id')
Try using this:
->innerJoin('u.documents', 'd', \Doctrine\ORM\Query\Expr\Join::ON, 'd.user_id = u.id')
This is because Doctrine needs to know to which field in user the documents have to be joined. Knowing the field Doctrine will fetch the target entity and that way Doctine directly knows the class.

Complex Query in Query Builder

How can I execute this query using QueryBuilder in Symfony 2
SELECT um.id, sv.patentgroup_id, pm.portfolio_id, pp.id
FROM sv_patents sv
LEFT JOIN pm_patentgroups pm ON sv.patentgroup_id = pm.id
LEFT JOIN pm_portfolios pp ON pm.portfolio_id = pp.id
LEFT JOIN um_users um ON pp.user_id
The associations in entity classes are defined as
In SvPatents I have
/**
* #var PmPatentgroups
*
* #ORM\ManyToOne(targetEntity="PmPatentgroups")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="patentgroup_id", referencedColumnName="id")
* })
*/
private $patentgroup;
In PmPatentgroups I have
/**
* #var PmPortfolios
*
* #ORM\ManyToOne(targetEntity="PmPortfolios")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="portfolio_id", referencedColumnName="id")
* })
*/
private $portfolio_id;
In PmPortfolios I have
/**
* #var UmUsers
*
* #ORM\ManyToOne(targetEntity="UmUsers")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
* })
*/
private $user;
I tried this but it is giving me the whole results from the table looks like joins are not working propoerly
$repository = $this->getDoctrine()->getRepository('MunichInnovationGroupBundle:SvPatents');
$qb = $repository->createQueryBuilder('sv')
->leftJoin('sv.patentgroup','pm')
->leftJoin('pm.portfolio_id','pp')
->leftJoin('pp.user','um')
->getQuery();
$patents = $qb->getArrayResult();
I am quite new to Symfony2 and Doctorine2. I read the whole documentation but could not find such complex example actually i am bit confused with how the querybuilder build the query. It would be great if you explain it a little in simple words
Thanks in advance
I found the solution to my problem. If u looks for the records of a specific user in the table then you need to add one where clause. This is how your query should looks like
$user_id = 1;
$repository = $this->getDoctrine()->getRepository('MunichInnovationGroupBundle:SvPatents');
$qb = $repository->createQueryBuilder('sv')
->select('sv')
->leftJoin('sv.patentgroup','pm')
->leftJoin('pm.portfolio_id','pp')
->leftJoin('pp.user','um')
->where('pp.user = :user_id')
->setParameter('user_id', $user_id)
->getQuery();
$patents = $qb->getArrayResult();
Thats all :)
You're very close. You just have to add a ->select() query part:
$repository = $this->getDoctrine()->getRepository('MunichInnovationGroupBundle:SvPatents');
$qb = $repository->createQueryBuilder('sv')
->select('um.id, pm.id, pp.id') // add this part
->leftJoin('sv.patentgroup','pm')
->leftJoin('pm.portfolio_id','pp')
->leftJoin('pp.user','um')
->getQuery();
$patents = $qb->getArrayResult();

Categories