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();
Related
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();
I have some pretty basic entities, containing stories and tags, which I'm trying to load as efficiently as possible.
When I query for stories like this:
SELECT a FROM Foo\Article a WHERE a.id IN (1,2,3,4,5)
I see see the following SQL queries being run:
SELECT f0_.id AS id_0, f0_.title AS title_1 FROM foo_article f0_ WHERE f0_.id IN (1, 2, 3)
SELECT t0.name AS name_1, t0.article_id AS article_id_2 FROM foo_tag t0 WHERE t0.article_id = 1
SELECT t0.name AS name_1, t0.article_id AS article_id_2 FROM foo_tag t0 WHERE t0.article_id = 2
SELECT t0.name AS name_1, t0.article_id AS article_id_2 FROM foo_tag t0 WHERE t0.article_id = 3
Where I would like to see this:
SELECT f0_.id AS id_0, f0_.title AS title_1 FROM foo_article f0_ WHERE f0_.id IN (1, 2, 3)
SELECT t0.name AS name_1, t0.article_id AS article_id_2 FROM foo_tag t0 WHERE t0.article_id IN (1, 2, 3);
Source code looks like this. Abbreviated from the actual code.
<?php
namespace Foo;
use Doctrine\ORM\Mapping as ORM;
/**
* Class Tag
*
* #ORM\Entity()
* #ORM\Table(name="foo_tag")
*
* #package Foo
*/
class Tag {
/**
* #ORM\Column(type="string")
* #ORM\Id()
*/
protected $name;
/**
* #ORM\ManyToOne(targetEntity="\Foo\Article",cascade={"persist"},fetch="LAZY",inversedBy="tags")
* #ORM\Id()
*/
protected $article;
}
/**
* Class Article
*
* #ORM\Entity()
* #ORM\Table(name="foo_article")
*
* #package Foo
*/
class Article {
/**
* #ORM\Id #ORM\Column(type="integer", name="id") #ORM\GeneratedValue
*/
protected $id;
/**
* #ORM\Column(type="string")
*/
protected $title;
/**
* #ORM\OneToMany(targetEntity="\Foo\Tag",mappedBy="article",cascade={"persist"},fetch="EAGER")
*/
protected $tags;
}
One possible approach I was thinking about myself would be adding something like this to my repository class. But it just doesn't feel quite right just yet. I want something that's more portable to other associations, works every time the Entity is queried, and also works with Paginated queries.
Perhaps something in a postLoad-like event, that covers an entire result set (instead of the per-entity postLoad).
$qb = $entityManager->getRepository('Foo\Article')->createQueryBuilder('a')
->where('a.id IN (1,2,3)');
$list = $qb->getQuery()->execute();
/** #var Foo\Article[] $indexed */
$indexed = array_reduce($list, function($result, \Foo\Article $article) {
$result[$article->getId()] = $article;
return $result;
}, Array());
$tags = $entityManager->getRepository('Foo\Tag')->createQueryBuilder('t')
->where('t.article IN (:ids)')
->setParameter('ids', $indexed)
->getQuery()->execute();
array_map(function(\Foo\Tag $tag) use ($indexed) {
/** #var \Doctrine\ORM\PersistentCollection $collection */
$collection = $tag->getArticle()->getTags();
$collection->add($tag);
$collection->setInitialized(true);
}, $tags);
foreach($indexed as $article) {
$article->getTags()->takeSnapshot();
$article->getTags()->setInitialized(true);
}
/** #var Foo\Article $article */
// and now use the Articles and Tags, to make sure that everything is loaded
foreach($list as $article) {
$tags = $article->getTags();
print " - ".$article->getTitle()."\n";
foreach($tags as $tag) {
print " - ".$tag."\n";
}
}
A little bit of background:
I've got Office entities.
Offices may have articles.
Articles have tags.
Offices can share a tag with other offices (and therefore articles).
Now I'm trying to build a query that says: "Fetch all articles that either belong to my office or have tags that have been shared with my office".
I've tried this in MySQL and the following query works as expected:
SELECT
*
FROM
`articles` `a`
WHERE
(
`office_id` = 2
OR
`a`.`id` IN (
SELECT
`at`.`article_id`
FROM
`article_tags` `at`
WHERE
`at`.`article_id` = `a`.`id`
AND
`at`.`tag_id` IN
(
SELECT
`st`.`tag_id`
FROM
`shared_tags` `st`
WHERE
`st`.`shared_with_id` = 2
AND
`st`.`article` = 1
)
)
)
AND
`status` = 1
My entities are as follows:
Article
/**
* #ORM\ManyToMany(targetEntity="Tag", inversedBy="articles")
* #ORM\JoinTable(name="article_tags")
* #var ArrayCollection|Tag[]
*/
protected $tags;
Tag
/**
* #ORM\ManyToMany(targetEntity="Article", mappedBy="tags")
* #var ArrayCollection|Article[]
*/
protected $articles;
SharedTag
class SharedTag extends Entity
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
* #var int
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Office", inversedBy="sharedTags")
* #ORM\JoinColumn(name="shared_with_id", referencedColumnName="id")
* #var Office
*/
protected $sharedWith;
/**
* #ORM\ManyToOne(targetEntity="Office", inversedBy="sharedTags")
* #ORM\JoinColumn(name="shared_by_id", referencedColumnName="id")
* #var Office
*/
protected $sharedBy;
/**
* #ORM\ManyToOne(targetEntity="Tag", inversedBy="sharedTags")
* #var Tag
*/
protected $tag;
/**
* #ORM\Column(type="boolean", options={"default" = 0})
* #var bool
*/
protected $template = 0;
/**
* #ORM\Column(type="boolean", options={"default" = 0})
* #var bool
*/
protected $article = 0;
/**
* #ORM\Embedded(class = "\ValueObjects\CreatedAt", columnPrefix=false)
* #var CreatedAt
*/
protected $createdAt;
}
So, how can I query this in DQL or with the QueryBuilder? I've tried several methods but I can't seem to use the tag_id from the relationship articles.tags in a WHERE IN() query.
Thank you in advance!
Edit:
After some trial and error and thanks to Jean's answer I was able to query it like this:
SELECT
a
FROM
Article a
LEFT JOIN a.tags t
LEFT JOIN a.office o
LEFT JOIN o.sharedTags st
WHERE
(
a.office = :office
OR
(
t = st.tag
AND
st.sharedWith = :office
AND
st.article = 1
)
)
AND
a.status.status = :status
AND
a.template IS NULL
GROUP BY a.id
You should use a join and not subqueries. This way you have better performance and less complicated DQL code:
SELECT DISTINCT `a`
FROM `articles` `a`
LEFT JOIN `a.tags` `t`
LEFT JOIN `t.shared_with_id` `o`
WHERE (`a`.`office_id` = 2 OR `o`.`id` = 2)
AND `status` = 1
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.
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();