Doctrine2 QB – how to select entities where OnetoOne entity doesn't exists - php

I have two entities:
class User extends BaseEntity {
/**
* #ORM\OneToOne(targetEntity="Profile", mappedBy="user", cascade={"persist", "remove"})
* #var Profile
*/
protected $profile;
}
class Profile extends BaseEntity {
/**
* #ORM\OneToOne(targetEntity="User", inversedBy="profile", cascade={"persist"})
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")
* #var User
*/
protected $user;
}
What I'm trying to do is to SELECT only users who don't have any profile (so there's no profile row with user_id=:user_id in profiles table). However, I have no idea how to make my QueryBuilder.
First, I tried something simple like
//u as user
$query = $this->er->createQueryBuilder('u');
$query
->join('u.profile', 'p')
->where('u.profile = 1');
But that returns A single-valued association path expression to an inverse side is not supported in DQL queries. Use an explicit join instead. so I suppose there's something wrong with my relationship? I tried to switch join() for leftJoin() but it didn't help either...
So what's up with this error and how to make proper condition with where() to tell Doctrine I want only users where there's no profile?

Here is how I was able to do it. Basically I had Domain & Hosting entities and I wanted to select Domains that had no Hosting attached to them in a one to one relationship.
$er->createQueryBuilder('d')
->leftJoin('d.hosting', 'h')
->where('h.id is NULL');
Credits for this solution goes to this Answer
Hope it works for you since our situations are almost identical.

Just check for null as value of profile
//u as user
$query = $this->er->createQueryBuilder('u');
$query
->where('u.profile is NULL');

Try to use repositories, example for UserRepository:
$qb = $this->createQueryBuilder('u');
$e = $qb->expr();
$qb->leftJoin('u.profile', 'p')
->where($e->isNull('p.id'))
Raw query will look like this:
SELECT * FROM users AS u LEFT JOIN u.profile AS p WHERE p.id IS NULL;

Related

Fetching all objects of one class with self-referencing ManyToMany

Think of this class:
class Person {
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="Person", fetch="EXTRA_LAZY")
* #ORM\OrderBy({"name" = "ASC"})
*/
public $friends;
/**
*
* #var Person
* #ORM\ManyToOne(targetEntity="Person")
*/
public $bestFriend;
}
I have to iterate a lot over all Persons, so I'd like to fetch join them all at once.
To save memory, I have to do this partially.
So what I can do is:
$this->em->createQuery('SELECT partial p.{id, name, bestFriend} FROM Person p')->getResult();
This is cool, after this query, all persons are in the UoW, and I can traverse the graph via $aPersion->bestFriend->bestFriend without creating an additional query to the DB, since all Persons are in memory.
However, this does not work with the ToMany association. Adding friends to the partial select gives an error. If I want to iterate over all friends, this will first create a query to the join table...
How can I realise the full hydration of the friends-ToMany-assotiation with one query? Maybe a second query could help? Or a clever join clause?
Thanks in advance!
I would create a query in PersonRepository.php with a leftJoin and a addSelect like so:
$qb = $this->em->getRepository('App:Person')
->createQueryBuilder('p')
->leftJoin('p.friends', 'friends')
->select('partial p.{id, name, bestFriend}'}
->addSelect('partial friends.{id, name}') // Retrieve what you want here
->getQuery()->getResult();
return $qb;
I have not tested this, but believe it should work.
#DirkJFaber your answer was right,
in terms of DQL here is my solution:
$this->em->createQuery('
SELECT partial p.{id, name, bestFriend}, f FROM Person p JOIN f.friends f')->getResult();

How to fetch (join) two records from database using doctrine/symfony4

I am learning about Symfony and Doctrine and created a simple site but I am stuck at this step.
I have two tables: users and languages
Users Contains: id, username ...
Languages Contains: user_id, language...
Here is a image of the two
Now I am trying to fetch by language, like: get user who speaks both english and french and the result would return user id 2
In plain PHP i can do inner join with PDO, but I am trying to follow the doctrine syntax and this does not return the correct result
public function getMatchingLanguages ($a, $b) {
return $this->createQueryBuilder('u')
->andWhere('u.language = :val1 AND u.language = :val2')
->setParameter('val1', $a)
->setParameter('val2', $b)
->getQuery()
->execute();
}
I call this method in my controllers, and the query is pretty basic since I can not find a documentation how to do the joins as per my example
In your User model add next code:
/**
* #ORM\OneToMany(targetEntity="Language", mappedBy="user", fetch="EXTRA_LAZY")
*/
public $languages;
In your Language model add next code:
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="languages")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
* })
*/
public $user;
By this way you define simple One-To-Many relation between User and Language, but it is not enough for getting your user that support both languages. You need to make 2 joins of user table and language table.
That's how it looks like (if you use controller):
$user = $this->get('doctrine')
->getEntityManager()
->createQueryBuilder()
->select('u')
->from(User::class, 'u')
->join('u.languages', 'l_eng', 'WITH', 'l_eng.language = :engCode')
->join('u.languages', 'l_fr', 'WITH', 'l_fr.language = :frCode')
->setParameters([
'engCode' => 'english',
'frCode' => 'french'
])
->getQuery()->execute();
Or, if you use UserRepository class (most preferable):
public function findAllByLangs()
{
return $this->createQueryBuilder('u')
->join('u.languages', 'l_eng', 'WITH', 'l_eng.lang = :engCode')
->join('u.languages', 'l_fr', 'WITH', 'l_fr.lang = :frCode')
->setParameters([
'engCode' => 'english',
'frCode' => 'french'
])
->getQuery()->execute();
}
So main trick is join language table with condition of english to filter users, that support english language AND join language table again but with french in "ON" section to filter users who support french language as well.
By analyzing your DB tables, I assume that your Entities are like this
// User.php
class User implements UserInterface
{
/**
* #ORM\Column(type="guid")
* #ORM\Id
* #ORM\GeneratedValue(strategy="UUID")
*/
private $id;
/**
* #ORM\Column(type="string", length=100)
*/
private $username;
}
// Language.php
class Language
{
/**
* #ORM\Column(type="guid")
*/
private $userId;
/**
* #ORM\Column(type="string", length=30)
*/
private $language;
}
If you have the same setup (as above Entities), then you can write your query like this in UserRepository.php
public function getUsersForMatchingLanguages ($langOne, $langTwo) {
return $this->createQueryBuilder('user')
->select('user.id, user.username, language.language')
->innerJoin(Language::class, 'language', 'WITH', 'language.user_id = user.id')
->where('language.language = :langOne AND language.language = :langTwo')
->setParameter('langOne ', $langOne )
->setParameter('langTwo', $langTwo)
->getQuery()
->getResult();
}
This will return you array of results.
Maybe I am not understand question correctly, please correct me if I am wrong, but if you need user(s) that speaks BOTH languages you have an error in SQL logic not in doctrine. You should do smth like:
SELECT * FROM user u JOIN language l ON u.id = l.user_id AND l.language = 'english' JOIN language l2 ON u.id = l2.user_id AND l2.language = 'french' GROUP BY u.id;
If query correct for you I can write DQL interpretation for it.
You can:
Inner join with the languages you want
use aggregate functions to count the joined results(joined languages)
group by the user entity
filter the results for count(lang) = 2
Code:
use Doctrine\ORM\Query\Expr\Join;
public function getMatchingLanguages ($a, $b) {
return $this->createQueryBuilder('u')
->addSelect('COUNT(a) as HIDDEN total')
->innerJoin('u.languages', 'a', Join::WITH, 'a.language = :val1 OR a.language = :val2')
->groupBy('u');
->having('total = :total') //or ->having('COUNT(a) = :total')
->setParameter('total', 2)
->setParameter('val1', $a)
->setParameter('val2', $b)
->getQuery()
->execute();
}
$this->getMatchingLanguages('english', 'french');
This works by inner joining user only with rows with english or french and then using mysql having to "check" if we got 2 rows for each user.
If you want also the Languages entities hydrated to your result, you cannot add it to the querybuilder result since you group by:
->addSelect('a')
you will have to do another query.

Symfony subquery for entity

Have a problem with subquery with symfony.
What I try to do - I have a table with users and a table with posts.
Posts Users
id|author|content id|username
I want create subquery to get user name by id.
/**
* #return array
*/
public function findAll()
{
return $this->getEntityManager()->createQuery(
'SELECT a, (SELECT u.username
FROM BackendBundle:User u WHERE u.id = a.author) as authorName
FROM BackendBundle:Article a'
)->getResult();
}
Result:
What am I doing wrong? What is the best way to join column from other table by id? Maybe i can use annotations?
Thx for any help.
You don't need a subquery here, what you need is a simple (INNER) JOIN to join Users with their Articles.
$em->createQuery("SELECT a FROM Article JOIN a.author'");
You don't even need an on clause in your join, because Doctrine should already know (through annotations on your entities or a separate yaml file), that the article.author field relates to user.id.
Edit:
I assume you have a User entity that is One-To-Many related to the Article entity.
class User
{
// ...
/**
* #OneToMany(targetEntity="Article", mappedBy="author")
*/
private $articles;
// ...
}
class Article
{
// ...
/**
* #var User
* #ManyToOne(targetEntity="User", inversedBy="articles")
*/
private $author;
// ...
}
Please refer to doctrines association mapping documentation: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html

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.

Categories