Doctrine ManyToMany relation with join condition - php

I've got a Many-To-Many relation between two tables but I want to add a join condition on the join table.
/**
* #ManyToMany(targetEntity="Company", inversedBy="Accounts")
* #JoinTable(name="account_company",
* joinColumns = { #JoinColumn(name="account_id", referencedColumnName="id") },
* inverseJoinColumns = { #JoinColumn(name="company_id", referencedColumnName="id") }
* )
*/
protected $companies;
I would have a condition like "account_company.is_deleted = 0", how can I make it ?

Yes, you have the choice to hydrate your object and its ManyToMany collection using custom dql:
make a repository method that adds conditions on your join:
// repository class:
public function getAccountWithNonDeletedCompanies($id)
{
return $this->createQueyBuilder('account')
->leftJoin('account.companies', 'companies', 'WITH', 'companies.deleted = 0')
->andWhere('account.id = :id')
->setParameter('id', $account)
->getQuery()
->getOneOrNullResult()
;
}
From what I can understand, you never want to display deleted companies ? (soft delete).
In this case, you may want to use SQL Filters: http://blog.loftdigital.com/doctrine-filters-and-soft-delete

Have dealt with this before and the only solution I got was creating a custom function that will make the query.
So on your Entity create a custom function getCompanies().

Related

Doctrine - relation of three tables

I have trouble with writing Doctrine Query Builder.
I have a Posts table which is related to the AccountToken table like:
class Posts
/**
* #ORM\ManyToOne(targetEntity="App\Entity\AccountToken")
* #ORM\JoinColumn(name="account_token_id", referencedColumnName="uuid", nullable=false)
*/
protected $accountTokenId;
I am writing a query builder where I want to pull all the results which are related to that table via UUID and have a specific status. The type value is defined in the AccountToken table.
In that table, type has relation also and it's defined in AccountToken table:
class AccountToken
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Types")
* #ORM\JoinColumn(name="type_id", referencedColumnName="id", nullable=false)
*/
protected $type;
It's a tricky one for me because I have to relations.
Does someone know how to pull the results:
"Return me posts which have the type='success' value".
$this->createQueryBuilder('p')
->select('p')
->join('App\Entity\AccountToken', 'at', Expr\Join::WITH, 'at.uuid = p.accountTokenId')
->join('App\Entity\ChannelType', 'ct', Expr\Join::WITH, 'ct.id = at.type')
->where('p.accountTokenId = :accountTokenId')
->setParameter('successStatus', Types::STATUS_SUCCESS)
->getQuery()
->getResult();
This is my draft of how I am trying to accomplish it but as I am new with Doctrine, don't have an idea how to join a table that has value in its related table. It's confusing.
Firstly you don't have any parameter successStatus in your query.
I think you over complicate the things.
return $this->createQueryBuilder('p')
->leftJoin('p.accountToken', 'a')
->leftJoin('a.type', 'ct')
->where('ct.successStatus = :successStatus')
->setParameter('successStatus', Types::STATUS_SUCCESS)
->getQuery()
->getResult()
;

How to order by using doctrine?

I used to get my data using this command so the first array is for find variable and the second array is for the order.
So I need to order with specific column:name of test(that is an object that has many columns: id/name ) But I dont know how !! when I enter _test.name I get unrecognized field
->findBy(
array('_id' => $id),
array('_test' =>'ASC')
);
class model1
{
private $_idModel1;
/**
*
* #ORM\ManyToOne()
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="id1",
* referencedColumnName="id2")
* })
*/
private $_test;
}
You cannot order using a relation with the findBy, but you can do it creating a query builder:
$model1Repository->createQueryBuilder('model1')
->leftJoin('model1._test', 't')
->where('model1._id = :id')
->setParameter('id', $id)
->orderBy('t.name', 'ASC');
The code has not been tested, you should adapt it to your code.
It creates a query builder over the model1 class , then joins it with your ManyToOne relation, filters via _id and then orders the result by the _test relation using the name field.
Aslo you need to declare in the #ManyToOne the target entity (https://www.doctrine-project.org/projects/doctrine-orm/en/2.8/reference/association-mapping.html#many-to-one-unidirectional)
#ManyToOne(targetEntity=TheNameOfTheTestClass)

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.

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

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;

Doctrine2 fetching rows that have manyToMany association by QueryBuilder

everyone.
I have 2 entities City and POI. Mapping looks like this:
class City {
/**
* #ORM\ManyToMany(targetEntity="POI", mappedBy="cities")
* #ORM\OrderBy({"position" = "ASC"})
*/
protected $pois;
and
class POI {
/**
* #ORM\ManyToMany(targetEntity="City", inversedBy="pois")
* #ORM\JoinTable(name="poi_cities")
*/
protected $cities;
I would like to fetch all POIs that have at least 1 association with some City using QueryBuilder. I should probably use exists() function but I don't quiet know how.
You'd have to Left join them and check if cities is null.
$qb->select('p', 'c')
->from('AcmeDemoBundle:POI', 'p')
->leftJoin('p.cities', 'c')
->where('c IS NOT NULL');
I haven't tested it, but I hope it gives you the general direction. You can read more about the QueryBuilder from here.
Docrine2 was changed in 2013, so the other solution displays error Error: Cannot add having condition on a non result variable. Now we cannot use joined alias just as a condition variable. We should use any of its properties like c.id
So you should fix the code to
$qb->select('p', 'c')
->from('AcmeDemoBundle:POI', 'p')
->leftJoin('p.cities', 'c')
->where('c.id IS NOT NULL');
$results = $qb->getQuery()->execute();
If you want to select entities that does not have any cities, use IS NULL.
$qb->leftJoin('p.cities', 'city')
->where('city.id IS NULL')
->getQuery()
->execute();
Description of a problem and link to the commit that responsible for that - http://www.doctrine-project.org/jira/browse/DDC-2780

Categories